Skip to content

Commit 666dc21

Browse files
authored
Fix casted collections of enums as primitive collections. (#35839)
1 parent 2ecd08f commit 666dc21

File tree

8 files changed

+119
-1
lines changed

8 files changed

+119
-1
lines changed

src/EFCore/Query/QueryRootProcessor.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,28 @@ private Expression VisitQueryRootCandidate(Expression expression, Type elementCl
101101
// Convert that into a NewArrayExpression for use with InlineQueryRootExpression.
102102
case ConstantExpression { Value: IEnumerable values }:
103103
var valueExpressions = new List<ConstantExpression>();
104+
105+
Type? valueClrType = null;
104106
foreach (var value in values)
105107
{
106-
valueExpressions.Add(Expression.Constant(value, elementClrType));
108+
var valueToAdd = value;
109+
110+
if (value is not null)
111+
{
112+
valueClrType ??= value.GetType();
113+
// Enums are implicitly castable to/from the underlying type
114+
// hence calling i.e. LINQ Cast<T> to underlying type
115+
// does not have to do anything and return the enum array.
116+
// But when expanding the constants, we need to change the type,
117+
// otherwise the type of value for the Expression.Constant
118+
// would not be the expected type and fail.
119+
if (valueClrType != elementClrType.UnwrapNullableType() && valueClrType.IsEnum)
120+
{
121+
valueToAdd = Convert.ChangeType(value, elementClrType);
122+
}
123+
}
124+
125+
valueExpressions.Add(Expression.Constant(valueToAdd, elementClrType));
107126
}
108127

109128
if (ShouldConvertToInlineQueryRoot(Expression.NewArrayInit(elementClrType, valueExpressions)))

test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,6 +2091,23 @@ public override Task Nested_contains_with_arrays_and_no_inferred_type_mapping(bo
20912091
SELECT VALUE c
20922092
FROM root c
20932093
WHERE ARRAY_CONTAINS(@strings, (ARRAY_CONTAINS(@ints, c["Int"]) ? "one" : "two"))
2094+
""");
2095+
});
2096+
2097+
public override Task Values_of_enum_casted_to_underlying_value(bool async)
2098+
=> CosmosTestHelpers.Instance.NoSyncTest(
2099+
async, async a =>
2100+
{
2101+
await base.Values_of_enum_casted_to_underlying_value(a);
2102+
2103+
AssertSql(
2104+
"""
2105+
SELECT VALUE c
2106+
FROM root c
2107+
WHERE ((
2108+
SELECT VALUE COUNT(1)
2109+
FROM a IN (SELECT VALUE [0, 1, 2, 3])
2110+
WHERE (a = c["Int"])) > 0)
20942111
""");
20952112
});
20962113

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,13 @@ public virtual Task Nested_contains_with_arrays_and_no_inferred_type_mapping(boo
14971497
ss => ss.Set<PrimitiveCollectionsEntity>().Where(e => strings.Contains(ints.Contains(e.Int) ? "one" : "two")));
14981498
}
14991499

1500+
[ConditionalTheory]
1501+
[MemberData(nameof(IsAsyncData))]
1502+
public virtual Task Values_of_enum_casted_to_underlying_value(bool async)
1503+
=> AssertQuery(
1504+
async,
1505+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(x => Enum.GetValues<MyEnum>().Cast<int>().Count(y => y == x.Int) > 0));
1506+
15001507
public abstract class PrimitiveCollectionsQueryFixtureBase : SharedStoreFixtureBase<PrimitiveCollectionsContext>, IQueryFixtureBase
15011508
{
15021509
private PrimitiveCollectionsData? _expectedData;

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,21 @@ WHERE [p].[NullableWrappedIdWithNullableComparer] NOT IN (11, 44) OR [p].[Nullab
13001300
""");
13011301
}
13021302

1303+
public override async Task Values_of_enum_casted_to_underlying_value(bool async)
1304+
{
1305+
await base.Values_of_enum_casted_to_underlying_value(async);
1306+
1307+
AssertSql(
1308+
"""
1309+
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].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
1310+
FROM [PrimitiveCollectionsEntity] AS [p]
1311+
WHERE (
1312+
SELECT COUNT(*)
1313+
FROM (VALUES (CAST(0 AS int)), (1), (2), (3)) AS [v]([Value])
1314+
WHERE [v].[Value] = [p].[Int]) > 0
1315+
""");
1316+
}
1317+
13031318
[ConditionalFact]
13041319
public virtual void Check_all_tests_overridden()
13051320
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,6 +2288,21 @@ FROM OPENJSON(@values) WITH ([value] int '$') AS [v]
22882288
""");
22892289
}
22902290

2291+
public override async Task Values_of_enum_casted_to_underlying_value(bool async)
2292+
{
2293+
await base.Values_of_enum_casted_to_underlying_value(async);
2294+
2295+
AssertSql(
2296+
"""
2297+
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].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
2298+
FROM [PrimitiveCollectionsEntity] AS [p]
2299+
WHERE (
2300+
SELECT COUNT(*)
2301+
FROM (VALUES (CAST(0 AS int)), (1), (2), (3)) AS [v]([Value])
2302+
WHERE [v].[Value] = [p].[Int]) > 0
2303+
""");
2304+
}
2305+
22912306
[ConditionalFact]
22922307
public virtual void Check_all_tests_overridden()
22932308
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,6 +2100,21 @@ FROM OPENJSON(@strings) WITH ([value] nvarchar(max) '$') AS [s]
21002100
""");
21012101
}
21022102

2103+
public override async Task Values_of_enum_casted_to_underlying_value(bool async)
2104+
{
2105+
await base.Values_of_enum_casted_to_underlying_value(async);
2106+
2107+
AssertSql(
2108+
"""
2109+
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].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
2110+
FROM [PrimitiveCollectionsEntity] AS [p]
2111+
WHERE (
2112+
SELECT COUNT(*)
2113+
FROM (VALUES (CAST(0 AS int)), (1), (2), (3)) AS [v]([Value])
2114+
WHERE [v].[Value] = [p].[Int]) > 0
2115+
""");
2116+
}
2117+
21032118
private void AssertSql(params string[] expected)
21042119
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
21052120

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2312,6 +2312,21 @@ FROM OPENJSON(@values) WITH ([value] int '$') AS [v]
23122312
""");
23132313
}
23142314

2315+
public override async Task Values_of_enum_casted_to_underlying_value(bool async)
2316+
{
2317+
await base.Values_of_enum_casted_to_underlying_value(async);
2318+
2319+
AssertSql(
2320+
"""
2321+
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].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
2322+
FROM [PrimitiveCollectionsEntity] AS [p]
2323+
WHERE (
2324+
SELECT COUNT(*)
2325+
FROM (VALUES (CAST(0 AS int)), (1), (2), (3)) AS [v]([Value])
2326+
WHERE [v].[Value] = [p].[Int]) > 0
2327+
""");
2328+
}
2329+
23152330
[ConditionalFact]
23162331
public virtual void Check_all_tests_overridden()
23172332
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,21 @@ FROM json_each(@values) AS "v"
21912191
""");
21922192
}
21932193

2194+
public override async Task Values_of_enum_casted_to_underlying_value(bool async)
2195+
{
2196+
await base.Values_of_enum_casted_to_underlying_value(async);
2197+
2198+
AssertSql(
2199+
"""
2200+
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"."NullableWrappedId", "p"."NullableWrappedIdWithNullableComparer", "p"."String", "p"."Strings", "p"."WrappedId"
2201+
FROM "PrimitiveCollectionsEntity" AS "p"
2202+
WHERE (
2203+
SELECT COUNT(*)
2204+
FROM (SELECT CAST(0 AS INTEGER) AS "Value" UNION ALL VALUES (1), (2), (3)) AS "v"
2205+
WHERE "v"."Value" = "p"."Int") > 0
2206+
""");
2207+
}
2208+
21942209
[ConditionalFact]
21952210
public virtual void Check_all_tests_overridden()
21962211
=> TestHelpers.AssertAllMethodsOverridden(GetType());

0 commit comments

Comments
 (0)