From 75d4d6dd3c4a520783171c2e1ddf8a63fd0b6d22 Mon Sep 17 00:00:00 2001 From: maumar Date: Tue, 14 Apr 2020 11:11:16 -0700 Subject: [PATCH] Fix to #20612 - Throw helpful translation exception for String.Equals(String, StringComparison) Adding state to ExpressionTranslatingExpressionVisitor that stores the information about why translation failed. The information is bubbled up and added to the "translation failed" exception. Currently only doing this for string.Equals and non-mapped member properties --- ...yableMethodTranslatingExpressionVisitor.cs | 10 ++- .../CosmosSqlTranslatingExpressionVisitor.cs | 49 ++++++++++- ...yExpressionTranslatingExpressionVisitor.cs | 37 ++++++++- ...yableMethodTranslatingExpressionVisitor.cs | 8 +- ...yableMethodTranslatingExpressionVisitor.cs | 25 ++++-- ...lationalSqlTranslatingExpressionVisitor.cs | 78 ++++++++++++++--- src/EFCore/Properties/CoreStrings.Designer.cs | 22 +++++ src/EFCore/Properties/CoreStrings.resx | 9 ++ ...yableMethodTranslatingExpressionVisitor.cs | 37 ++++++++- .../CustomConvertersCosmosTest.cs | 5 ++ ...thwindAggregateOperatorsQueryCosmosTest.cs | 6 ++ .../NorthwindMiscellaneousQueryCosmosTest.cs | 8 ++ .../Query/GearsOfWarQueryInMemoryTest.cs | 56 ++++--------- ...NorthwindMiscellaneousQueryInMemoryTest.cs | 12 ++- .../Query/QueryNoClientEvalTestBase.cs | 53 ++++++++---- .../CustomConvertersTestBase.cs | 4 +- .../Query/GearsOfWarQueryTestBase.cs | 83 ++++++++++++++++++- ...orthwindAggregateOperatorsQueryTestBase.cs | 13 +++ .../NorthwindIncludeAsyncQueryTestBase.cs | 4 +- .../Query/NorthwindIncludeQueryTestBase.cs | 4 +- .../NorthwindMiscellaneousQueryTestBase.cs | 50 ++++++----- .../NorthwindNavigationsQueryTestBase.cs | 16 ++-- .../Query/NorthwindSelectQueryTestBase.cs | 1 + .../Query/NorthwindWhereQueryTestBase.cs | 31 ++++--- .../Query/QueryTestBase.cs | 6 ++ .../NorthwindSelectQuerySqlServerTest.cs | 4 +- .../Query/QueryBugsTest.cs | 5 +- .../Query/GearsOfWarQuerySqliteTest.cs | 5 +- .../NorthwindFunctionsQuerySqliteTest.cs | 1 + .../NorthwindMiscellaneousQuerySqliteTest.cs | 2 + .../Query/NorthwindSelectQuerySqliteTest.cs | 4 +- .../Query/NorthwindWhereQuerySqliteTest.cs | 2 + 32 files changed, 519 insertions(+), 131 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index ac3cf630e2d..a206198ed48 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -1006,7 +1006,15 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so } private SqlExpression TranslateExpression(Expression expression) - => _sqlTranslator.Translate(expression); + { + var translation = _sqlTranslator.Translate(expression); + if (_sqlTranslator.TranslationErrorDetails != null) + { + ProvideTranslationErrorDetails(_sqlTranslator.TranslationErrorDetails); + } + + return translation; + } private SqlExpression TranslateLambdaExpression( ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs index 34bb6b21c1c..244c8023455 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs @@ -63,6 +63,34 @@ public CosmosSqlTranslatingExpressionVisitor( _sqlVerifyingExpressionVisitor = new SqlTypeMappingVerifyingExpressionVisitor(); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string TranslationErrorDetails { get; private set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual void ProvideTranslationErrorDetails([NotNull] string details) + { + Check.NotNull(details, nameof(details)); + + if (TranslationErrorDetails == null) + { + TranslationErrorDetails = details; + } + else + { + TranslationErrorDetails += " " + details; + } + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -267,10 +295,23 @@ protected override Expression VisitMember(MemberExpression memberExpression) var innerExpression = Visit(memberExpression.Expression); - return TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member)) - ?? (TranslationFailed(memberExpression.Expression, innerExpression, out var sqlInnerExpression) - ? null - : _memberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type)); + var binding = TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member)); + if (binding != null) + { + return binding; + } + + if (innerExpression is EntityReferenceExpression entityReferenceExpression) + { + ProvideTranslationErrorDetails( + CoreStrings.QueryUnableToTranslateMember( + memberExpression.Member.Name, + entityReferenceExpression.EntityType.DisplayName())); + } + + return TranslationFailed(memberExpression.Expression, innerExpression, out var sqlInnerExpression) + ? null + : _memberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type); } /// diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 56f415be741..1b8e019ffaa 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -67,7 +67,32 @@ public InMemoryExpressionTranslatingExpressionVisitor( _model = queryCompilationContext.Model; } + public virtual string TranslationErrorDetails { get; private set; } + + protected virtual void ProvideTranslationErrorDetails([NotNull] string details) + { + Check.NotNull(details, nameof(details)); + + if (TranslationErrorDetails == null) + { + TranslationErrorDetails = details; + } + else + { + TranslationErrorDetails += " " + details; + } + } + public virtual Expression Translate([NotNull] Expression expression) + { + Check.NotNull(expression, nameof(expression)); + + TranslationErrorDetails = null; + + return TranslateInternal(expression); + } + + private Expression TranslateInternal(Expression expression) { var result = Visit(expression); @@ -220,6 +245,14 @@ protected override Expression VisitMember(MemberExpression memberExpression) return result; } + if (innerExpression is EntityReferenceExpression entityReferenceExpression) + { + ProvideTranslationErrorDetails( + CoreStrings.QueryUnableToTranslateMember( + memberExpression.Member.Name, + entityReferenceExpression.EntityType.DisplayName())); + } + var updatedMemberExpression = (Expression)memberExpression.Update(innerExpression); if (innerExpression != null && innerExpression.Type.IsNullableType() @@ -294,7 +327,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Enumerable.Min): case nameof(Enumerable.Sum): { - var translation = Translate(GetSelectorOnGrouping(methodCallExpression, groupByShaperExpression)); + var translation = TranslateInternal(GetSelectorOnGrouping(methodCallExpression, groupByShaperExpression)); if (translation == null) { return null; @@ -337,7 +370,7 @@ MethodInfo GetMethod() groupByShaperExpression.GroupingParameter); } - var translation = Translate(predicate); + var translation = TranslateInternal(predicate); if (translation == null) { return null; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index fcceb5f806a..cc6311be314 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -351,7 +351,7 @@ private Expression TranslateGroupingKey(Expression expression) return memberInitExpression.Update(updatedNewExpression, newBindings); default: - var translation = _expressionTranslator.Translate(expression); + var translation = TranslateExpression(expression); if (translation == null) { return null; @@ -953,6 +953,10 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so private Expression TranslateExpression(Expression expression, bool preserveType = false) { var result = _expressionTranslator.Translate(expression); + if (!string.IsNullOrEmpty(_expressionTranslator.TranslationErrorDetails)) + { + ProvideTranslationErrorDetails(_expressionTranslator.TranslationErrorDetails); + } if (expression != null && result != null @@ -1002,6 +1006,8 @@ public WeakEntityExpandingExpressionVisitor(InMemoryExpressionTranslatingExpress _expressionTranslator = expressionTranslator; } + public string TranslationErrorDetails => _expressionTranslator.TranslationErrorDetails; + public Expression Expand(InMemoryQueryExpression queryExpression, Expression lambdaBody) { _queryExpression = queryExpression; diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 7044c159cd2..60f31ee2ce7 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -39,6 +39,7 @@ public RelationalQueryableMethodTranslatingExpressionVisitor( RelationalDependencies = relationalDependencies; var sqlExpressionFactory = relationalDependencies.SqlExpressionFactory; + _queryCompilationContext = queryCompilationContext; _model = queryCompilationContext.Model; _sqlTranslator = relationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(queryCompilationContext, this); _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_sqlTranslator, sqlExpressionFactory); @@ -55,7 +56,7 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( { RelationalDependencies = parentVisitor.RelationalDependencies; _queryCompilationContext = parentVisitor._queryCompilationContext; - _sqlTranslator = parentVisitor._sqlTranslator; + _sqlTranslator = RelationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(parentVisitor._queryCompilationContext, parentVisitor); _weakEntityExpandingExpressionVisitor = parentVisitor._weakEntityExpandingExpressionVisitor; _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; @@ -79,7 +80,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var arguments = new List(); foreach (var arg in queryableFunctionQueryRootExpression.Arguments) { - var sqlArgument = _sqlTranslator.Translate(arg); + var sqlArgument = TranslateExpression(arg); if (sqlArgument == null) { var methodCall = Expression.Call( @@ -87,7 +88,10 @@ protected override Expression VisitExtension(Expression extensionExpression) function.MethodInfo, queryableFunctionQueryRootExpression.Arguments); - throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCall.Print())); + throw new InvalidOperationException( + TranslationErrorDetails == null + ? CoreStrings.TranslationFailed(methodCall.Print()) + : CoreStrings.TranslationFailedWithDetails(methodCall.Print(), TranslationErrorDetails)); } arguments.Add(sqlArgument); @@ -435,7 +439,7 @@ private Expression TranslateGroupingKey(Expression expression) return memberInitExpression.Update(updatedNewExpression, newBindings); default: - var translation = _sqlTranslator.Translate(expression); + var translation = TranslateExpression(expression); if (translation == null) { return null; @@ -1053,7 +1057,16 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so return source; } - private SqlExpression TranslateExpression(Expression expression) => _sqlTranslator.Translate(expression); + private SqlExpression TranslateExpression(Expression expression) + { + var result = _sqlTranslator.Translate(expression); + if (_sqlTranslator.TranslationErrorDetails != null) + { + ProvideTranslationErrorDetails(_sqlTranslator.TranslationErrorDetails); + } + + return result; + } private SqlExpression TranslateLambdaExpression( ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) @@ -1084,6 +1097,8 @@ public WeakEntityExpandingExpressionVisitor( _sqlExpressionFactory = sqlExpressionFactory; } + public string TranslationErrorDetails => _sqlTranslator.TranslationErrorDetails; + public Expression Expand(SelectExpression selectExpression, Expression lambdaBody) { _selectExpression = selectExpression; diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 81a684d578e..ee0e29ba933 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -26,6 +26,11 @@ public class RelationalSqlTranslatingExpressionVisitor : ExpressionVisitor private static readonly MethodInfo _parameterListValueExtractor = typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterListValueExtractor)); + private static readonly MethodInfo _stringEqualsWithStringComparison + = typeof(string).GetRuntimeMethod(nameof(string.Equals), new[] { typeof(string), typeof(StringComparison) }); + private static readonly MethodInfo _stringEqualsWithStringComparisonStatic + = typeof(string).GetRuntimeMethod(nameof(string.Equals), new[] { typeof(string), typeof(string), typeof(StringComparison) }); + private readonly QueryCompilationContext _queryCompilationContext; private readonly IModel _model; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -49,12 +54,35 @@ public RelationalSqlTranslatingExpressionVisitor( _sqlTypeMappingVerifyingExpressionVisitor = new SqlTypeMappingVerifyingExpressionVisitor(); } + public virtual string TranslationErrorDetails { get; private set; } + + protected virtual void ProvideTranslationErrorDetails([NotNull] string details) + { + Check.NotNull(details, nameof(details)); + + if (TranslationErrorDetails == null) + { + TranslationErrorDetails = details; + } + else + { + TranslationErrorDetails += " " + details; + } + } + protected virtual RelationalSqlTranslatingExpressionVisitorDependencies Dependencies { get; } public virtual SqlExpression Translate([NotNull] Expression expression) { Check.NotNull(expression, nameof(expression)); + TranslationErrorDetails = null; + + return TranslateInternal(expression); + } + + private SqlExpression TranslateInternal(Expression expression) + { var result = Visit(expression); if (result is SqlExpression translation) @@ -88,12 +116,15 @@ public virtual SqlExpression TranslateAverage([NotNull] Expression expression) if (!(expression is SqlExpression sqlExpression)) { - sqlExpression = Translate(expression); + sqlExpression = TranslateInternal(expression); } if (sqlExpression == null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(expression.Print())); + throw new InvalidOperationException( + TranslationErrorDetails == null + ? CoreStrings.TranslationFailed(expression.Print()) + : CoreStrings.TranslationFailedWithDetails(expression.Print(), TranslationErrorDetails)); } var inputType = sqlExpression.Type.UnwrapNullableType(); @@ -163,7 +194,7 @@ public virtual SqlExpression TranslateMax([NotNull] Expression expression) if (!(expression is SqlExpression sqlExpression)) { - sqlExpression = Translate(expression); + sqlExpression = TranslateInternal(expression); } return sqlExpression != null @@ -183,7 +214,7 @@ public virtual SqlExpression TranslateMin([NotNull] Expression expression) if (!(expression is SqlExpression sqlExpression)) { - sqlExpression = Translate(expression); + sqlExpression = TranslateInternal(expression); } return sqlExpression != null @@ -203,12 +234,15 @@ public virtual SqlExpression TranslateSum([NotNull] Expression expression) if (!(expression is SqlExpression sqlExpression)) { - sqlExpression = Translate(expression); + sqlExpression = TranslateInternal(expression); } if (sqlExpression == null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(expression.Print())); + throw new InvalidOperationException( + TranslationErrorDetails == null + ? CoreStrings.TranslationFailed(expression.Print()) + : CoreStrings.TranslationFailedWithDetails(expression.Print(), TranslationErrorDetails)); } var inputType = sqlExpression.Type.UnwrapNullableType(); @@ -330,10 +364,23 @@ protected override Expression VisitMember(MemberExpression memberExpression) var innerExpression = Visit(memberExpression.Expression); - return TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member)) - ?? (TranslationFailed(memberExpression.Expression, Visit(memberExpression.Expression), out var sqlInnerExpression) - ? null - : Dependencies.MemberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type)); + var binding = TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member)); + if (binding != null) + { + return binding; + } + + if (innerExpression is EntityReferenceExpression entityReferenceExpression) + { + ProvideTranslationErrorDetails( + CoreStrings.QueryUnableToTranslateMember( + memberExpression.Member.Name, + entityReferenceExpression.EntityType.DisplayName())); + } + + return TranslationFailed(memberExpression.Expression, Visit(memberExpression.Expression), out var sqlInnerExpression) + ? null + : Dependencies.MemberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type); } protected override Expression VisitMemberInit(MemberInitExpression memberInitExpression) @@ -375,7 +422,10 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp if (translatedAggregate == null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + throw new InvalidOperationException( + TranslationErrorDetails == null + ? CoreStrings.TranslationFailed(methodCallExpression.Print()) + : CoreStrings.TranslationFailedWithDetails(methodCallExpression.Print(), TranslationErrorDetails)); } return translatedAggregate; @@ -553,6 +603,12 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) arguments[i] = sqlArgument; } + + if (methodCallExpression.Method == _stringEqualsWithStringComparison + || methodCallExpression.Method == _stringEqualsWithStringComparisonStatic) + { + ProvideTranslationErrorDetails(CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison); + } } return Dependencies.MethodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments); diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 2d3560d7c2a..49b5064cdd7 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -40,6 +40,14 @@ public static string TranslationFailed([CanBeNull] object expression) GetString("TranslationFailed", nameof(expression)), expression); + /// + /// The LINQ expression '{expression}' could not be translated. Additional information: {details} Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. + /// + public static string TranslationFailedWithDetails([CanBeNull] object expression, [CanBeNull] object details) + => string.Format( + GetString("TranslationFailedWithDetails", nameof(expression), nameof(details)), + expression, details); + /// /// Processing of the LINQ expression '{expression}' by '{visitor}' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information. /// @@ -2542,6 +2550,20 @@ public static string QueryUnableToTranslateEFProperty([CanBeNull] object express GetString("QueryUnableToTranslateEFProperty", nameof(expression)), expression); + /// + /// Translation of member '{member}' on entity type '{entityType}' failed. The specified member does not exist on the entity type, possibly it is not mapped. + /// + public static string QueryUnableToTranslateMember([CanBeNull] object member, [CanBeNull] object entityType) + => string.Format( + GetString("QueryUnableToTranslateMember", nameof(member), nameof(entityType)), + member, entityType); + + /// + /// Translation of 'string.Equals' method which takes 'StringComparison' argument is not supported. + /// + public static string QueryUnableToTranslateStringEqualsWithStringComparison + => GetString("QueryUnableToTranslateStringEqualsWithStringComparison"); + /// /// Invalid {state} encountered. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 76248cb542f..489bdf70a90 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -123,6 +123,9 @@ The LINQ expression '{expression}' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. + + The LINQ expression '{expression}' could not be translated. Additional information: {details} Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. + Processing of the LINQ expression '{expression}' by '{visitor}' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information. @@ -1344,6 +1347,12 @@ Translation of '{expression}' failed. Either source is not an entity type or the specified property does not exist on the entity type. + + Translation of member '{member}' on entity type '{entityType}' failed. The specified member does not exist on the entity type, possibly it is not mapped. + + + Translation of 'string.Equals' method which takes 'StringComparison' argument is not supported. + Invalid {state} encountered. diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index 25086603865..9916be3f897 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -32,6 +32,22 @@ protected QueryableMethodTranslatingExpressionVisitor( protected virtual QueryableMethodTranslatingExpressionVisitorDependencies Dependencies { get; } + public virtual string TranslationErrorDetails { get; private set; } + + protected virtual void ProvideTranslationErrorDetails([NotNull] string details) + { + Check.NotNull(details, nameof(details)); + + if (TranslationErrorDetails == null) + { + TranslationErrorDetails = details; + } + else + { + TranslationErrorDetails += " " + details; + } + } + protected override Expression VisitExtension(Expression extensionExpression) { Check.NotNull(extensionExpression, nameof(extensionExpression)); @@ -50,7 +66,17 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp ShapedQueryExpression CheckTranslated(ShapedQueryExpression translated) { - return translated ?? throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + if (translated != null) + { + return translated; + } + + throw new InvalidOperationException( + TranslationErrorDetails == null + ? CoreStrings.TranslationFailed(methodCallExpression.Print()) + : CoreStrings.TranslationFailedWithDetails( + methodCallExpression.Print(), + TranslationErrorDetails)); } var method = methodCallExpression.Method; @@ -548,7 +574,14 @@ public virtual ShapedQueryExpression TranslateSubquery([NotNull] Expression expr { Check.NotNull(expression, nameof(expression)); - return (ShapedQueryExpression)CreateSubqueryVisitor().Visit(expression); + var subqueryVisitor = CreateSubqueryVisitor(); + var result = (ShapedQueryExpression)subqueryVisitor.Visit(expression); + if (subqueryVisitor.TranslationErrorDetails != null) + { + ProvideTranslationErrorDetails(subqueryVisitor.TranslationErrorDetails); + } + + return result; } protected abstract QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor(); diff --git a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs index bc2f44d1bfa..19634d55e3c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs @@ -179,6 +179,11 @@ public override void Object_to_string_conversion() AssertSql(@" "); } + public override void Collection_property_as_scalar_Count_member() + { + base.Collection_property_as_scalar_Count_member(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index 361517cf452..110f7ec28b7 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -1600,6 +1600,12 @@ public override Task Min_after_default_if_empty_does_not_throw(bool isAsync) return base.Min_after_default_if_empty_does_not_throw(isAsync); } + [ConditionalTheory(Skip = "Issue#20677")] + public override Task Average_with_unmapped_property_access_throws_meaningful_exception(bool async) + { + return base.Average_with_unmapped_property_access_throws_meaningful_exception(async); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index 551870c001a..03c2fa0d9c6 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -4113,6 +4113,14 @@ FROM root c WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""CustomerID""] IN (""ALFKI"") OR (c[""CustomerID""] = null)))"); } + [ConditionalTheory(Skip = "Issue #17246")] + public override Task All_client_and_server_top_level(bool async) + => base.All_client_and_server_top_level(async); + + [ConditionalTheory(Skip = "Issue #17246")] + public override Task All_client_or_server_top_level(bool async) + => base.All_client_or_server_top_level(async); + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index e763cb39903..a2230048f52 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -17,76 +17,54 @@ public GearsOfWarQueryInMemoryTest(GearsOfWarQueryInMemoryFixture fixture, ITest [ConditionalTheory(Skip = "issue #17386")] public override Task Correlated_collection_order_by_constant_null_of_non_mapped_type(bool async) - { - return base.Correlated_collection_order_by_constant_null_of_non_mapped_type(async); - } + => base.Correlated_collection_order_by_constant_null_of_non_mapped_type(async); [ConditionalTheory(Skip = "issue #17386")] public override Task Client_side_equality_with_parameter_works_with_optional_navigations(bool async) - { - return base.Client_side_equality_with_parameter_works_with_optional_navigations(async); - } + => base.Client_side_equality_with_parameter_works_with_optional_navigations(async); [ConditionalTheory(Skip = "issue #17386")] public override Task Where_coalesce_with_anonymous_types(bool async) - { - return base.Where_coalesce_with_anonymous_types(async); - } + => base.Where_coalesce_with_anonymous_types(async); [ConditionalTheory(Skip = "issue #17386")] public override Task Where_conditional_with_anonymous_type(bool async) - { - return base.Where_conditional_with_anonymous_type(async); - } + => base.Where_conditional_with_anonymous_type(async); [ConditionalTheory(Skip = "issue #17386")] public override Task GetValueOrDefault_on_DateTimeOffset(bool async) - { - return base.GetValueOrDefault_on_DateTimeOffset(async); - } + => base.GetValueOrDefault_on_DateTimeOffset(async); [ConditionalFact(Skip = "issue #17537")] public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1() - { - base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1(); - } + => base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1(); [ConditionalFact(Skip = "issue #17537")] public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2() - { - base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2(); - } + => base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2(); [ConditionalTheory(Skip = "issue #17540")] - public override Task - Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool async) - { - return base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex( - async); - } + public override Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool async) + => base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(async); [ConditionalTheory(Skip = "issue #18284")] public override Task GroupBy_with_boolean_groupin_key_thru_navigation_access(bool async) - { - return GroupBy_with_boolean_groupin_key_thru_navigation_access(async); - } + => GroupBy_with_boolean_groupin_key_thru_navigation_access(async); [ConditionalTheory(Skip = "issue #17620")] public override Task Select_subquery_projecting_single_constant_inside_anonymous(bool async) - { - return base.Select_subquery_projecting_single_constant_inside_anonymous(async); - } + => base.Select_subquery_projecting_single_constant_inside_anonymous(async); [ConditionalTheory(Skip = "issue #19683")] public override Task Group_by_on_StartsWith_with_null_parameter_as_argument(bool async) - { - return base.Group_by_on_StartsWith_with_null_parameter_as_argument(async); - } + => base.Group_by_on_StartsWith_with_null_parameter_as_argument(async); [ConditionalTheory(Skip = "issue #18284")] public override Task Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(bool async) - { - return base.Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(async); - } + => base.Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(async); + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Using_string_Equals_with_StringComparison_throws_informative_error(bool async) + => base.Using_string_Equals_with_StringComparison_throws_informative_error(async); } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs index 38dc9462e88..c8b266c0f81 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs @@ -118,10 +118,16 @@ public override Task Random_next_is_not_funcletized_6(bool async) return base.Random_next_is_not_funcletized_6(async); } - [ConditionalTheory] - public override Task DefaultIfEmpty_in_subquery_nested(bool async) + [ConditionalTheory(Skip = "issue#17386")] + public override Task Where_query_composition5(bool async) { - return base.DefaultIfEmpty_in_subquery_nested(async); + return base.Where_query_composition5(async); + } + + [ConditionalTheory(Skip = "issue#17386")] + public override Task Where_query_composition6(bool async) + { + return base.Where_query_composition6(async); } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs index 6c10a8500b6..8d7cbfe2a2d 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs @@ -23,25 +23,30 @@ public abstract class QueryNoClientEvalTestBase : IClassFixture context.Customers.Where(c => c.IsLondon).ToList()); + AssertTranslationFailedWithDetails( + () => context.Customers.Where(c => c.IsLondon).ToList(), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_orderby() { using var context = CreateContext(); - AssertTranslationFailed(() => context.Customers.OrderBy(c => c.IsLondon).ToList()); + AssertTranslationFailedWithDetails( + () => context.Customers.OrderBy(c => c.IsLondon).ToList(), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_orderby_multiple() { using var context = CreateContext(); - AssertTranslationFailed( + AssertTranslationFailedWithDetails( () => context.Customers .OrderBy(c => c.IsLondon) .ThenBy(c => ClientMethod(c)) - .ToList()); + .ToList(), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } private static object ClientMethod(object o) => o.GetHashCode(); @@ -50,28 +55,32 @@ public virtual void Throws_when_orderby_multiple() public virtual void Throws_when_where_subquery_correlated() { using var context = CreateContext(); - AssertTranslationFailed( + AssertTranslationFailedWithDetails( () => context.Customers .Where(c1 => context.Customers.Any(c2 => c1.CustomerID == c2.CustomerID && c2.IsLondon)) - .ToList()); + .ToList(), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_all() { using var context = CreateContext(); - AssertTranslationFailed(() => context.Customers.All(c => c.IsLondon)); + AssertTranslationFailedWithDetails( + () => context.Customers.All(c => c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_from_sql_composed() { using var context = CreateContext(); - AssertTranslationFailed( + AssertTranslationFailedWithDetails( () => context.Customers .FromSqlRaw(NormalizeDelimitersInRawString("select * from [Customers]")) .Where(c => c.IsLondon) - .ToList()); + .ToList(), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] @@ -90,13 +99,14 @@ var customers public virtual void Throws_when_subquery_main_from_clause() { using var context = CreateContext(); - AssertTranslationFailed( + AssertTranslationFailedWithDetails( () => (from c1 in context.Customers .Where(c => c.IsLondon) .OrderBy(c => c.CustomerID) .Take(5) select c1) - .ToList()); + .ToList(), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] @@ -151,28 +161,36 @@ public virtual void Throws_when_group_by() public virtual void Throws_when_first() { using var context = CreateContext(); - AssertTranslationFailed(() => context.Customers.First(c => c.IsLondon)); + AssertTranslationFailedWithDetails( + () => context.Customers.First(c => c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_single() { using var context = CreateContext(); - AssertTranslationFailed(() => context.Customers.Single(c => c.IsLondon)); + AssertTranslationFailedWithDetails( + () => context.Customers.Single(c => c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_first_or_default() { using var context = CreateContext(); - AssertTranslationFailed(() => context.Customers.FirstOrDefault(c => c.IsLondon)); + AssertTranslationFailedWithDetails( + () => context.Customers.FirstOrDefault(c => c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalFact] public virtual void Throws_when_single_or_default() { using var context = CreateContext(); - AssertTranslationFailed(() => context.Customers.SingleOrDefault(c => c.IsLondon)); + AssertTranslationFailedWithDetails( + () => context.Customers.SingleOrDefault(c => c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } private string NormalizeDelimitersInRawString(string sql) @@ -183,6 +201,11 @@ private void AssertTranslationFailed(Action testCode) CoreStrings.TranslationFailed("").Substring(21), Assert.Throws(testCode).Message); + private void AssertTranslationFailedWithDetails(Action testCode, string details) + => Assert.Contains( + CoreStrings.TranslationFailedWithDetails("", details).Substring(21), + Assert.Throws(testCode).Message); + protected NorthwindContext CreateContext() => Fixture.CreateContext(); } } diff --git a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs index e99e7d226bc..b9fa3db8fb6 100644 --- a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Xunit; @@ -581,7 +582,8 @@ public virtual void Collection_property_as_scalar_Count_member() { using var context = CreateContext(); Assert.Equal( - @"The LINQ expression 'DbSet() .Where(c => c.Tags.Count == 2)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.", + CoreStrings.TranslationFailed( + @"DbSet() .Where(c => c.Tags.Count == 2)"), Assert.Throws( () => context.Set().Where(e => e.Tags.Count == 2).ToList()) .Message.Replace("\r", "").Replace("\n", "")); diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index f69b45002d3..17c373f0eac 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -1563,12 +1563,13 @@ public virtual Task Select_subquery_distinct_firstordefault(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task Select_Where_Navigation_Client(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from t in ss.Set() where t.Gear != null && t.Gear.IsMarcus - select t)); + select t), + CoreStrings.QueryUnableToTranslateMember(nameof(Gear.IsMarcus), nameof(Gear))); } [ConditionalTheory] @@ -7469,6 +7470,84 @@ public virtual Task Constant_enum_with_same_underlying_value_as_previously_param .Select(g => g.Rank & MilitaryRank.Private)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Trying_to_access_unmapped_property_throws_informative_error(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(g => g.IsMarcus)), + CoreStrings.QueryUnableToTranslateMember(nameof(Gear.IsMarcus), nameof(Gear))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Using_string_Equals_with_StringComparison_throws_informative_error(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(g => g.CityOfBirthName.Equals("Ephyra", StringComparison.InvariantCulture))), + CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Trying_to_access_unmapped_property_in_projection(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(g => g.IsMarcus)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Trying_to_access_unmapped_property_inside_aggregate(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(c => c.BornGears.Count(g => g.IsMarcus) > 0)), + CoreStrings.QueryUnableToTranslateMember(nameof(Gear.IsMarcus), nameof(Gear))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Trying_to_access_unmapped_property_inside_subquery(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(c => ss.Set().Where(g => g.IsMarcus).Select(g => g.Nickname).FirstOrDefault() == "Marcus")), + CoreStrings.QueryUnableToTranslateMember(nameof(Gear.IsMarcus), nameof(Gear))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Trying_to_access_unmapped_property_inside_join_key_selector(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => from w in ss.Set() + join g in ss.Set() on w.IsAutomatic equals g.IsMarcus into grouping + from g in grouping.DefaultIfEmpty() + select new { w, g }), + CoreStrings.QueryUnableToTranslateMember(nameof(Gear.IsMarcus), nameof(Gear))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Client_projection_with_nested_unmapped_property_bubbles_up_translation_failure_info(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Select(g => new { nested = ss.Set().Where(gg => gg.IsMarcus).ToList() })), + CoreStrings.QueryUnableToTranslateMember(nameof(Gear.IsMarcus), nameof(Gear))); + } + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); protected virtual void ClearLog() diff --git a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs index 646edc41142..a157786cad4 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -1889,6 +1890,18 @@ await AssertCount( ss => ss.Set().Select(o => new { Id = CodeFormat(o.OrderID) })); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Average_with_unmapped_property_access_throws_meaningful_exception(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertAverage( + async, + ss => ss.Set(), + selector: c => c.ShipVia), + CoreStrings.QueryUnableToTranslateMember(nameof(Order.ShipVia), nameof(Order))); + } + private static string CodeFormat(int str) => str.ToString(); [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeAsyncQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeAsyncQueryTestBase.cs index 208e53d65ef..8d8cdea0f04 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeAsyncQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeAsyncQueryTestBase.cs @@ -566,7 +566,9 @@ public virtual async Task Include_collection_with_client_filter() { using var context = CreateContext(); Assert.Contains( - CoreStrings.TranslationFailed("").Substring(21), + CoreStrings.TranslationFailedWithDetails( + "", + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))).Substring(21), (await Assert.ThrowsAsync( () => context.Set() .Include(c => c.Orders) diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs index 39909188a59..5e0f397e3d3 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -1814,7 +1814,9 @@ public virtual void Include_collection_with_client_filter(bool useString) { using var context = CreateContext(); Assert.Contains( - CoreStrings.TranslationFailed("").Substring(21), + CoreStrings.TranslationFailedWithDetails( + "", + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))).Substring(21), Assert.Throws( () => useString ? context.Set() diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index f44f0131546..98e2064fb27 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -559,11 +559,12 @@ public virtual Task Queryable_simple_anonymous_subquery(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task Queryable_reprojection(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set().Where(c => c.IsLondon) - .Select(c => new Customer { CustomerID = "Foo", City = c.City }))); + .Select(c => new Customer { CustomerID = "Foo", City = c.City })), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] @@ -1140,33 +1141,36 @@ public virtual Task All_top_level_subquery_ef_property(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task All_client(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertAll( async, ss => ss.Set(), - predicate: c => c.IsLondon)); + predicate: c => c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task All_client_and_server_top_level(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertAll( async, ss => ss.Set(), - predicate: c => c.CustomerID != "Foo" && c.IsLondon)); + predicate: c => c.CustomerID != "Foo" && c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task All_client_or_server_top_level(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertAll( async, ss => ss.Set(), - predicate: c => c.CustomerID != "Foo" || c.IsLondon)); + predicate: c => c.CustomerID != "Foo" || c.IsLondon), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] @@ -1207,12 +1211,13 @@ public virtual Task Cast_results_to_object(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task First_client_predicate(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertFirst( async, ss => ss.Set().OrderBy(c => c.CustomerID), predicate: c => c.IsLondon, - entryCount: 1)); + entryCount: 1), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] @@ -1935,20 +1940,21 @@ where e1.FirstName [MemberData(nameof(IsAsyncData))] public virtual Task Where_query_composition3(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from c1 in ss.Set() where c1.City == ss.Set().OrderBy(c => c.CustomerID).First(c => c.IsLondon).City select c1, - entryCount: 6)); + entryCount: 6), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_query_composition4(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from c1 in ss.Set().OrderBy(c => c.CustomerID).Take(2) @@ -1957,27 +1963,29 @@ where c1.City from c3 in ss.Set().OrderBy(c => c.IsLondon).ThenBy(c => c.CustomerID) select new { c3 }).First().c3.City select c1, - entryCount: 1)); + entryCount: 1), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_query_composition5(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from c1 in ss.Set() where c1.IsLondon == ss.Set().OrderBy(c => c.CustomerID).First().IsLondon select c1, - entryCount: 85)); + entryCount: 85), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_query_composition6(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from c1 in ss.Set() @@ -1986,7 +1994,8 @@ where c1.IsLondon .Select(c => new { Foo = c }) .First().Foo.IsLondon select c1, - entryCount: 85)); + entryCount: 85), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] @@ -2614,12 +2623,13 @@ public virtual Task OrderBy_anon2(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task OrderBy_client_mixed(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set().OrderBy(c => c.IsLondon).ThenBy(c => c.CompanyName), assertOrder: true, - entryCount: 91)); + entryCount: 91), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs index 388cdd30b8a..8c31159bd29 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -146,13 +147,14 @@ from o2 in ss.Set().Where(o => o.OrderID < 10400) [MemberData(nameof(IsAsyncData))] public virtual Task Select_Where_Navigation_Client(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from o in ss.Set() where o.Customer.IsLondon select o, - entryCount: 46)); + entryCount: 46), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] @@ -603,7 +605,7 @@ public virtual Task Collection_select_nav_prop_all(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task Collection_select_nav_prop_all_client(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from c in ss.Set() @@ -612,7 +614,8 @@ orderby c.CustomerID ss => from c in ss.Set() orderby c.CustomerID select new { All = (c.Orders ?? new List()).All(o => false) }, - assertOrder: true)); + assertOrder: true), + CoreStrings.QueryUnableToTranslateMember(nameof(Order.ShipCity), nameof(Order))); } [ConditionalTheory] @@ -634,13 +637,14 @@ where c.Orders.All(o => o.CustomerID == "ALFKI") [MemberData(nameof(IsAsyncData))] public virtual Task Collection_where_nav_prop_all_client(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => from c in ss.Set() orderby c.CustomerID where c.Orders.All(o => o.ShipCity == "London") - select c)); + select c), + CoreStrings.QueryUnableToTranslateMember(nameof(Order.ShipCity), nameof(Order))); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index 1f7c39b981c..b87b33c820e 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index 7029721de67..3b33eb28537 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -591,11 +592,12 @@ where EF.Property(e, "Title") [MemberData(nameof(IsAsyncData))] public virtual Task Where_client(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set().Where(c => c.IsLondon), - entryCount: 6)); + entryCount: 6), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] @@ -612,59 +614,64 @@ public virtual Task Where_subquery_correlated(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task Where_subquery_correlated_client_eval(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set() .OrderBy(c1 => c1.CustomerID) .Take(5) .Where(c1 => ss.Set().Any(c2 => c1.CustomerID == c2.CustomerID && c2.IsLondon)), - entryCount: 1)); + entryCount: 1), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_client_and_server_top_level(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set().Where(c => c.IsLondon && c.CustomerID != "AROUT"), - entryCount: 5)); + entryCount: 5), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_client_or_server_top_level(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set().Where(c => c.IsLondon || c.CustomerID == "ALFKI"), - entryCount: 7)); + entryCount: 7), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_client_and_server_non_top_level(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set().Where(c => c.CustomerID != "ALFKI" == (c.IsLondon && c.CustomerID != "AROUT")), - entryCount: 6)); + entryCount: 6), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_client_deep_inside_predicate_and_server_top_level(bool async) { - return AssertTranslationFailed( + return AssertTranslationFailedWithDetails( () => AssertQuery( async, ss => ss.Set() .Where(c => c.CustomerID != "ALFKI" && (c.CustomerID == "MAUMAR" || (c.CustomerID != "AROUT" && c.IsLondon))), - entryCount: 5)); + entryCount: 5), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs index 5fa2f444eee..fe62972594d 100644 --- a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs @@ -1153,6 +1153,12 @@ protected static async Task AssertTranslationFailed(Func query) (await Assert.ThrowsAsync(query)) .Message); + protected static async Task AssertTranslationFailedWithDetails(Func query, string details) + => Assert.Contains( + CoreStrings.TranslationFailedWithDetails("", details).Substring(21), + (await Assert.ThrowsAsync(query)) + .Message); + #endregion } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index cc723da7f1a..7841e40dd72 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit.Abstractions; @@ -1100,7 +1101,8 @@ FROM [Orders] AS [o] public override Task Member_binding_after_ctor_arguments_fails_with_client_eval(bool async) { - return AssertTranslationFailed(() => base.Member_binding_after_ctor_arguments_fails_with_client_eval(async)); + return AssertTranslationFailed( + () => base.Member_binding_after_ctor_arguments_fails_with_client_eval(async)); } public override async Task Filtered_collection_projection_is_tracked(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 42dd81259b2..22e17cfd533 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -6723,8 +6723,9 @@ public void Cast_to_non_implemented_interface_is_not_removed_from_expression_tre () => queryBase.Cast().FirstOrDefault(x => x.Id == id)).Message; Assert.Equal( - CoreStrings.TranslationFailed(@"DbSet() .Cast() .Where(e => e.Id == __id_0)"), - message.Replace("\r", "").Replace("\n", "")); + CoreStrings.TranslationFailed( + @"DbSet() .Cast() .Where(e => e.Id == __id_0)"), + message.Replace("\r", "").Replace("\n", "")); } private SqlServerTestStore CreateDatabase18087() diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index c584a7f307b..e6bfe394ec1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Xunit; using Xunit.Abstractions; @@ -44,7 +46,8 @@ public override Task Where_datetimeoffset_second_component(bool async) => AssertTranslationFailed(() => base.Where_datetimeoffset_second_component(async)); public override Task Where_datetimeoffset_utcnow(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_utcnow(async)); + => AssertTranslationFailed( + () => base.Where_datetimeoffset_utcnow(async)); public override Task Where_datetimeoffset_year_component(bool async) => AssertTranslationFailed(() => base.Where_datetimeoffset_year_component(async)); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs index 5afafc9b3b0..bd41716b68c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit.Abstractions; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs index b20dd511cc2..a069da48a69 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs @@ -1,8 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs index a31c6d39644..6d199bdb4a3 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -158,7 +159,8 @@ public override Task Project_single_element_from_collection_with_OrderBy_over_na public override Task Member_binding_after_ctor_arguments_fails_with_client_eval(bool async) { - return AssertTranslationFailed(() => base.Member_binding_after_ctor_arguments_fails_with_client_eval(async)); + return AssertTranslationFailed( + () => base.Member_binding_after_ctor_arguments_fails_with_client_eval(async)); } [ConditionalTheory(Skip = "Issue#17230")] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index cf0e3f04557..be07efc3e3b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; using Xunit.Abstractions;