From 60ccd6ee43ecf341181808b346afe08ef12a6cd5 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 | 26 ++++- .../CosmosSqlTranslatingExpressionVisitor.cs | 73 +++++++++++++- ...yExpressionTranslatingExpressionVisitor.cs | 53 ++++++++++- ...yableMethodTranslatingExpressionVisitor.cs | 12 ++- ...yableMethodTranslatingExpressionVisitor.cs | 27 ++++-- ...lationalSqlTranslatingExpressionVisitor.cs | 94 ++++++++++++++++--- src/EFCore/Properties/CoreStrings.Designer.cs | 22 +++++ src/EFCore/Properties/CoreStrings.resx | 9 ++ ...yableMethodTranslatingExpressionVisitor.cs | 44 ++++++++- ...thwindAggregateOperatorsQueryCosmosTest.cs | 6 ++ .../NorthwindMiscellaneousQueryCosmosTest.cs | 8 ++ .../Query/GearsOfWarQueryInMemoryTest.cs | 56 ++++------- ...NorthwindMiscellaneousQueryInMemoryTest.cs | 84 ++++++----------- .../Query/QueryNoClientEvalTestBase.cs | 53 ++++++++--- .../CustomConvertersTestBase.cs | 4 +- .../Query/GearsOfWarQueryTestBase.cs | 84 ++++++++++++++++- ...orthwindAggregateOperatorsQueryTestBase.cs | 13 +++ .../NorthwindIncludeAsyncQueryTestBase.cs | 4 +- .../Query/NorthwindIncludeQueryTestBase.cs | 4 +- .../NorthwindMiscellaneousQueryTestBase.cs | 72 ++++++++++---- .../NorthwindNavigationsQueryTestBase.cs | 16 ++-- .../Query/NorthwindWhereQueryTestBase.cs | 31 +++--- .../Query/OwnedQueryTestBase.cs | 11 +++ .../Query/QueryTestBase.cs | 6 ++ .../Query/QueryBugsTest.cs | 5 +- 25 files changed, 628 insertions(+), 189 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index 1f1a984b226..259299bc920 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -27,6 +27,8 @@ public class CosmosQueryableMethodTranslatingExpressionVisitor : QueryableMethod { private readonly IModel _model; private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IMemberTranslatorProvider _memberTranslatorProvider; + private readonly IMethodCallTranslatorProvider _methodCallTranslatorProvider; private readonly CosmosSqlTranslatingExpressionVisitor _sqlTranslator; private readonly CosmosProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; @@ -46,11 +48,13 @@ public CosmosQueryableMethodTranslatingExpressionVisitor( { _model = queryCompilationContext.Model; _sqlExpressionFactory = sqlExpressionFactory; + _memberTranslatorProvider = memberTranslatorProvider; + _methodCallTranslatorProvider = methodCallTranslatorProvider; _sqlTranslator = new CosmosSqlTranslatingExpressionVisitor( queryCompilationContext, - sqlExpressionFactory, - memberTranslatorProvider, - methodCallTranslatorProvider); + _sqlExpressionFactory, + _memberTranslatorProvider, + _methodCallTranslatorProvider); _projectionBindingExpressionVisitor = new CosmosProjectionBindingExpressionVisitor(_model, _sqlTranslator); } @@ -66,7 +70,11 @@ protected CosmosQueryableMethodTranslatingExpressionVisitor( { _model = parentVisitor._model; _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; - _sqlTranslator = parentVisitor._sqlTranslator; + _sqlTranslator = new CosmosSqlTranslatingExpressionVisitor( + QueryCompilationContext, + _sqlExpressionFactory, + _memberTranslatorProvider, + _methodCallTranslatorProvider); _projectionBindingExpressionVisitor = new CosmosProjectionBindingExpressionVisitor(_model, _sqlTranslator); } @@ -1091,7 +1099,15 @@ when methodCallExpression.TryGetIndexerArguments(_model, out _, out var property } 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 cf7fd5fb10b..cc48beb6318 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs @@ -35,6 +35,10 @@ public class CosmosSqlTranslatingExpressionVisitor : ExpressionVisitor private static readonly MethodInfo _concatMethodInfo = typeof(string).GetRuntimeMethod(nameof(string.Concat), new[] { typeof(object), typeof(object) }); + 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; @@ -63,6 +67,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 += Environment.NewLine + 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 @@ -70,6 +102,15 @@ public CosmosSqlTranslatingExpressionVisitor( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// 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); @@ -267,10 +308,15 @@ 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; + } + + return TranslationFailed(memberExpression.Expression, innerExpression, out var sqlInnerExpression) + ? null + : _memberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type); } /// @@ -416,7 +462,16 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } - return _methodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments); + var methodTranslationResult = _methodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments); + + if (methodTranslationResult == null + && (methodCallExpression.Method == _stringEqualsWithStringComparison + || methodCallExpression.Method == _stringEqualsWithStringComparisonStatic)) + { + ProvideTranslationErrorDetails(CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison); + } + + return methodTranslationResult; static Expression RemoveObjectConvert(Expression expression) => expression is UnaryExpression unaryExpression @@ -518,6 +573,14 @@ private Expression TryBindMember(Expression source, MemberIdentity member) ? entityReferenceExpression.ParameterEntity.BindMember(member.MemberInfo, entityReferenceExpression.Type, clientEval: false, out _) : entityReferenceExpression.ParameterEntity.BindMember(member.Name, entityReferenceExpression.Type, clientEval: false, out _); + if (result == null) + { + ProvideTranslationErrorDetails( + CoreStrings.QueryUnableToTranslateMember( + member.Name, + entityReferenceExpression.EntityType.DisplayName())); + } + return result switch { EntityProjectionExpression entityProjectionExpression => new EntityReferenceExpression(entityProjectionExpression), diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 58edc24780c..e8644282530 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 += Environment.NewLine + 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); @@ -366,7 +403,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 +446,7 @@ MethodInfo GetMethod() groupByShaperExpression.GroupingParameter); } - var translation = Translate(predicate); + var translation = TranslateInternal(predicate); if (translation == null) { return null; @@ -829,7 +866,17 @@ private Expression TryBindMember(Expression source, MemberIdentity member, Type ? entityType.FindProperty(member.MemberInfo) : entityType.FindProperty(member.Name); - return property != null ? BindProperty(entityReferenceExpression, property, type) : null; + var result = property != null ? BindProperty(entityReferenceExpression, property, type) : null; + + if (result == null) + { + ProvideTranslationErrorDetails( + CoreStrings.QueryUnableToTranslateMember( + member.Name, + entityReferenceExpression.EntityType.DisplayName())); + } + + return result; } private Expression BindProperty(EntityReferenceExpression entityReferenceExpression, IProperty property, Type type) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index f2aa685c340..7e260311b12 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -56,8 +56,8 @@ protected InMemoryQueryableMethodTranslatingExpressionVisitor( [NotNull] InMemoryQueryableMethodTranslatingExpressionVisitor parentVisitor) : base(parentVisitor.Dependencies, parentVisitor.QueryCompilationContext, subquery: true) { - _expressionTranslator = parentVisitor._expressionTranslator; - _weakEntityExpandingExpressionVisitor = parentVisitor._weakEntityExpandingExpressionVisitor; + _expressionTranslator = new InMemoryExpressionTranslatingExpressionVisitor(QueryCompilationContext, parentVisitor); + _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_expressionTranslator); _projectionBindingExpressionVisitor = new InMemoryProjectionBindingExpressionVisitor(this, _expressionTranslator); _model = parentVisitor._model; } @@ -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 (_expressionTranslator.TranslationErrorDetails != null) + { + 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 14be52c8e65..7bf149ac0ea 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -47,6 +47,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); @@ -70,8 +71,8 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( { RelationalDependencies = parentVisitor.RelationalDependencies; _queryCompilationContext = parentVisitor._queryCompilationContext; - _sqlTranslator = parentVisitor._sqlTranslator; - _weakEntityExpandingExpressionVisitor = parentVisitor._weakEntityExpandingExpressionVisitor; + _sqlTranslator = RelationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(parentVisitor._queryCompilationContext, parentVisitor); + _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_sqlTranslator, parentVisitor._sqlExpressionFactory); _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; _subquery = true; @@ -98,7 +99,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var arguments = new List(); foreach (var arg in tableValuedFunctionQueryRootExpression.Arguments) { - var sqlArgument = _sqlTranslator.Translate(arg); + var sqlArgument = TranslateExpression(arg); if (sqlArgument == null) { var methodCall = Expression.Call( @@ -106,7 +107,10 @@ protected override Expression VisitExtension(Expression extensionExpression) function.MethodInfo, tableValuedFunctionQueryRootExpression.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); @@ -469,7 +473,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; @@ -1110,7 +1114,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) @@ -1141,6 +1154,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 314339ecd7a..900dea4b28a 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -35,6 +35,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; @@ -64,6 +69,29 @@ public RelationalSqlTranslatingExpressionVisitor( _sqlTypeMappingVerifyingExpressionVisitor = new SqlTypeMappingVerifyingExpressionVisitor(); } + /// + /// Detailed information about errors encountered during translation. + /// + public virtual string TranslationErrorDetails { get; private set; } + + /// + /// Provides detailed information about error encountered during translation. + /// + /// Detailed information about error encountered during translation. + protected virtual void ProvideTranslationErrorDetails([NotNull] string details) + { + Check.NotNull(details, nameof(details)); + + if (TranslationErrorDetails == null) + { + TranslationErrorDetails = details; + } + else + { + TranslationErrorDetails += Environment.NewLine + details; + } + } + /// /// Parameter object containing service dependencies. /// @@ -78,6 +106,13 @@ 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) @@ -116,12 +151,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(); @@ -206,7 +244,7 @@ public virtual SqlExpression TranslateMax([NotNull] Expression expression) if (!(expression is SqlExpression sqlExpression)) { - sqlExpression = Translate(expression); + sqlExpression = TranslateInternal(expression); } return sqlExpression != null @@ -231,7 +269,7 @@ public virtual SqlExpression TranslateMin([NotNull] Expression expression) if (!(expression is SqlExpression sqlExpression)) { - sqlExpression = Translate(expression); + sqlExpression = TranslateInternal(expression); } return sqlExpression != null @@ -256,12 +294,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(); @@ -391,10 +432,15 @@ 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; + } + + return TranslationFailed(memberExpression.Expression, Visit(memberExpression.Expression), out var sqlInnerExpression) + ? null + : Dependencies.MemberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type); } /// @@ -438,7 +484,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; @@ -618,7 +667,16 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) } } - return Dependencies.MethodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments); + var methodTranslationResult = Dependencies.MethodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments); + + if (methodTranslationResult == null + && (methodCallExpression.Method == _stringEqualsWithStringComparison + || methodCallExpression.Method == _stringEqualsWithStringComparisonStatic)) + { + ProvideTranslationErrorDetails(CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison); + } + + return methodTranslationResult; static Expression RemoveObjectConvert(Expression expression) => expression is UnaryExpression unaryExpression @@ -750,7 +808,17 @@ private Expression TryBindMember(Expression source, MemberIdentity member) ? entityType.FindProperty(member.MemberInfo) : entityType.FindProperty(member.Name); - return property != null ? BindProperty(entityReferenceExpression, property) : null; + var result = property != null ? BindProperty(entityReferenceExpression, property) : null; + + if (result == null) + { + ProvideTranslationErrorDetails( + CoreStrings.QueryUnableToTranslateMember( + member.Name, + entityReferenceExpression.EntityType.DisplayName())); + } + + return result; } private SqlExpression BindProperty(EntityReferenceExpression entityReferenceExpression, IProperty property) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 95935186f11..a2e9c58c9b8 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. /// @@ -2560,6 +2568,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. See https://go.microsoft.com/fwlink/?linkid=2129535 for more information. + /// + public static string QueryUnableToTranslateStringEqualsWithStringComparison + => GetString("QueryUnableToTranslateStringEqualsWithStringComparison"); + /// /// Invalid {state} encountered. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index e32292d21df..0072be98d56 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. See https://go.microsoft.com/fwlink/?linkid=2129535 for more information. + Invalid {state} encountered. diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index e0ecbf9844d..224c1ee5686 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -54,6 +54,29 @@ protected QueryableMethodTranslatingExpressionVisitor( /// protected virtual QueryableMethodTranslatingExpressionVisitorDependencies Dependencies { get; } + /// + /// Additional information about errors encountered during translation. + /// + public virtual string TranslationErrorDetails { get; private set; } + + /// + /// Provides 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 += Environment.NewLine + 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 8d3059fd816..b4bc8bcec64 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -4125,6 +4125,14 @@ public override Task Perform_identity_resolution_reuses_same_instances_across_jo return base.Perform_identity_resolution_reuses_same_instances_across_joins(async); } + [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..38637311e72 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 Client_member_and_unsupported_string_Equals_in_the_same_query(bool async) + => base.Client_member_and_unsupported_string_Equals_in_the_same_query(async); } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs index 38dc9462e88..fb05af7aae8 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs @@ -22,106 +22,84 @@ public NorthwindMiscellaneousQueryInMemoryTest( } public override Task Where_query_composition_entity_equality_one_element_Single(bool async) - { - return Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Where_query_composition_entity_equality_one_element_Single(async)); - } public override Task Where_query_composition_entity_equality_one_element_First(bool async) - { - return Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Where_query_composition_entity_equality_one_element_First(async)); - } public override Task Where_query_composition_entity_equality_no_elements_Single(bool async) - { - return Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Where_query_composition_entity_equality_no_elements_Single(async)); - } public override Task Where_query_composition_entity_equality_no_elements_First(bool async) - { - return Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Where_query_composition_entity_equality_no_elements_First(async)); - } public override Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool async) - { - return Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(async)); - } public override Task Where_query_composition_entity_equality_multiple_elements_Single(bool async) - { - return Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Where_query_composition_entity_equality_multiple_elements_Single(async)); - } // Sending client code to server [ConditionalFact(Skip = "Issue#17050")] public override void Client_code_using_instance_in_anonymous_type() - { - base.Client_code_using_instance_in_anonymous_type(); - } + => base.Client_code_using_instance_in_anonymous_type(); [ConditionalFact(Skip = "Issue#17050")] public override void Client_code_using_instance_in_static_method() - { - base.Client_code_using_instance_in_static_method(); - } + => base.Client_code_using_instance_in_static_method(); [ConditionalFact(Skip = "Issue#17050")] public override void Client_code_using_instance_method_throws() - { - base.Client_code_using_instance_method_throws(); - } + => base.Client_code_using_instance_method_throws(); [ConditionalTheory(Skip = "Issue#17386")] public override Task OrderBy_multiple_queries(bool async) - { - return base.OrderBy_multiple_queries(async); - } + => base.OrderBy_multiple_queries(async); [ConditionalTheory(Skip = "Issue#17386")] public override Task Random_next_is_not_funcletized_1(bool async) - { - return base.Random_next_is_not_funcletized_1(async); - } + => base.Random_next_is_not_funcletized_1(async); [ConditionalTheory(Skip = "Issue#17386")] public override Task Random_next_is_not_funcletized_2(bool async) - { - return base.Random_next_is_not_funcletized_2(async); - } + => base.Random_next_is_not_funcletized_2(async); [ConditionalTheory(Skip = "Issue#17386")] public override Task Random_next_is_not_funcletized_3(bool async) - { - return base.Random_next_is_not_funcletized_3(async); - } + => base.Random_next_is_not_funcletized_3(async); [ConditionalTheory(Skip = "Issue#17386")] public override Task Random_next_is_not_funcletized_4(bool async) - { - return base.Random_next_is_not_funcletized_4(async); - } + => base.Random_next_is_not_funcletized_4(async); [ConditionalTheory(Skip = "Issue#17386")] public override Task Random_next_is_not_funcletized_5(bool async) - { - return base.Random_next_is_not_funcletized_5(async); - } + => base.Random_next_is_not_funcletized_5(async); [ConditionalTheory(Skip = "Issue#17386")] public override Task Random_next_is_not_funcletized_6(bool async) - { - return base.Random_next_is_not_funcletized_6(async); - } + => base.Random_next_is_not_funcletized_6(async); - [ConditionalTheory] - public override Task DefaultIfEmpty_in_subquery_nested(bool async) - { - return base.DefaultIfEmpty_in_subquery_nested(async); - } + [ConditionalTheory(Skip = "issue#17386")] + public override Task Where_query_composition5(bool async) + => base.Where_query_composition5(async); + + [ConditionalTheory(Skip = "issue#17386")] + public override Task Where_query_composition6(bool async) + => base.Where_query_composition6(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); + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Using_static_string_Equals_with_StringComparison_throws_informative_error(bool async) + => base.Using_static_string_Equals_with_StringComparison_throws_informative_error(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 2620c130b63..fcfb133361f 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] @@ -7481,6 +7482,85 @@ public virtual Task Enum_array_contains(bool async) .Where(w => w.SynergyWith != null && types.Contains(w.SynergyWith.AmmunitionType))); } + [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 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 + + Environment.NewLine + 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 01f12db1bbb..312de559ef9 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 a93cd8a45a3..bb2ed89ab58 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] @@ -5919,5 +5929,27 @@ on c.CustomerID equals o.CustomerID Assert.All(arouts, t => Assert.Same(firstArout, t)); Assert.Empty(context.ChangeTracker.Entries()); } + + [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(c => c.CustomerID.Equals("ALFKI", StringComparison.InvariantCulture))), + CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Using_static_string_Equals_with_StringComparison_throws_informative_error(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(c => string.Equals(c.CustomerID, "ALFKI", StringComparison.InvariantCulture))), + CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison); + } } } 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/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/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index f117d106de6..a53e4d1cce6 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -785,6 +785,17 @@ public virtual async Task NoTracking_Include_with_cycles_does_not_throw_when_per } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Trying_to_access_non_existent_indexer_property_throws_meaningful_exception(bool async) + { + return AssertTranslationFailedWithDetails( + () => AssertQuery( + async, + ss => ss.Set().Where(op => (bool)op["Foo"])), + CoreStrings.QueryUnableToTranslateMember("Foo", nameof(OwnedPerson))); + } + protected virtual DbContext CreateContext() => Fixture.CreateContext(); public abstract class OwnedQueryFixtureBase : SharedStoreFixtureBase, IQueryFixtureBase 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/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()