diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs index 3589fcc6195..c8c35aca551 100644 --- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs @@ -178,6 +178,13 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp visitedExpression = TryConvertCollectionContainsToQueryableContains(methodCallExpression); } + if (method.Name == nameof(List<>.Exists) + && method.DeclaringType is { IsGenericType: true } existsDeclaringType + && existsDeclaringType.GetGenericTypeDefinition() == typeof(List<>)) + { + visitedExpression = TryConvertListExistsToQueryableAny(methodCallExpression); + } + if (method.DeclaringType == typeof(EntityFrameworkQueryableExtensions) && method.Name is nameof(EntityFrameworkQueryableExtensions.Include) or nameof(EntityFrameworkQueryableExtensions.ThenInclude) @@ -506,6 +513,35 @@ private Expression TryConvertEnumerableToQueryable(MethodCallExpression methodCa return methodCallExpression.Update(Visit(methodCallExpression.Object), arguments); } + private Expression TryConvertListExistsToQueryableAny(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Object is MemberInitExpression or NewExpression) + { + return base.VisitMethodCall(methodCallExpression); + } + + // List.Exists takes a Predicate; rewrite the lambda to Func so it matches + // Queryable.Any's Expression> parameter. + if (methodCallExpression.Arguments[0] is not LambdaExpression predicateLambda) + { + return base.VisitMethodCall(methodCallExpression); + } + + var sourceType = methodCallExpression.Method.DeclaringType!.GetGenericArguments()[0]; + var rewrittenPredicate = Expression.Lambda( + typeof(Func<,>).MakeGenericType(sourceType, typeof(bool)), + predicateLambda.Body, + predicateLambda.Parameters); + + return VisitMethodCall( + Expression.Call( + QueryableMethods.AnyWithPredicate.MakeGenericMethod(sourceType), + Expression.Call( + QueryableMethods.AsQueryable.MakeGenericMethod(sourceType), + methodCallExpression.Object!), + Expression.Quote(rewrittenPredicate))); + } + private Expression TryConvertCollectionContainsToQueryableContains(MethodCallExpression methodCallExpression) { if (methodCallExpression.Object is MemberInitExpression or NewExpression) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index f3e2da296dd..c19501d12b0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -1103,28 +1103,32 @@ public override async Task Where_Join_Any(bool async) public override async Task Where_Join_Exists(bool async) { - await base.Where_Join_Exists(async); + // Cosmos client evaluation. Issue #17246. + await AssertTranslationFailed(() => base.Where_Join_Exists(async)); AssertSql(); } public override async Task Where_Join_Exists_Inequality(bool async) { - await base.Where_Join_Exists_Inequality(async); + // Cosmos client evaluation. Issue #17246. + await AssertTranslationFailed(() => base.Where_Join_Exists_Inequality(async)); AssertSql(); } public override async Task Where_Join_Exists_Constant(bool async) { - await base.Where_Join_Exists_Constant(async); + // Cosmos client evaluation. Issue #17246. + await AssertTranslationFailed(() => base.Where_Join_Exists_Constant(async)); AssertSql(); } public override async Task Where_Join_Not_Exists(bool async) { - await base.Where_Join_Not_Exists(async); + // Cosmos client evaluation. Issue #17246. + await AssertTranslationFailed(() => base.Where_Join_Not_Exists(async)); AssertSql(); } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index bbff0e844c1..10509d303b0 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -1942,33 +1942,31 @@ public virtual Task Where_Join_Any(bool async) [ConditionalTheory, MemberData(nameof(IsAsyncData))] public virtual Task Where_Join_Exists(bool async) - // Translate List.Exists. Issue #17762. - => AssertTranslationFailed(() => AssertQuery( + => AssertQuery( async, ss => ss.Set() - .Where(c => c.CustomerID == "ALFKI" && c.Orders.Exists(o => o.OrderDate == new DateTime(2008, 10, 24))))); + .Where(c => c.CustomerID == "ALFKI" && c.Orders.Exists(o => o.OrderDate == new DateTime(2008, 10, 24))), + assertEmpty: true); [ConditionalTheory, MemberData(nameof(IsAsyncData))] public virtual Task Where_Join_Exists_Inequality(bool async) - // Translate List.Exists. Issue #17762. - => AssertTranslationFailed(() => AssertQuery( + => AssertQuery( async, ss => ss.Set() - .Where(c => c.CustomerID == "ALFKI" && c.Orders.Exists(o => o.OrderDate != new DateTime(2008, 10, 24))))); + .Where(c => c.CustomerID == "ALFKI" && c.Orders.Exists(o => o.OrderDate != new DateTime(2008, 10, 24)))); [ConditionalTheory, MemberData(nameof(IsAsyncData))] public virtual Task Where_Join_Exists_Constant(bool async) - // Translate List.Exists. Issue #17762. - => AssertTranslationFailed(() => AssertQuery( + => AssertQuery( async, - ss => ss.Set().Where(c => c.CustomerID == "ALFKI" && c.Orders.Exists(o => false)))); + ss => ss.Set().Where(c => c.CustomerID == "ALFKI" && c.Orders.Exists(o => false)), + assertEmpty: true); [ConditionalTheory, MemberData(nameof(IsAsyncData))] public virtual Task Where_Join_Not_Exists(bool async) - // Translate List.Exists. Issue #17762. - => AssertTranslationFailed(() => AssertQuery( + => AssertQuery( async, - ss => ss.Set().Where(c => c.CustomerID == "ALFKI" && !c.Orders.Exists(o => false)))); + ss => ss.Set().Where(c => c.CustomerID == "ALFKI" && !c.Orders.Exists(o => false))); [ConditionalTheory, MemberData(nameof(IsAsyncData))] public virtual Task Multiple_joins_Where_Order_Any(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 138214a588c..45cf929fbfd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -1964,28 +1964,54 @@ public override async Task Where_Join_Exists(bool async) { await base.Where_Join_Exists(async); - AssertSql(); + AssertSql( + """ +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = N'ALFKI' AND EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] AND [o].[OrderDate] = '2008-10-24T00:00:00.000') +"""); } public override async Task Where_Join_Exists_Inequality(bool async) { await base.Where_Join_Exists_Inequality(async); - AssertSql(); + AssertSql( + """ +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = N'ALFKI' AND EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] AND ([o].[OrderDate] <> '2008-10-24T00:00:00.000' OR [o].[OrderDate] IS NULL)) +"""); } public override async Task Where_Join_Exists_Constant(bool async) { await base.Where_Join_Exists_Constant(async); - AssertSql(); + AssertSql( + """ +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE 0 = 1 +"""); } public override async Task Where_Join_Not_Exists(bool async) { await base.Where_Join_Not_Exists(async); - AssertSql(); + AssertSql( + """ +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = N'ALFKI' +"""); } public override async Task Join_OrderBy_Count(bool async)