Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support accessing members of a conditional value #35390

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,16 @@ public Expression Expand(InMemoryQueryExpression queryExpression, Expression lam

protected override Expression VisitMember(MemberExpression memberExpression)
{
// Fold member access into conditional, i.e. transform
// (test ? expr1 : expr2).Member -> (test ? expr1.Member : expr2.Member)
if (memberExpression.Expression is ConditionalExpression cond) {
return Visit(Expression.Condition(
cond.Test,
Expression.MakeMemberAccess(cond.IfTrue, memberExpression.Member),
Expression.MakeMemberAccess(cond.IfFalse, memberExpression.Member)
));
}

var innerExpression = Visit(memberExpression.Expression);

return TryExpand(innerExpression, MemberIdentity.Create(memberExpression.Member))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,16 @@ protected override Expression VisitListInit(ListInitExpression listInitExpressio
/// <inheritdoc />
protected override Expression VisitMember(MemberExpression memberExpression)
{
// Fold member access into conditional, i.e. transform
// (test ? expr1 : expr2).Member -> (test ? expr1.Member : expr2.Member)
if (memberExpression.Expression is ConditionalExpression cond) {
return Visit(Expression.Condition(
cond.Test,
Expression.MakeMemberAccess(cond.IfTrue, memberExpression.Member),
Expression.MakeMemberAccess(cond.IfFalse, memberExpression.Member)
));
}

var innerExpression = Visit(memberExpression.Expression);

return TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member), out var expression)
Expand Down
30 changes: 30 additions & 0 deletions test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,36 @@ from t2 in ss.Set<CogTag>()
AssertEqual(e.t2, e.t2);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Conditional_Navigation_With_Trivial_Member_Access(bool async)
=> AssertQuery(
async,
ss => ss.Set<Gear>()
.Where(g => (g.AssignedCity != null ? g.AssignedCity : g.CityOfBirth).Name != "Ephyra")
.Select(g => new { g.Nickname }),
elementSorter: e => e.Nickname);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Conditional_Navigation_With_Member_Access_On_Same_Type(bool async)
=> AssertQuery(
async,
ss => ss.Set<Gear>()
.Where(g => (g.AssignedCity != null ? g.AssignedCity : g.CityOfBirth).Nation == "Tyrus")
.Select(g => new { g.Nickname, g.FullName }),
elementSorter: e => e.Nickname);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Conditional_Navigation_With_Member_Access_On_Related_Types(bool async)
=> AssertQuery(
async,
ss => ss.Set<LocustHorde>()
.Where(g => (g.DeputyCommander != null ? g.DeputyCommander : g.Commander).ThreatLevel == 4)
.Select(g => new { g.Name }),
elementSorter: e => e.Name);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Singleton_Navigation_With_Member_Access(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
public class LocustHorde : Faction
{
public LocustCommander Commander { get; set; }
public LocustLeader DeputyCommander { get; set; }
public List<LocustLeader> Leaders { get; set; }

public string CommanderName { get; set; }
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,65 @@ FROM [Gears] AS [g0]
""");
}

public override async Task Conditional_Navigation_With_Trivial_Member_Access(bool async)
{
await base.Conditional_Navigation_With_Trivial_Member_Access(async);

AssertSql(
"""
SELECT [g].[Nickname]
FROM [Gears] AS [g]
LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name]
INNER JOIN [Cities] AS [c0] ON [g].[CityOfBirthName] = [c0].[Name]
WHERE CASE
WHEN [c].[Name] IS NOT NULL THEN [c].[Name]
ELSE [c0].[Name]
END <> N'Ephyra'
""");
}

public override async Task Conditional_Navigation_With_Member_Access_On_Same_Type(bool async)
{
await base.Conditional_Navigation_With_Member_Access_On_Same_Type(async);

AssertSql(
"""
SELECT [g].[Nickname], [g].[FullName]
FROM [Gears] AS [g]
LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name]
INNER JOIN [Cities] AS [c0] ON [g].[CityOfBirthName] = [c0].[Name]
WHERE CASE
WHEN [c].[Name] IS NOT NULL THEN [c].[Nation]
ELSE [c0].[Nation]
END = N'Tyrus'
""");
}

public override async Task Conditional_Navigation_With_Member_Access_On_Related_Types(bool async)
{
await base.Conditional_Navigation_With_Member_Access_On_Related_Types(async);

AssertSql(
"""
SELECT [f].[Name]
FROM [Factions] AS [f]
INNER JOIN [LocustHordes] AS [l] ON [f].[Id] = [l].[Id]
LEFT JOIN (
SELECT [l0].[Name], [l0].[ThreatLevel]
FROM [LocustLeaders] AS [l0]
) AS [s] ON [l].[DeputyCommanderName] = [s].[Name]
LEFT JOIN (
SELECT [l1].[Name], [l1].[ThreatLevel]
FROM [LocustLeaders] AS [l1]
INNER JOIN [LocustCommanders] AS [l2] ON [l1].[Name] = [l2].[Name]
) AS [s0] ON [l].[CommanderName] = [s0].[Name]
WHERE CASE
WHEN [s].[Name] IS NOT NULL THEN [s].[ThreatLevel]
ELSE [s0].[ThreatLevel]
END = CAST(4 AS smallint)
""");
}

public override async Task Select_Singleton_Navigation_With_Member_Access(bool async)
{
await base.Select_Singleton_Navigation_With_Member_Access(async);
Expand Down Expand Up @@ -3184,7 +3243,7 @@ public override async Task Member_access_on_derived_materialized_entity_using_ca

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -3247,7 +3306,7 @@ public override async Task Navigation_access_on_derived_materialized_entity_usin

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [s].[ThreatLevel] AS [Threat]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -3341,7 +3400,7 @@ public override async Task Include_on_derived_entity_using_OfType(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [s].[Name], [s].[LocustHordeId], [s].[ThreatLevel], [s].[ThreatLevelByte], [s].[ThreatLevelNullableByte], [s].[DefeatedByNickname], [s].[DefeatedBySquadId], [s].[HighCommandId], [s0].[Name], [s0].[LocustHordeId], [s0].[ThreatLevel], [s0].[ThreatLevelByte], [s0].[ThreatLevelNullableByte], [s0].[DefeatedByNickname], [s0].[DefeatedBySquadId], [s0].[HighCommandId], [s0].[Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -3865,7 +3924,7 @@ public override async Task ThenInclude_collection_on_derived_after_derived_refer

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [s].[Name], [s].[LocustHordeId], [s].[ThreatLevel], [s].[ThreatLevelByte], [s].[ThreatLevelNullableByte], [s].[DefeatedByNickname], [s].[DefeatedBySquadId], [s].[HighCommandId], [s0].[Nickname], [s0].[SquadId], [s0].[AssignedCityName], [s0].[CityOfBirthName], [s0].[FullName], [s0].[HasSoulPatch], [s0].[LeaderNickname], [s0].[LeaderSquadId], [s0].[Rank], [s0].[Discriminator], [s1].[Nickname], [s1].[SquadId], [s1].[AssignedCityName], [s1].[CityOfBirthName], [s1].[FullName], [s1].[HasSoulPatch], [s1].[LeaderNickname], [s1].[LeaderSquadId], [s1].[Rank], [s1].[Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -3928,7 +3987,7 @@ public override async Task ThenInclude_reference_on_derived_after_derived_collec

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [s0].[Name], [s0].[LocustHordeId], [s0].[ThreatLevel], [s0].[ThreatLevelByte], [s0].[ThreatLevelNullableByte], [s0].[DefeatedByNickname], [s0].[DefeatedBySquadId], [s0].[HighCommandId], [s0].[Discriminator], [s0].[Nickname], [s0].[SquadId], [s0].[AssignedCityName], [s0].[CityOfBirthName], [s0].[FullName], [s0].[HasSoulPatch], [s0].[LeaderNickname], [s0].[LeaderSquadId], [s0].[Rank], [s0].[Discriminator0]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -3957,7 +4016,7 @@ public override async Task Multiple_derived_included_on_one_method(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [s].[Name], [s].[LocustHordeId], [s].[ThreatLevel], [s].[ThreatLevelByte], [s].[ThreatLevelNullableByte], [s].[DefeatedByNickname], [s].[DefeatedBySquadId], [s].[HighCommandId], [s0].[Nickname], [s0].[SquadId], [s0].[AssignedCityName], [s0].[CityOfBirthName], [s0].[FullName], [s0].[HasSoulPatch], [s0].[LeaderNickname], [s0].[LeaderSquadId], [s0].[Rank], [s0].[Discriminator], [s1].[Nickname], [s1].[SquadId], [s1].[AssignedCityName], [s1].[CityOfBirthName], [s1].[FullName], [s1].[HasSoulPatch], [s1].[LeaderNickname], [s1].[LeaderSquadId], [s1].[Rank], [s1].[Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -5130,10 +5189,10 @@ public override async Task Null_semantics_on_nullable_bool_from_inner_join_subqu

AssertSql(
"""
SELECT [s].[Id], [s].[CapitalName], [s].[Name], [s].[ServerAddress], [s].[CommanderName], [s].[Eradicated], [s].[Discriminator]
SELECT [s].[Id], [s].[CapitalName], [s].[Name], [s].[ServerAddress], [s].[CommanderName], [s].[DeputyCommanderName], [s].[Eradicated], [s].[Discriminator]
FROM [LocustLeaders] AS [l]
INNER JOIN (
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l0].[CommanderName], [l0].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l0].[CommanderName], [l0].[DeputyCommanderName], [l0].[Eradicated], CASE
WHEN [l0].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand All @@ -5150,10 +5209,10 @@ public override async Task Null_semantics_on_nullable_bool_from_left_join_subque

AssertSql(
"""
SELECT [s].[Id], [s].[CapitalName], [s].[Name], [s].[ServerAddress], [s].[CommanderName], [s].[Eradicated], [s].[Discriminator]
SELECT [s].[Id], [s].[CapitalName], [s].[Name], [s].[ServerAddress], [s].[CommanderName], [s].[DeputyCommanderName], [s].[Eradicated], [s].[Discriminator]
FROM [LocustLeaders] AS [l]
LEFT JOIN (
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l0].[CommanderName], [l0].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l0].[CommanderName], [l0].[DeputyCommanderName], [l0].[Eradicated], CASE
WHEN [l0].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -6548,9 +6607,7 @@ SELECT CASE
END
FROM [Gears] AS [g]
ORDER BY CASE
WHEN CASE
WHEN [g].[LeaderNickname] IS NOT NULL THEN ~CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit)
END IS NOT NULL THEN CAST(1 AS bit)
WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
""");
Expand Down Expand Up @@ -6789,7 +6846,7 @@ public override async Task Nav_rewrite_with_convert2(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand All @@ -6810,7 +6867,7 @@ public override async Task Nav_rewrite_with_convert3(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -7126,7 +7183,7 @@ public override async Task Navigation_based_on_complex_expression1(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand All @@ -7149,7 +7206,7 @@ public override async Task Navigation_based_on_complex_expression2(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -9021,7 +9078,7 @@ WHEN [l0].[Name] IS NOT NULL THEN N'LocustCommander'
END AS [Discriminator], [s].[Nickname], [s].[SquadId], [s].[AssignedCityName], [s].[CityOfBirthName], [s].[FullName], [s].[HasSoulPatch], [s].[LeaderNickname], [s].[LeaderSquadId], [s].[Rank], [s].[Discriminator], CASE
WHEN [s].[Nickname] IS NULL OR [s].[SquadId] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [IsNull], [s0].[Id], [s0].[CapitalName], [s0].[Name], [s0].[ServerAddress], [s0].[CommanderName], [s0].[Eradicated], CASE
END AS [IsNull], [s0].[Id], [s0].[CapitalName], [s0].[Name], [s0].[ServerAddress], [s0].[CommanderName], [s0].[DeputyCommanderName], [s0].[Eradicated], CASE
WHEN [s0].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [IsNull], [l2].[Id], [l2].[IsOperational], [l2].[Name], CASE
Expand All @@ -9038,7 +9095,7 @@ FROM [Gears] AS [g]
LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId]
) AS [s] ON [l0].[DefeatedByNickname] = [s].[Nickname] AND [l0].[DefeatedBySquadId] = [s].[SquadId]
LEFT JOIN (
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l1].[CommanderName], [l1].[Eradicated]
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l1].[CommanderName], [l1].[DeputyCommanderName], [l1].[Eradicated]
FROM [Factions] AS [f]
INNER JOIN [LocustHordes] AS [l1] ON [f].[Id] = [l1].[Id]
) AS [s0] ON [l].[Name] = [s0].[CommanderName]
Expand Down Expand Up @@ -9298,7 +9355,7 @@ public override async Task Comparison_with_value_converted_subclass(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -9424,7 +9481,7 @@ public override async Task Include_after_Select_throws(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [c].[Name], [c].[Location], [c].[Nation]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -9511,7 +9568,7 @@ public override async Task Project_derivied_entity_with_convert_to_parent(bool a

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator]
FROM [Factions] AS [f]
Expand Down Expand Up @@ -9678,7 +9735,7 @@ public override async Task Include_on_derived_entity_with_cast(bool async)

AssertSql(
"""
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[Eradicated], CASE
SELECT [f].[Id], [f].[CapitalName], [f].[Name], [f].[ServerAddress], [l].[CommanderName], [l].[DeputyCommanderName], [l].[Eradicated], CASE
WHEN [l].[Id] IS NOT NULL THEN N'LocustHorde'
END AS [Discriminator], [c].[Name], [c].[Location], [c].[Nation]
FROM [Factions] AS [f]
Expand Down
Loading