Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to #20612 - Throw helpful translation exception for String.Equals(String, StringComparison) #20663

Merged
merged 1 commit into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 (translation == null && _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;
}
}

maumar marked this conversation as resolved.
Show resolved Hide resolved
/// <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 @@ -416,7 +457,16 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
}
}

return _methodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments);
var translation = _methodCallTranslatorProvider.Translate(_model, sqlObject, methodCallExpression.Method, arguments);

if (translation == null
&& (methodCallExpression.Method == _stringEqualsWithStringComparison
|| methodCallExpression.Method == _stringEqualsWithStringComparisonStatic))
{
ProvideTranslationErrorDetails(CoreStrings.QueryUnableToTranslateStringEqualsWithStringComparison);
}

return translation;

static Expression RemoveObjectConvert(Expression expression)
=> expression is UnaryExpression unaryExpression
Expand Down Expand Up @@ -518,6 +568,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,18 @@ private Expression TryBindMember(Expression source, MemberIdentity member, Type
? entityType.FindProperty(member.MemberInfo)
: entityType.FindProperty(member.Name);

return property != null ? BindProperty(entityReferenceExpression, property, type) : null;
if (property != null)
{
return BindProperty(entityReferenceExpression, property, type);

}

ProvideTranslationErrorDetails(
CoreStrings.QueryUnableToTranslateMember(
member.Name,
entityReferenceExpression.EntityType.DisplayName()));

return null;
}

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 @@ -1204,19 +1204,23 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so

private Expression TranslateExpression(Expression expression, bool preserveType = false)
{
var result = _expressionTranslator.Translate(expression);
var translation = _expressionTranslator.Translate(expression);
if (translation == null && _expressionTranslator.TranslationErrorDetails != null)
{
ProvideTranslationErrorDetails(_expressionTranslator.TranslationErrorDetails);
}

if (expression != null
&& result != null
&& translation != null
&& preserveType
&& expression.Type != result.Type)
&& expression.Type != translation.Type)
{
result = expression.Type == typeof(bool)
? Expression.Equal(result, Expression.Constant(true, result.Type))
: (Expression)Expression.Convert(result, expression.Type);
translation = expression.Type == typeof(bool)
? Expression.Equal(translation, Expression.Constant(true, translation.Type))
: (Expression)Expression.Convert(translation, expression.Type);
}

return result;
return translation;
}

private LambdaExpression TranslateLambdaExpression(
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);
maumar marked this conversation as resolved.
Show resolved Hide resolved
_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 @@ -476,7 +480,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 @@ -1139,7 +1143,16 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so
return source;
}

private SqlExpression TranslateExpression(Expression expression) => _sqlTranslator.Translate(expression);
private SqlExpression TranslateExpression(Expression expression)
{
var translation = _sqlTranslator.Translate(expression);
if (translation == null && _sqlTranslator.TranslationErrorDetails != null)
{
ProvideTranslationErrorDetails(_sqlTranslator.TranslationErrorDetails);
}

return translation;
}

private SqlExpression TranslateLambdaExpression(
ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression)
Expand Down Expand Up @@ -1170,6 +1183,8 @@ public WeakEntityExpandingExpressionVisitor(
_sqlExpressionFactory = sqlExpressionFactory;
}

public string TranslationErrorDetails => _sqlTranslator.TranslationErrorDetails;

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