Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7c8127b

Browse files
authored
Visit arguments in QueryableMethodNormalizingExpressionVisitor after converting List.Contains (#32219) (#32266)
Fixes #32215 Fixes #32218 (cherry picked from commit 08ee676)
1 parent e6fb36c commit 7c8127b

File tree

7 files changed

+111
-6
lines changed

7 files changed

+111
-6
lines changed

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class RelationalQueryableMethodTranslatingExpressionVisitor : QueryableMe
2222
private readonly ISqlExpressionFactory _sqlExpressionFactory;
2323
private readonly bool _subquery;
2424

25+
private static readonly bool UseOldBehavior32218 =
26+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32218", out var enabled32218) && enabled32218;
27+
2528
/// <summary>
2629
/// Creates a new instance of the <see cref="QueryableMethodTranslatingExpressionVisitor" /> class.
2730
/// </summary>
@@ -288,7 +291,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
288291
// Server), we need to fall back to the previous IN translation.
289292
if (method.IsGenericMethod
290293
&& method.GetGenericMethodDefinition() == QueryableMethods.Contains
291-
&& methodCallExpression.Arguments[0] is ParameterQueryRootExpression parameterSource
294+
&& (UseOldBehavior32218
295+
? methodCallExpression.Arguments[0]
296+
: UnwrapAsQueryable(methodCallExpression.Arguments[0])) is ParameterQueryRootExpression parameterSource
292297
&& TranslateExpression(methodCallExpression.Arguments[1]) is SqlExpression item
293298
&& _sqlTranslator.Visit(parameterSource.ParameterExpression) is SqlParameterExpression sqlParameterExpression)
294299
{
@@ -300,6 +305,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
300305
.UpdateResultCardinality(ResultCardinality.Single);
301306
return shapedQueryExpression;
302307
}
308+
309+
static Expression UnwrapAsQueryable(Expression expression)
310+
=> expression is MethodCallExpression { Method: { IsGenericMethod: true } method } methodCall
311+
&& method.GetGenericMethodDefinition() == QueryableMethods.AsQueryable
312+
? methodCall.Arguments[0]
313+
: expression;
303314
}
304315

305316
return translated;

src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public class QueryableMethodNormalizingExpressionVisitor : ExpressionVisitor
1919
private readonly SelectManyVerifyingExpressionVisitor _selectManyVerifyingExpressionVisitor = new();
2020
private readonly GroupJoinConvertingExpressionVisitor _groupJoinConvertingExpressionVisitor = new();
2121

22+
private static readonly bool UseOldBehavior32215 =
23+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32215", out var enabled32215) && enabled32215;
24+
2225
/// <summary>
2326
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
2427
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -435,12 +438,14 @@ private Expression TryConvertListContainsToQueryableContains(MethodCallExpressio
435438

436439
var sourceType = methodCallExpression.Method.DeclaringType!.GetGenericArguments()[0];
437440

438-
return Expression.Call(
441+
var converted = Expression.Call(
439442
QueryableMethods.Contains.MakeGenericMethod(sourceType),
440443
Expression.Call(
441444
QueryableMethods.AsQueryable.MakeGenericMethod(sourceType),
442445
methodCallExpression.Object!),
443446
methodCallExpression.Arguments[0]);
447+
448+
return UseOldBehavior32215 ? converted : VisitMethodCall(converted);
444449
}
445450

446451
private static bool CanConvertEnumerableToQueryable(Type enumerableType, Type queryableType)

test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@ public virtual Task Project_primitive_collections_element(bool async)
807807
},
808808
assertOrder: true);
809809

810-
[ConditionalTheory] // #32208
810+
[ConditionalTheory] // #32208, #32215
811811
[MemberData(nameof(IsAsyncData))]
812812
public virtual Task Nested_contains_with_Lists_and_no_inferred_type_mapping(bool async)
813813
{
@@ -821,6 +821,20 @@ public virtual Task Nested_contains_with_Lists_and_no_inferred_type_mapping(bool
821821
ss => ss.Set<PrimitiveCollectionsEntity>().Where(e => strings.Contains(ints.Contains(e.Int) ? "one" : "two")));
822822
}
823823

824+
[ConditionalTheory] // #32208, #32215
825+
[MemberData(nameof(IsAsyncData))]
826+
public virtual Task Nested_contains_with_arrays_and_no_inferred_type_mapping(bool async)
827+
{
828+
var ints = new[] { 1, 2, 3 };
829+
var strings = new[] { "one", "two", "three" };
830+
831+
// Note that in this query, the outer Contains really has no type mapping, neither for its source (collection parameter), nor
832+
// for its item (the conditional expression returns constants). The default type mapping must be applied.
833+
return AssertQuery(
834+
async,
835+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(e => strings.Contains(ints.Contains(e.Int) ? "one" : "two")));
836+
}
837+
824838
public abstract class PrimitiveCollectionsQueryFixtureBase : SharedStoreFixtureBase<PrimitiveCollectionsContext>, IQueryFixtureBase
825839
{
826840
private PrimitiveArrayData? _expectedData;

test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,21 @@ END IN (N'one', N'two', N'three')
625625
""");
626626
}
627627

628+
public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapping(bool async)
629+
{
630+
await base.Nested_contains_with_arrays_and_no_inferred_type_mapping(async);
631+
632+
AssertSql(
633+
"""
634+
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
635+
FROM [PrimitiveCollectionsEntity] AS [p]
636+
WHERE CASE
637+
WHEN [p].[Int] IN (1, 2, 3) THEN N'one'
638+
ELSE N'two'
639+
END IN (N'one', N'two', N'three')
640+
""");
641+
}
642+
628643
[ConditionalFact]
629644
public virtual void Check_all_tests_overridden()
630645
=> TestHelpers.AssertAllMethodsOverridden(GetType());

test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1233,12 +1233,40 @@ public override async Task Nested_contains_with_Lists_and_no_inferred_type_mappi
12331233

12341234
AssertSql(
12351235
"""
1236+
@__ints_1='[1,2,3]' (Size = 4000)
12361237
@__strings_0='["one","two","three"]' (Size = 4000)
12371238
12381239
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
12391240
FROM [PrimitiveCollectionsEntity] AS [p]
12401241
WHERE CASE
1241-
WHEN [p].[Int] IN (1, 2, 3) THEN N'one'
1242+
WHEN [p].[Int] IN (
1243+
SELECT [i].[value]
1244+
FROM OPENJSON(@__ints_1) WITH ([value] int '$') AS [i]
1245+
) THEN N'one'
1246+
ELSE N'two'
1247+
END IN (
1248+
SELECT [s].[value]
1249+
FROM OPENJSON(@__strings_0) WITH ([value] nvarchar(max) '$') AS [s]
1250+
)
1251+
""");
1252+
}
1253+
1254+
public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapping(bool async)
1255+
{
1256+
await base.Nested_contains_with_arrays_and_no_inferred_type_mapping(async);
1257+
1258+
AssertSql(
1259+
"""
1260+
@__ints_1='[1,2,3]' (Size = 4000)
1261+
@__strings_0='["one","two","three"]' (Size = 4000)
1262+
1263+
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
1264+
FROM [PrimitiveCollectionsEntity] AS [p]
1265+
WHERE CASE
1266+
WHEN [p].[Int] IN (
1267+
SELECT [i].[value]
1268+
FROM OPENJSON(@__ints_1) WITH ([value] int '$') AS [i]
1269+
) THEN N'one'
12421270
ELSE N'two'
12431271
END IN (
12441272
SELECT [s].[value]

test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3982,13 +3982,17 @@ public virtual async Task Nested_contains_with_enum()
39823982

39833983
AssertSql(
39843984
"""
3985+
@__todoTypes_1='[0]' (Size = 4000)
39853986
@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
39863987
@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
39873988
39883989
SELECT [t].[Id], [t].[Type]
39893990
FROM [Todos] AS [t]
39903991
WHERE CASE
3991-
WHEN [t].[Type] = 0 THEN @__key_2
3992+
WHEN [t].[Type] IN (
3993+
SELECT [t0].[value]
3994+
FROM OPENJSON(@__todoTypes_1) WITH ([value] int '$') AS [t0]
3995+
) THEN @__key_2
39923996
ELSE @__key_2
39933997
END IN (
39943998
SELECT [k].[value]

test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,12 +1115,40 @@ public override async Task Nested_contains_with_Lists_and_no_inferred_type_mappi
11151115

11161116
AssertSql(
11171117
"""
1118+
@__ints_1='[1,2,3]' (Size = 7)
11181119
@__strings_0='["one","two","three"]' (Size = 21)
11191120
11201121
SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings"
11211122
FROM "PrimitiveCollectionsEntity" AS "p"
11221123
WHERE CASE
1123-
WHEN "p"."Int" IN (1, 2, 3) THEN 'one'
1124+
WHEN "p"."Int" IN (
1125+
SELECT "i"."value"
1126+
FROM json_each(@__ints_1) AS "i"
1127+
) THEN 'one'
1128+
ELSE 'two'
1129+
END IN (
1130+
SELECT "s"."value"
1131+
FROM json_each(@__strings_0) AS "s"
1132+
)
1133+
""");
1134+
}
1135+
1136+
public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapping(bool async)
1137+
{
1138+
await base.Nested_contains_with_arrays_and_no_inferred_type_mapping(async);
1139+
1140+
AssertSql(
1141+
"""
1142+
@__ints_1='[1,2,3]' (Size = 7)
1143+
@__strings_0='["one","two","three"]' (Size = 21)
1144+
1145+
SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings"
1146+
FROM "PrimitiveCollectionsEntity" AS "p"
1147+
WHERE CASE
1148+
WHEN "p"."Int" IN (
1149+
SELECT "i"."value"
1150+
FROM json_each(@__ints_1) AS "i"
1151+
) THEN 'one'
11241152
ELSE 'two'
11251153
END IN (
11261154
SELECT "s"."value"

0 commit comments

Comments
 (0)