diff --git a/src/EFCore/Query/Internal/AnonymousObject.cs b/src/EFCore.InMemory/Query/Internal/AnonymousObject.cs similarity index 98% rename from src/EFCore/Query/Internal/AnonymousObject.cs rename to src/EFCore.InMemory/Query/Internal/AnonymousObject.cs index b4736963d72..f9255713d27 100644 --- a/src/EFCore/Query/Internal/AnonymousObject.cs +++ b/src/EFCore.InMemory/Query/Internal/AnonymousObject.cs @@ -6,7 +6,7 @@ using System.Reflection; using JetBrains.Annotations; -namespace Microsoft.EntityFrameworkCore.Query.Internal +namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index ca4788e25eb..56f415be741 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -80,6 +80,13 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { Check.NotNull(binaryExpression, nameof(binaryExpression)); + if (binaryExpression.Left.Type == typeof(object[]) + && binaryExpression.Left is NewArrayExpression + && binaryExpression.NodeType == ExpressionType.Equal) + { + return Visit(ConvertObjectArrayEqualityComparison(binaryExpression)); + } + var newLeft = Visit(binaryExpression.Left); var newRight = Visit(binaryExpression.Right); @@ -1098,6 +1105,38 @@ private static bool CanEvaluate(Expression expression) } } + private static Expression ConvertObjectArrayEqualityComparison(BinaryExpression binaryExpression) + { + var leftExpressions = ((NewArrayExpression)binaryExpression.Left).Expressions; + var rightExpressions = ((NewArrayExpression)binaryExpression.Right).Expressions; + + return leftExpressions.Zip( + rightExpressions, + (l, r) => + { + l = RemoveObjectConvert(l); + r = RemoveObjectConvert(r); + if (l.Type.IsNullableType()) + { + r = r.Type.IsNullableType() ? r : Expression.Convert(r, l.Type); + } + else if (r.Type.IsNullableType()) + { + l = l.Type.IsNullableType() ? l : Expression.Convert(l, r.Type); + } + + return Expression.Equal(l, r); + }) + .Aggregate((a, b) => Expression.AndAlso(a, b)); + + static Expression RemoveObjectConvert(Expression expression) + => expression is UnaryExpression unaryExpression + && expression.Type == typeof(object) + && expression.NodeType == ExpressionType.Convert + ? unaryExpression.Operand + : expression; + } + private static bool IsNullConstantExpression(Expression expression) => expression is ConstantExpression constantExpression && constantExpression.Value == null; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 6a467f5de42..74da00424e6 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore.InMemory.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -457,6 +456,7 @@ private static (Expression, Expression) DecomposeJoinCondition(Expression joinCo : (CreateAnonymousObject(leftExpressions), CreateAnonymousObject(rightExpressions)) : (null, null); + // InMemory joins need to use AnonymousObject to perform correct key comparison for server side joins static Expression CreateAnonymousObject(List expressions) => Expression.New( AnonymousObject.AnonymousObjectCtor, diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 509d3291960..e0b1fc1ff29 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -544,8 +544,7 @@ private SqlExpression CreateJoinPredicate( var outerKey = RemapLambdaBody(outer, outerKeySelector); var innerKey = RemapLambdaBody(inner, innerKeySelector); - if (outerKey is NewExpression outerNew - && outerNew.Type != typeof(AnonymousObject)) + if (outerKey is NewExpression outerNew) { var innerNew = (NewExpression)innerKey; diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index d596e7a6d87..81a684d578e 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -12,7 +12,6 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Utilities; @@ -237,10 +236,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { Check.NotNull(binaryExpression, nameof(binaryExpression)); - if (binaryExpression.Left.Type == typeof(AnonymousObject) + if (binaryExpression.Left.Type == typeof(object[]) + && binaryExpression.Left is NewArrayExpression && binaryExpression.NodeType == ExpressionType.Equal) { - return Visit(ConvertAnonymousObjectEqualityComparison(binaryExpression)); + return Visit(ConvertObjectArrayEqualityComparison(binaryExpression)); } var left = TryRemoveImplicitConvert(binaryExpression.Left); @@ -768,10 +768,10 @@ private static Expression TryRemoveImplicitConvert(Expression expression) return expression; } - private static Expression ConvertAnonymousObjectEqualityComparison(BinaryExpression binaryExpression) + private static Expression ConvertObjectArrayEqualityComparison(BinaryExpression binaryExpression) { - var leftExpressions = ((NewArrayExpression)((NewExpression)binaryExpression.Left).Arguments[0]).Expressions; - var rightExpressions = ((NewArrayExpression)((NewExpression)binaryExpression.Right).Arguments[0]).Expressions; + var leftExpressions = ((NewArrayExpression)binaryExpression.Left).Expressions; + var rightExpressions = ((NewArrayExpression)binaryExpression.Right).Expressions; return leftExpressions.Zip( rightExpressions, diff --git a/src/EFCore/Infrastructure/ExpressionExtensions.cs b/src/EFCore/Infrastructure/ExpressionExtensions.cs index 00814f26495..866bf20326c 100644 --- a/src/EFCore/Infrastructure/ExpressionExtensions.cs +++ b/src/EFCore/Infrastructure/ExpressionExtensions.cs @@ -293,13 +293,11 @@ public static Expression CreateKeyValueReadExpression( bool makeNullable = false) => properties.Count == 1 ? target.CreateEFPropertyExpression(properties[0], makeNullable) - : Expression.New( - AnonymousObject.AnonymousObjectCtor, - Expression.NewArrayInit( - typeof(object), - properties - .Select(p => Expression.Convert(target.CreateEFPropertyExpression(p, makeNullable), typeof(object))) - .Cast())); + : Expression.NewArrayInit( + typeof(object), + properties + .Select(p => Expression.Convert(target.CreateEFPropertyExpression(p, makeNullable), typeof(object))) + .Cast()); /// /// diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index e624ec5135c..9331ee7a2ac 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -215,8 +215,7 @@ protected Expression ExpandNavigation( // This is intentionally deferred to be applied to innerSource.Source // Since outerKey's reference could change if a reference navigation is expanded afterwards var predicateBody = Expression.AndAlso( - outerKey is NewExpression newExpression - && newExpression.Arguments[0] is NewArrayExpression newArrayExpression + outerKey is NewArrayExpression newArrayExpression ? newArrayExpression.Expressions .Select(e => {