Skip to content

Commit 0d62111

Browse files
committed
Fix to #30565 - Query/Json: projecting entity collection along with json collection generates invalid shaper
When we project entity collection, the shaper uses result coordinator and generates different shaper. However we process this scenario as if "regular" code path was being executed. Basically, we would generate code for collection projection outside of `resultContext.Values == null` block (this needs to happen for regular collections due to result coordination). For JSON it's not needed, because entire object is loaded from a single row. So we can move the processing into the block and then just refer to it's product in the final projection. We also need to shuffle around the order in which we build the block. We should build expressions and json entities first, then populate resultContext and then build includes (which depend on values in the context). Before we were populating result values before we generated json entities, (which didn't matter because we were not using that info) but now is necessary. Fixes #30565
1 parent ae1bc0d commit 0d62111

File tree

3 files changed

+131
-2
lines changed

3 files changed

+131
-2
lines changed

src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,9 @@ public LambdaExpression ProcessShaper(
325325
var valueArrayInitializationExpression = Expression.Assign(
326326
_valuesArrayExpression, Expression.NewArrayInit(typeof(object), _valuesArrayInitializers));
327327

328+
_expressions.AddRange(_jsonEntityExpressions);
328329
_expressions.Add(valueArrayInitializationExpression);
329330
_expressions.AddRange(_includeExpressions);
330-
_expressions.AddRange(_jsonEntityExpressions);
331331

332332
if (_splitQuery)
333333
{
@@ -567,7 +567,14 @@ when collectionResultExpression.Navigation is INavigation navigation
567567

568568
var visitedShaperResult = Visit(shaperResult);
569569

570-
return visitedShaperResult;
570+
var jsonCollectionParameter = Expression.Parameter(collectionResultExpression.Type);
571+
572+
_variables.Add(jsonCollectionParameter);
573+
_jsonEntityExpressions.Add(Expression.Assign(jsonCollectionParameter, visitedShaperResult));
574+
575+
return CompensateForCollectionMaterialization(
576+
jsonCollectionParameter,
577+
collectionResultExpression.Type);
571578
}
572579

573580
case ProjectionBindingExpression projectionBindingExpression

test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,74 @@ public virtual Task Json_with_projection_of_multiple_json_references_and_entity_
14601460
AssertEqual(e.Reference4, a.Reference4);
14611461
});
14621462

1463+
[ConditionalTheory]
1464+
[MemberData(nameof(IsAsyncData))]
1465+
public virtual Task Json_with_projection_of_json_collection_leaf_and_entity_collection(bool async)
1466+
=> AssertQuery(
1467+
async,
1468+
ss => ss.Set<JsonEntityBasic>().Select(x => new { x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf, x.EntityCollection }).AsNoTracking(),
1469+
elementAsserter: (e, a) =>
1470+
{
1471+
AssertCollection(e.OwnedCollectionLeaf, a.OwnedCollectionLeaf, ordered: true);
1472+
AssertCollection(e.EntityCollection, a.EntityCollection);
1473+
});
1474+
1475+
[ConditionalTheory]
1476+
[MemberData(nameof(IsAsyncData))]
1477+
public virtual Task Json_with_projection_of_json_collection_and_entity_collection(bool async)
1478+
=> AssertQuery(
1479+
async,
1480+
ss => ss.Set<JsonEntityBasic>().Select(x => new { x.OwnedCollectionRoot, x.EntityCollection }).AsNoTracking(),
1481+
elementAsserter: (e, a) =>
1482+
{
1483+
AssertCollection(e.OwnedCollectionRoot, a.OwnedCollectionRoot, ordered: true);
1484+
AssertCollection(e.EntityCollection, a.EntityCollection);
1485+
});
1486+
1487+
[ConditionalTheory]
1488+
[MemberData(nameof(IsAsyncData))]
1489+
public virtual Task Json_with_projection_of_json_collection_element_and_entity_collection(bool async)
1490+
=> AssertQuery(
1491+
async,
1492+
ss => ss.Set<JsonEntityBasic>().Select(x => new { JsonCollectionElement = x.OwnedCollectionRoot[0], x.EntityReference, x.EntityCollection }).AsNoTracking(),
1493+
elementAsserter: (e, a) =>
1494+
{
1495+
AssertEqual(e.JsonCollectionElement, a.JsonCollectionElement);
1496+
AssertEqual(e.EntityReference, a.EntityReference);
1497+
AssertCollection(e.EntityCollection, a.EntityCollection);
1498+
});
1499+
1500+
[ConditionalTheory]
1501+
[MemberData(nameof(IsAsyncData))]
1502+
public virtual Task Json_with_projection_of_mix_of_json_collections_json_references_and_entity_collection(bool async)
1503+
=> AssertQuery(
1504+
async,
1505+
ss => ss.Set<JsonEntityBasic>().Select(x => new
1506+
{
1507+
Collection1 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf,
1508+
x.EntityReference,
1509+
Reference1 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf,
1510+
x.EntityCollection,
1511+
Reference2 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf[0],
1512+
Collection2 = x.OwnedReferenceRoot.OwnedCollectionBranch,
1513+
Collection3 = x.OwnedCollectionRoot,
1514+
Reference3 = x.OwnedCollectionRoot[0].OwnedReferenceBranch,
1515+
Collection4 = x.OwnedCollectionRoot[0].OwnedCollectionBranch
1516+
}).AsNoTracking(),
1517+
elementAsserter: (e, a) =>
1518+
{
1519+
AssertCollection(e.Collection1, a.Collection1, ordered: true);
1520+
AssertCollection(e.Collection2, a.Collection2, ordered: true);
1521+
AssertCollection(e.Collection3, a.Collection3, ordered: true);
1522+
AssertCollection(e.Collection4, a.Collection4, ordered: true);
1523+
AssertCollection(e.Collection1, a.Collection1, ordered: true);
1524+
AssertEqual(e.Reference1, a.Reference1);
1525+
AssertEqual(e.Reference2, a.Reference2);
1526+
AssertEqual(e.Reference3, a.Reference3);
1527+
AssertEqual(e.EntityReference, a.EntityReference);
1528+
AssertCollection(e.EntityCollection, a.EntityCollection);
1529+
});
1530+
14631531
[ConditionalTheory]
14641532
[MemberData(nameof(IsAsyncData))]
14651533
public virtual Task Json_all_types_entity_projection(bool async)

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,60 @@ ORDER BY [j].[Id]
13511351
""");
13521352
}
13531353

1354+
public override async Task Json_with_projection_of_json_collection_leaf_and_entity_collection(bool async)
1355+
{
1356+
await base.Json_with_projection_of_json_collection_leaf_and_entity_collection(async);
1357+
1358+
AssertSql(
1359+
"""
1360+
SELECT JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch.OwnedCollectionLeaf'), [j].[Id], [j0].[Id], [j0].[Name], [j0].[ParentId]
1361+
FROM [JsonEntitiesBasic] AS [j]
1362+
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j0] ON [j].[Id] = [j0].[ParentId]
1363+
ORDER BY [j].[Id]
1364+
""");
1365+
}
1366+
1367+
public override async Task Json_with_projection_of_json_collection_and_entity_collection(bool async)
1368+
{
1369+
await base.Json_with_projection_of_json_collection_and_entity_collection(async);
1370+
1371+
AssertSql(
1372+
"""
1373+
SELECT [j].[OwnedCollectionRoot], [j].[Id], [j0].[Id], [j0].[Name], [j0].[ParentId]
1374+
FROM [JsonEntitiesBasic] AS [j]
1375+
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j0] ON [j].[Id] = [j0].[ParentId]
1376+
ORDER BY [j].[Id]
1377+
""");
1378+
}
1379+
1380+
public override async Task Json_with_projection_of_json_collection_element_and_entity_collection(bool async)
1381+
{
1382+
await base.Json_with_projection_of_json_collection_element_and_entity_collection(async);
1383+
1384+
AssertSql(
1385+
"""
1386+
SELECT JSON_QUERY([j].[OwnedCollectionRoot], '$[0]'), [j].[Id], [j0].[Id], [j0].[Name], [j0].[ParentId], [j1].[Id], [j1].[Name], [j1].[ParentId]
1387+
FROM [JsonEntitiesBasic] AS [j]
1388+
LEFT JOIN [JsonEntitiesBasicForReference] AS [j0] ON [j].[Id] = [j0].[ParentId]
1389+
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j1] ON [j].[Id] = [j1].[ParentId]
1390+
ORDER BY [j].[Id], [j0].[Id]
1391+
""");
1392+
}
1393+
1394+
public override async Task Json_with_projection_of_mix_of_json_collections_json_references_and_entity_collection(bool async)
1395+
{
1396+
await base.Json_with_projection_of_mix_of_json_collections_json_references_and_entity_collection(async);
1397+
1398+
AssertSql(
1399+
"""
1400+
SELECT JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch.OwnedCollectionLeaf'), [j].[Id], [j0].[Id], [j0].[Name], [j0].[ParentId], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch.OwnedReferenceLeaf'), [j1].[Id], [j1].[Name], [j1].[ParentId], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedCollectionBranch'), [j].[OwnedCollectionRoot]
1401+
FROM [JsonEntitiesBasic] AS [j]
1402+
LEFT JOIN [JsonEntitiesBasicForReference] AS [j0] ON [j].[Id] = [j0].[ParentId]
1403+
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j1] ON [j].[Id] = [j1].[ParentId]
1404+
ORDER BY [j].[Id], [j0].[Id]
1405+
""");
1406+
}
1407+
13541408
public override async Task Json_all_types_entity_projection(bool async)
13551409
{
13561410
await base.Json_all_types_entity_projection(async);

0 commit comments

Comments
 (0)