diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs index 1f46832d55f..82e36b225b1 100644 --- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs @@ -49,23 +49,47 @@ public virtual Expression Normalize(Expression expression) /// 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 override Expression VisitBinary(BinaryExpression binaryExpression) - { - // Convert array[x] to array.ElementAt(x) - if (binaryExpression is - { - NodeType: ExpressionType.ArrayIndex, - Left: var source, - Right: var index - }) + protected override Expression VisitBinary(BinaryExpression binaryExpression) => + binaryExpression switch { - return VisitMethodCall( + // Convert array[x] to array.ElementAt(x) + { NodeType: ExpressionType.ArrayIndex, Left: var source, Right: var index } + => VisitMethodCall( + Expression.Call(EnumerableMethods.ElementAt.MakeGenericMethod(source.Type.GetSequenceType()), source, index)), + + // Convert x.Count > 0 and x.Count != 0 to x.Any() + { + NodeType: ExpressionType.GreaterThan or ExpressionType.NotEqual, + Left: MemberExpression + { + Member: { Name: nameof(ICollection.Count), DeclaringType.IsGenericType: true } member, + Expression: Expression source + }, + Right: ConstantExpression { Value: 0 } + } + when (member.DeclaringType.GetGenericTypeDefinition().GetInterfaces().Any(x => x.GetGenericTypeDefinition() == typeof(ICollection<>))) + => VisitMethodCall( Expression.Call( - EnumerableMethods.ElementAt.MakeGenericMethod(source.Type.GetSequenceType()), source, index)); - } + EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(source.Type.GetSequenceType()), + source)), - return base.VisitBinary(binaryExpression); - } + // Same for arrays: convert x.Length > 0 and x.Length != 0 to x.Any() + { + NodeType: ExpressionType.GreaterThan or ExpressionType.NotEqual, + Left: UnaryExpression + { + NodeType: ExpressionType.ArrayLength, + Operand: Expression source + }, + Right: ConstantExpression { Value: 0 } + } + => VisitMethodCall( + Expression.Call( + EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(source.Type.GetSequenceType()), + source)), + + _ => base.VisitBinary(binaryExpression) + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs index 35ad5fa2366..6fab28fb8c7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs @@ -526,10 +526,10 @@ public override async Task Collection_select_nav_prop_predicate(bool async) AssertSql( """ SELECT CASE - WHEN ( - SELECT COUNT(*) + WHEN EXISTS ( + SELECT 1 FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID]) > 0 THEN CAST(1 AS bit) + WHERE [c].[CustomerID] = [o].[CustomerID]) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END FROM [Customers] AS [c]