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 Apr 27, 2020
1 parent c67507b commit 326b684
Show file tree
Hide file tree
Showing 31 changed files with 549 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,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 @@ -63,6 +63,34 @@ 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 += " " + 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
Expand Down Expand Up @@ -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);
}

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

var translation = Translate(predicate);
var translation = TranslateInternal(predicate);
if (translation == null)
{
return null;
Expand Down
Original file line number Diff line number Diff line change
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 (!string.IsNullOrEmpty(_expressionTranslator.TranslationErrorDetails))
{
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 @@ -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);
Expand All @@ -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;
Expand All @@ -80,15 +81,18 @@ protected override Expression VisitExtension(Expression extensionExpression)
var arguments = new List<SqlExpression>();
foreach (var arg in queryableFunctionQueryRootExpression.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,
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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1085,6 +1098,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 326b684

Please sign in to comment.