diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index d7ef6147907..6b75c0ddd9f 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -1154,7 +1154,15 @@ static IProperty GetPropertyFromMethodCall(MethodCallExpression methodCallExpres } 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 58edc24780c..448ba2cd170 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -79,6 +79,34 @@ public InMemoryExpressionTranslatingExpressionVisitor( _model = queryCompilationContext.Model; } + /// + /// 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 @@ -86,6 +114,15 @@ public InMemoryExpressionTranslatingExpressionVisitor( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// 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); @@ -280,6 +317,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() @@ -366,7 +411,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; @@ -409,7 +454,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 f2aa685c340..8e1988eb5f5 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -465,7 +465,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; @@ -1205,6 +1205,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 @@ -1254,6 +1258,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 62b15aa8f4c..e99e674a57f 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -40,6 +40,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); @@ -56,7 +57,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; @@ -80,7 +81,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( @@ -88,7 +89,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); @@ -436,7 +440,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; @@ -1054,7 +1058,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) @@ -1085,6 +1098,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 2fd40222e8b..72ee059e445 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. /// @@ -2558,6 +2566,20 @@ public static string QueryUnableToTranslateEFProperty([CanBeNull] object express GetString("QueryUnableToTranslateEFProperty", nameof(expression)), expression); + /// + /// Translation of member '{member}' on entity type '{entityType}' failed. Possibly the specified member 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 c9cbb00e898..701cd0d5ce7 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. @@ -1356,6 +1359,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. Possibly the specified member 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 e0ecbf9844d..0ae08e4d22d 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -54,6 +54,29 @@ protected QueryableMethodTranslatingExpressionVisitor( /// protected virtual QueryableMethodTranslatingExpressionVisitorDependencies Dependencies { get; } + /// + /// Additioanl information about errors encountered during translation. + /// + public virtual string TranslationErrorDetails { get; private set; } + + /// + /// Method which allows providing additional information about errors encountered during translation. + /// + /// Error encountered during translation + protected virtual void ProvideTranslationErrorDetails([NotNull] string details) + { + Check.NotNull(details, nameof(details)); + + if (TranslationErrorDetails == null) + { + TranslationErrorDetails = details; + } + else + { + TranslationErrorDetails += " " + details; + } + } + /// /// The query compilation context object for current compilation. /// @@ -79,7 +102,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; @@ -596,7 +629,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; } /// 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..e4047ed41ee 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,96 @@ 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))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Client_member_and_unsupported_string_Equals_in_the_same_query(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(g => g.FullName.Equals(g.Nickname, StringComparison.InvariantCulture) || g.IsMarcus)), + CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison + + " " + 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 605dd6d0be1..12fde7e2aa4 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeAsyncQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeAsyncQueryTestBase.cs @@ -567,7 +567,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 82dd184b187..f3626132c61 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -1812,7 +1812,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;