Skip to content

Commit 8f3fce9

Browse files
committed
Process Coalesce-simplified Convert node properly in funcletizer
Fixes #35656 (cherry picked from commit c54f51d)
1 parent fb2f189 commit 8f3fce9

File tree

7 files changed

+105
-2
lines changed

7 files changed

+105
-2
lines changed

Diff for: src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor
109109
private static readonly bool UseOldBehavior35111 =
110110
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35111", out var enabled35111) && enabled35111;
111111

112+
private static readonly bool UseOldBehavior35656 =
113+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35656", out var enabled35656) && enabled35656;
114+
112115
private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
113116
.Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
114117

@@ -2132,8 +2135,12 @@ static Expression RemoveConvert(Expression expression)
21322135
}
21332136
}
21342137

2135-
private static Expression ConvertIfNeeded(Expression expression, Type type)
2136-
=> expression.Type == type ? expression : Convert(expression, type);
2138+
private Expression ConvertIfNeeded(Expression expression, Type type)
2139+
=> expression.Type == type
2140+
? expression
2141+
: UseOldBehavior35656
2142+
? Convert(expression, type)
2143+
: Visit(Convert(expression, type));
21372144

21382145
private bool IsGenerallyEvaluatable(Expression expression)
21392146
=> _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model)

Diff for: test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs

+17
Original file line numberDiff line numberDiff line change
@@ -6603,6 +6603,23 @@ public virtual Task Enum_array_contains(bool async)
66036603
.Where(w => w.SynergyWith != null && types.Contains(w.SynergyWith.AmmunitionType)));
66046604
}
66056605

6606+
[ConditionalTheory] // #35656
6607+
[MemberData(nameof(IsAsyncData))]
6608+
public virtual Task Coalesce_with_non_root_evaluatable_Convert(bool async)
6609+
{
6610+
MilitaryRank? rank = MilitaryRank.Private;
6611+
6612+
// The coalesce is simplified away in the funcletizer (since rank is non-null), but a Convert node is added
6613+
// to convert from MilitaryRank? (the type of rank) to the type of the coalesce expression (non-nullable
6614+
// MilitaryRank).
6615+
// This resulting Convert node isn't evaluatable as root (enum convert), and so the NotEvaluatableAsRootHandler
6616+
// is invoked.
6617+
return AssertQuery(
6618+
async,
6619+
// ReSharper disable once ConstantNullCoalescingCondition
6620+
ss => ss.Set<Gear>().Where(g => (rank ?? g.Rank) == g.Rank));
6621+
}
6622+
66066623
[ConditionalTheory]
66076624
[MemberData(nameof(IsAsyncData))]
66086625
public virtual async Task Client_eval_followed_by_aggregate_operation(bool async)

Diff for: test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs

+14
Original file line numberDiff line numberDiff line change
@@ -8162,6 +8162,20 @@ FROM OPENJSON(@__types_0_without_nulls) AS [t]
81628162
""");
81638163
}
81648164

8165+
public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async)
8166+
{
8167+
await base.Coalesce_with_non_root_evaluatable_Convert(async);
8168+
8169+
AssertSql(
8170+
"""
8171+
@__rank_0='1' (Nullable = true)
8172+
8173+
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank]
8174+
FROM [Gears] AS [g]
8175+
WHERE @rank = [g].[Rank]
8176+
""");
8177+
}
8178+
81658179
[ConditionalTheory]
81668180
[MemberData(nameof(IsAsyncData))]
81678181
public async Task DataLength_function_for_string_parameter(bool async)

Diff for: test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs

+20
Original file line numberDiff line numberDiff line change
@@ -10883,6 +10883,26 @@ FROM OPENJSON(@__types_0_without_nulls) AS [t]
1088310883
""");
1088410884
}
1088510885

10886+
public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async)
10887+
{
10888+
await base.Coalesce_with_non_root_evaluatable_Convert(async);
10889+
10890+
AssertSql(
10891+
"""
10892+
@__rank_0='1' (Nullable = true)
10893+
10894+
SELECT [u].[Nickname], [u].[SquadId], [u].[AssignedCityName], [u].[CityOfBirthName], [u].[FullName], [u].[HasSoulPatch], [u].[LeaderNickname], [u].[LeaderSquadId], [u].[Rank], [u].[Discriminator]
10895+
FROM (
10896+
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], N'Gear' AS [Discriminator]
10897+
FROM [Gears] AS [g]
10898+
UNION ALL
10899+
SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator]
10900+
FROM [Officers] AS [o]
10901+
) AS [u]
10902+
WHERE @rank = [u].[Rank]
10903+
""");
10904+
}
10905+
1088610906
[ConditionalTheory]
1088710907
[MemberData(nameof(IsAsyncData))]
1088810908
public async Task DataLength_function_for_string_parameter(bool async)

Diff for: test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs

+17
Original file line numberDiff line numberDiff line change
@@ -9220,6 +9220,23 @@ FROM OPENJSON(@__types_0_without_nulls) AS [t]
92209220
""");
92219221
}
92229222

9223+
public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async)
9224+
{
9225+
await base.Coalesce_with_non_root_evaluatable_Convert(async);
9226+
9227+
AssertSql(
9228+
"""
9229+
@__rank_0='1' (Nullable = true)
9230+
9231+
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE
9232+
WHEN [o].[Nickname] IS NOT NULL THEN N'Officer'
9233+
END AS [Discriminator]
9234+
FROM [Gears] AS [g]
9235+
LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId]
9236+
WHERE @rank = [g].[Rank]
9237+
""");
9238+
}
9239+
92239240
[ConditionalTheory]
92249241
[MemberData(nameof(IsAsyncData))]
92259242
public async Task DataLength_function_for_string_parameter(bool async)

Diff for: test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs

+14
Original file line numberDiff line numberDiff line change
@@ -6987,6 +6987,20 @@ SELECT TOP(1) ~CAST(([g].[Rank] & 2) ^ 2 AS bit) AS [BitwiseTrue], ~CAST(([g].[R
69876987
""");
69886988
}
69896989

6990+
public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async)
6991+
{
6992+
await base.Coalesce_with_non_root_evaluatable_Convert(async);
6993+
6994+
AssertSql(
6995+
"""
6996+
@__rank_0='1' (Nullable = true)
6997+
6998+
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank]
6999+
FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g]
7000+
WHERE @rank = [g].[Rank]
7001+
""");
7002+
}
7003+
69907004
public override async Task Comparison_with_value_converted_subclass(bool async)
69917005
{
69927006
await base.Comparison_with_value_converted_subclass(async);

Diff for: test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs

+14
Original file line numberDiff line numberDiff line change
@@ -6383,6 +6383,20 @@ LIMIT @__p_0
63836383
""");
63846384
}
63856385

6386+
public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async)
6387+
{
6388+
await base.Coalesce_with_non_root_evaluatable_Convert(async);
6389+
6390+
AssertSql(
6391+
"""
6392+
@__rank_0='1' (Nullable = true)
6393+
6394+
SELECT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank"
6395+
FROM "Gears" AS "g"
6396+
WHERE @rank = "g"."Rank"
6397+
""");
6398+
}
6399+
63866400
public override async Task Correlated_collections_with_Take(bool async)
63876401
{
63886402
await base.Correlated_collections_with_Take(async);

0 commit comments

Comments
 (0)