Skip to content

Commit 2b2b13c

Browse files
committed
Fix to #12575 - Query: compilation error for query with SelectMany optional navigation followed by a Select optional navigation
Problem was that when we rewrite SelectMany, query sources get changed and need to be updated. We do that already for Include query annotations, but we were not doing it for query sources stored in CorrelatedSubqueryMetadata. Fix is to update CorrelatedSubqueryMetadata query sources after we rewrite SelectMany, just like we do for Include annotations.
1 parent 7486099 commit 2b2b13c

6 files changed

Lines changed: 150 additions & 4 deletions

File tree

src/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4078,5 +4078,44 @@ private class ProjectedDto<T>
40784078
{
40794079
public T Value { get; set; }
40804080
}
4081+
4082+
[ConditionalFact]
4083+
public virtual void SelectMany_navigation_property_followed_by_select_collection_navigation()
4084+
{
4085+
AssertQuery<Level1>(
4086+
l1s => l1s.SelectMany(l1 => l1.OneToMany_Optional).Select(l2 => new { l2.Id, l2.OneToMany_Optional }),
4087+
elementSorter: e => e.Id,
4088+
elementAsserter: (e, a) =>
4089+
{
4090+
Assert.Equal(e.Id, a.Id);
4091+
CollectionAsserter<Level3>(ee => ee.Id, (ee, aa) => Assert.Equal(ee.Id, aa.Id))(e.OneToMany_Optional, a.OneToMany_Optional);
4092+
});
4093+
}
4094+
4095+
[ConditionalFact]
4096+
public virtual void Multiple_SelectMany_navigation_property_followed_by_select_collection_navigation()
4097+
{
4098+
AssertQuery<Level1>(
4099+
l1s => l1s.SelectMany(l1 => l1.OneToMany_Optional).SelectMany(l2 => l2.OneToMany_Optional).Select(l2 => new { l2.Id, l2.OneToMany_Optional }),
4100+
elementSorter: e => e.Id,
4101+
elementAsserter: (e, a) =>
4102+
{
4103+
Assert.Equal(e.Id, a.Id);
4104+
CollectionAsserter<Level4>(ee => ee.Id, (ee, aa) => Assert.Equal(ee.Id, aa.Id))(e.OneToMany_Optional, a.OneToMany_Optional);
4105+
});
4106+
}
4107+
4108+
[ConditionalFact]
4109+
public virtual void SelectMany_navigation_property_with_include_and_followed_by_select_collection_navigation()
4110+
{
4111+
using (var ctx = CreateContext())
4112+
{
4113+
var query = ctx.LevelOne.SelectMany(l1 => l1.OneToMany_Optional).Include(l2 => l2.OneToMany_Required).Select(l2 => new { l2, l2.OneToMany_Optional });
4114+
var result = query.ToList();
4115+
4116+
Assert.True(result.All(r => r.l2.OneToMany_Required != null));
4117+
Assert.True(result.Any(r => r.OneToMany_Optional.Count > 0));
4118+
}
4119+
}
40814120
}
40824121
}

src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,18 @@ public override void Null_check_in_Dto_projection_should_not_be_removed()
441441
{
442442
}
443443

444+
public override void SelectMany_navigation_property_followed_by_select_collection_navigation()
445+
{
446+
}
447+
448+
public override void Multiple_SelectMany_navigation_property_followed_by_select_collection_navigation()
449+
{
450+
}
451+
452+
public override void SelectMany_navigation_property_with_include_and_followed_by_select_collection_navigation()
453+
{
454+
}
455+
444456
#endregion
445457
}
446458
}

src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,10 @@ private Expression RewriteSelectManyNavigationsIntoJoins(
12081208
ReferenceReplacingExpressionVisitor
12091209
.ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false));
12101210

1211-
AdjustIncludeAnnotations(querySourceMapping, additionalFromClauseBeingProcessed, querySourceReferenceExpression.ReferencedQuerySource);
1211+
AdjustQueryCompilationContextStateAfterSelectMany(
1212+
querySourceMapping,
1213+
additionalFromClauseBeingProcessed,
1214+
querySourceReferenceExpression.ReferencedQuerySource);
12121215

12131216
return querySourceReferenceExpression;
12141217
}
@@ -1263,7 +1266,10 @@ private Expression RewriteSelectManyInsideSubqueryIntoJoins(
12631266
e => ReferenceReplacingExpressionVisitor
12641267
.ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false));
12651268

1266-
AdjustIncludeAnnotations(querySourceMapping, additionalFromClauseBeingProcessed, navigationJoin.QuerySourceReferenceExpression.ReferencedQuerySource);
1269+
AdjustQueryCompilationContextStateAfterSelectMany(
1270+
querySourceMapping,
1271+
additionalFromClauseBeingProcessed,
1272+
navigationJoin.QuerySourceReferenceExpression.ReferencedQuerySource);
12671273

12681274
return navigationJoin.QuerySourceReferenceExpression;
12691275
}
@@ -1276,7 +1282,7 @@ private Expression RewriteSelectManyInsideSubqueryIntoJoins(
12761282
return outerQuerySourceReferenceExpression;
12771283
}
12781284

1279-
private void AdjustIncludeAnnotations(QuerySourceMapping querySourceMapping, IQuerySource querySourceBeingProcessed, IQuerySource resultQuerySource)
1285+
private void AdjustQueryCompilationContextStateAfterSelectMany(QuerySourceMapping querySourceMapping, IQuerySource querySourceBeingProcessed, IQuerySource resultQuerySource)
12801286
{
12811287
foreach (var includeResultOperator in _queryModelVisitor.QueryCompilationContext.QueryAnnotations.OfType<IncludeResultOperator>())
12821288
{
@@ -1291,6 +1297,18 @@ private void AdjustIncludeAnnotations(QuerySourceMapping querySourceMapping, IQu
12911297
includeResultOperator.QuerySource = resultQuerySource;
12921298
}
12931299
}
1300+
1301+
if (_queryModelVisitor.QueryCompilationContext.CorrelatedSubqueryMetadataMap != null
1302+
&& (!AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue12575", out var isEnabled) || !isEnabled))
1303+
{
1304+
foreach (var mapping in _queryModelVisitor.QueryCompilationContext.CorrelatedSubqueryMetadataMap)
1305+
{
1306+
if (mapping.Value.ParentQuerySource == querySourceBeingProcessed)
1307+
{
1308+
mapping.Value.ParentQuerySource = resultQuerySource;
1309+
}
1310+
}
1311+
}
12941312
}
12951313

12961314
private JoinClause BuildJoinFromNavigation(

src/EFCore/Query/Internal/CorrelatedSubqueryMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ public CorrelatedSubqueryMetadata(
5757
/// <summary>
5858
/// Query source that is origin of the collection navigation.
5959
/// </summary>
60-
public virtual IQuerySource ParentQuerySource { get; }
60+
public virtual IQuerySource ParentQuerySource { get; internal set; }
6161
}
6262
}

src/EFCore/Query/QueryCompilationContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ public virtual bool TryGetCorrelatedSubqueryMetadata(
119119
&& _correlatedSubqueryMetadataMap.TryGetValue(mainFromClause, out correlatedSubqueryMetadata);
120120
}
121121

122+
/// <summary>
123+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
124+
/// directly from your code. This API may change or be removed in future releases.
125+
/// </summary>
126+
internal virtual IDictionary<MainFromClause, CorrelatedSubqueryMetadata> CorrelatedSubqueryMetadataMap => _correlatedSubqueryMetadataMap;
127+
122128
/// <summary>
123129
/// Gets the model.
124130
/// </summary>

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3741,6 +3741,77 @@ FROM [LevelOne] AS [l10]
37413741
ORDER BY [t].[Id]");
37423742
}
37433743

3744+
public override void SelectMany_navigation_property_followed_by_select_collection_navigation()
3745+
{
3746+
base.SelectMany_navigation_property_followed_by_select_collection_navigation();
3747+
3748+
AssertSql(
3749+
@"SELECT [l1.OneToMany_Optional].[Id]
3750+
FROM [LevelOne] AS [l1]
3751+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional] ON [l1].[Id] = [l1.OneToMany_Optional].[OneToMany_Optional_InverseId]
3752+
ORDER BY [l1.OneToMany_Optional].[Id]",
3753+
//
3754+
@"SELECT [l1.OneToMany_Optional.OneToMany_Optional].[Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Optional_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Required_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Name], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_SelfId], [t].[Id]
3755+
FROM [LevelThree] AS [l1.OneToMany_Optional.OneToMany_Optional]
3756+
INNER JOIN (
3757+
SELECT [l1.OneToMany_Optional0].[Id]
3758+
FROM [LevelOne] AS [l10]
3759+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional0] ON [l10].[Id] = [l1.OneToMany_Optional0].[OneToMany_Optional_InverseId]
3760+
) AS [t] ON [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId] = [t].[Id]
3761+
ORDER BY [t].[Id]");
3762+
}
3763+
3764+
public override void Multiple_SelectMany_navigation_property_followed_by_select_collection_navigation()
3765+
{
3766+
base.Multiple_SelectMany_navigation_property_followed_by_select_collection_navigation();
3767+
3768+
AssertSql(
3769+
@"SELECT [l1.OneToMany_Optional.OneToMany_Optional].[Id]
3770+
FROM [LevelOne] AS [l1]
3771+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional] ON [l1].[Id] = [l1.OneToMany_Optional].[OneToMany_Optional_InverseId]
3772+
INNER JOIN [LevelThree] AS [l1.OneToMany_Optional.OneToMany_Optional] ON [l1.OneToMany_Optional].[Id] = [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId]
3773+
ORDER BY [l1.OneToMany_Optional.OneToMany_Optional].[Id]",
3774+
//
3775+
@"SELECT [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[Id], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[Level3_Optional_Id], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[Level3_Required_Id], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[Name], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_SelfId], [t].[Id]
3776+
FROM [LevelFour] AS [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional]
3777+
INNER JOIN (
3778+
SELECT [l1.OneToMany_Optional.OneToMany_Optional0].[Id]
3779+
FROM [LevelOne] AS [l10]
3780+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional0] ON [l10].[Id] = [l1.OneToMany_Optional0].[OneToMany_Optional_InverseId]
3781+
INNER JOIN [LevelThree] AS [l1.OneToMany_Optional.OneToMany_Optional0] ON [l1.OneToMany_Optional0].[Id] = [l1.OneToMany_Optional.OneToMany_Optional0].[OneToMany_Optional_InverseId]
3782+
) AS [t] ON [l1.OneToMany_Optional.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId] = [t].[Id]
3783+
ORDER BY [t].[Id]");
3784+
}
3785+
3786+
public override void SelectMany_navigation_property_with_include_and_followed_by_select_collection_navigation()
3787+
{
3788+
base.SelectMany_navigation_property_with_include_and_followed_by_select_collection_navigation();
3789+
3790+
AssertSql(
3791+
@"SELECT [l1.OneToMany_Optional].[Id], [l1.OneToMany_Optional].[Date], [l1.OneToMany_Optional].[Level1_Optional_Id], [l1.OneToMany_Optional].[Level1_Required_Id], [l1.OneToMany_Optional].[Name], [l1.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional].[OneToOne_Optional_SelfId]
3792+
FROM [LevelOne] AS [l1]
3793+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional] ON [l1].[Id] = [l1.OneToMany_Optional].[OneToMany_Optional_InverseId]
3794+
ORDER BY [l1.OneToMany_Optional].[Id]",
3795+
//
3796+
@"SELECT [l1.OneToMany_Optional.OneToMany_Required].[Id], [l1.OneToMany_Optional.OneToMany_Required].[Level2_Optional_Id], [l1.OneToMany_Optional.OneToMany_Required].[Level2_Required_Id], [l1.OneToMany_Optional.OneToMany_Required].[Name], [l1.OneToMany_Optional.OneToMany_Required].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional.OneToMany_Required].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Required].[OneToMany_Required_InverseId], [l1.OneToMany_Optional.OneToMany_Required].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Required].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional.OneToMany_Required].[OneToOne_Optional_SelfId]
3797+
FROM [LevelThree] AS [l1.OneToMany_Optional.OneToMany_Required]
3798+
INNER JOIN (
3799+
SELECT DISTINCT [l1.OneToMany_Optional0].[Id]
3800+
FROM [LevelOne] AS [l10]
3801+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional0] ON [l10].[Id] = [l1.OneToMany_Optional0].[OneToMany_Optional_InverseId]
3802+
) AS [t] ON [l1.OneToMany_Optional.OneToMany_Required].[OneToMany_Required_InverseId] = [t].[Id]
3803+
ORDER BY [t].[Id]",
3804+
//
3805+
@"SELECT [l1.OneToMany_Optional.OneToMany_Optional].[Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Optional_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Required_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Name], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_SelfId], [t0].[Id]
3806+
FROM [LevelThree] AS [l1.OneToMany_Optional.OneToMany_Optional]
3807+
INNER JOIN (
3808+
SELECT [l1.OneToMany_Optional1].[Id]
3809+
FROM [LevelOne] AS [l11]
3810+
INNER JOIN [LevelTwo] AS [l1.OneToMany_Optional1] ON [l11].[Id] = [l1.OneToMany_Optional1].[OneToMany_Optional_InverseId]
3811+
) AS [t0] ON [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId] = [t0].[Id]
3812+
ORDER BY [t0].[Id]");
3813+
}
3814+
37443815
private void AssertSql(params string[] expected)
37453816
{
37463817
Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

0 commit comments

Comments
 (0)