From 8499531b1513c9e20335a6115f938b8de69da445 Mon Sep 17 00:00:00 2001 From: maumar Date: Thu, 31 Oct 2024 22:22:23 -0700 Subject: [PATCH] Fix to #35024 - Query could not be translated when using a static ICollection/IList field Problem was that when converting primitive collection to inline query root we were not matching the expression if it was constant wrapped in convert. Fix is to remove convert for the purpose of pattern match for the transformation. Fixes #35024 --- src/EFCore/Query/QueryRootProcessor.cs | 7 ++++++- .../NorthwindAggregateOperatorsQueryCosmosTest.cs | 14 ++++++++++++++ .../NorthwindAggregateOperatorsQueryTestBase.cs | 9 +++++++++ ...orthwindAggregateOperatorsQuerySqlServerTest.cs | 12 ++++++++++++ .../NorthwindAggregateOperatorsQuerySqliteTest.cs | 12 ++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/EFCore/Query/QueryRootProcessor.cs b/src/EFCore/Query/QueryRootProcessor.cs index ec87a57f35d..9a4c5c38202 100644 --- a/src/EFCore/Query/QueryRootProcessor.cs +++ b/src/EFCore/Query/QueryRootProcessor.cs @@ -85,7 +85,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private Expression VisitQueryRootCandidate(Expression expression, Type elementClrType) { - switch (expression) + switch (RemoveConvert(expression)) { // An array containing only constants is represented as a ConstantExpression with the array as the value. // Convert that into a NewArrayExpression for use with InlineQueryRootExpression @@ -122,6 +122,11 @@ when listInitExpression.Type.TryGetElementType(typeof(IList<>)) is not null default: return Visit(expression); } + + static Expression RemoveConvert(Expression e) + => e is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary + ? RemoveConvert(unary.Operand) + : e; } /// diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index c5a415fb1b9..5af640a78c1 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -2068,6 +2068,20 @@ public override async Task Contains_with_local_anonymous_type_array_closure(bool AssertSql(); } + public override Task Contains_with_static_IList(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Contains_with_static_IList(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["id"] IN ("ALFKI", "ANATR") +"""); + }); + public override async Task OfType_Select(bool async) { // Contains over subquery. Issue #17246. diff --git a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs index 71e06a15bee..b5ff257f0d3 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs @@ -1361,6 +1361,15 @@ public virtual Task Contains_with_local_anonymous_type_array_closure(bool async) ss => ss.Set().Where(o => ids.Contains(new { Id1 = o.OrderID, Id2 = o.ProductID }))); } + private static readonly IList StaticIds = new List { "ALFKI", "ANATR" }; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_with_static_IList(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => StaticIds.Contains(c.CustomerID))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task OfType_Select(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs index a34a4630461..7f05fa6817b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs @@ -2303,6 +2303,18 @@ public override async Task Contains_with_local_anonymous_type_array_closure(bool AssertSql(); } + public override async Task Contains_with_static_IList(bool async) + { + await base.Contains_with_static_IList(async); + + 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] IN (N'ALFKI', N'ANATR') +"""); + } + public override async Task OfType_Select(bool async) { await base.OfType_Select(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs index 4f5c029aa42..301a48f3f73 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs @@ -123,6 +123,18 @@ public override async Task Contains_with_local_anonymous_type_array_closure(bool public override async Task Contains_with_local_tuple_array_closure(bool async) => await AssertTranslationFailed(() => base.Contains_with_local_tuple_array_closure(async)); + public override async Task Contains_with_static_IList(bool async) + { + await base.Contains_with_static_IList(async); + + 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" IN ('ALFKI', 'ANATR') +"""); + } + public override async Task Contains_inside_aggregate_function_with_GroupBy(bool async) { await base.Contains_inside_aggregate_function_with_GroupBy(async);