Skip to content

Commit

Permalink
Fix to #20612 - Throw helpful translation exception for String.Equals…
Browse files Browse the repository at this point in the history
…(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
  • Loading branch information
maumar committed May 12, 2020
1 parent 35f8fc6 commit 60ccd6e
Show file tree
Hide file tree
Showing 25 changed files with 628 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,13 +67,50 @@ public CosmosSqlTranslatingExpressionVisitor(
_sqlVerifyingExpressionVisitor = new SqlTypeMappingVerifyingExpressionVisitor();
}

/// <summary>
/// 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.
/// </summary>
public virtual string TranslationErrorDetails { get; private set; }

/// <summary>
/// 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.
/// </summary>
protected virtual void ProvideTranslationErrorDetails([NotNull] string details)
{
Check.NotNull(details, nameof(details));

if (TranslationErrorDetails == null)
{
TranslationErrorDetails = details;
}
else
{
TranslationErrorDetails += Environment.NewLine + details;
}
}

/// <summary>
/// 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.
/// </summary>
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);

Expand Down Expand Up @@ -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);
}

/// <summary>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,50 @@ public InMemoryExpressionTranslatingExpressionVisitor(
_model = queryCompilationContext.Model;
}

/// <summary>
/// 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.
/// </summary>
public virtual string TranslationErrorDetails { get; private set; }

/// <summary>
/// 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.
/// </summary>
protected virtual void ProvideTranslationErrorDetails([NotNull] string details)
{
Check.NotNull(details, nameof(details));

if (TranslationErrorDetails == null)
{
TranslationErrorDetails = details;
}
else
{
TranslationErrorDetails += Environment.NewLine + details;
}
}

/// <summary>
/// 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.
/// </summary>
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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -409,7 +446,7 @@ MethodInfo GetMethod()
groupByShaperExpression.GroupingParameter);
}

var translation = Translate(predicate);
var translation = TranslateInternal(predicate);
if (translation == null)
{
return null;
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1254,6 +1258,8 @@ public WeakEntityExpandingExpressionVisitor(InMemoryExpressionTranslatingExpress
_expressionTranslator = expressionTranslator;
}

public string TranslationErrorDetails => _expressionTranslator.TranslationErrorDetails;

public Expression Expand(InMemoryQueryExpression queryExpression, Expression lambdaBody)
{
_queryExpression = queryExpression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -98,15 +99,18 @@ protected override Expression VisitExtension(Expression extensionExpression)
var arguments = new List<SqlExpression>();
foreach (var arg in tableValuedFunctionQueryRootExpression.Arguments)
{
var sqlArgument = _sqlTranslator.Translate(arg);
var sqlArgument = TranslateExpression(arg);
if (sqlArgument == null)
{
var methodCall = Expression.Call(
Expression.Constant(null, function.MethodInfo.DeclaringType),
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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1141,6 +1154,8 @@ public WeakEntityExpandingExpressionVisitor(
_sqlExpressionFactory = sqlExpressionFactory;
}

public string TranslationErrorDetails => _sqlTranslator.TranslationErrorDetails;

public Expression Expand(SelectExpression selectExpression, Expression lambdaBody)
{
_selectExpression = selectExpression;
Expand Down
Loading

0 comments on commit 60ccd6e

Please sign in to comment.