Skip to content

Commit

Permalink
Fix to dotnet#30266 - Linq select cannot project an entity containing…
Browse files Browse the repository at this point in the history
… both Json columns and ICollections (dotnet#30566)

Problem was that when we projection collection of entities we use result coordinator and generate different pattern in shaper code. Json entity shaper code is generated inside `resultContext.Values == null` block and all the products should be referred to via resultContext.Values array access.
We were not doing that for json, so in the final projection code we would refer to parameters (storing the actual json entities) that were out of scope.

Fix is to use the correct result coordinator pattern, just like we do for non-json entities.

Fixes dotnet#30266
  • Loading branch information
maumar authored Mar 24, 2023
1 parent 8da7a4d commit ae1bc0d
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,10 @@ protected override Expression VisitExtension(Expression extensionExpression)
var visitedShaperResultParameter = Expression.Parameter(visitedShaperResult.Type);
_variables.Add(visitedShaperResultParameter);
_jsonEntityExpressions.Add(Expression.Assign(visitedShaperResultParameter, visitedShaperResult));
accessor = visitedShaperResultParameter;

accessor = CompensateForCollectionMaterialization(
visitedShaperResultParameter,
entityShaperExpression.Type);
}
else
{
Expand All @@ -509,19 +512,9 @@ protected override Expression VisitExtension(Expression extensionExpression)

_expressions.Add(Expression.Assign(entityParameter, entityMaterializationExpression));

if (_containsCollectionMaterialization)
{
_valuesArrayInitializers!.Add(entityParameter);
accessor = Expression.Convert(
Expression.ArrayIndex(
_valuesArrayExpression!,
Expression.Constant(_valuesArrayInitializers.Count - 1)),
entityShaperExpression.Type);
}
else
{
accessor = entityParameter;
}
accessor = CompensateForCollectionMaterialization(
entityParameter,
entityShaperExpression.Type);
}

_variableShaperMapping[entityShaperExpression.ValueBufferExpression] = accessor;
Expand Down Expand Up @@ -1056,6 +1049,23 @@ when collectionResultExpression.Navigation is INavigation navigation
}

return base.VisitExtension(extensionExpression);

Expression CompensateForCollectionMaterialization(ParameterExpression parameter, Type resultType)
{
if (_containsCollectionMaterialization)
{
_valuesArrayInitializers!.Add(parameter);
return Expression.Convert(
Expression.ArrayIndex(
_valuesArrayExpression!,
Expression.Constant(_valuesArrayInitializers.Count - 1)),
resultType);
}
else
{
return parameter;
}
}
}

protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,53 @@ public virtual Task Json_with_include_on_entity_collection_and_reference(bool as
new ExpectedInclude<JsonEntityBasic>(x => x.EntityCollection)),
entryCount: 44);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Json_with_projection_of_json_reference_leaf_and_entity_collection(bool async)
=> AssertQuery(
async,
ss => ss.Set<JsonEntityBasic>().Select(x => new { x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf, x.EntityCollection }).AsNoTracking(),
elementAsserter: (e, a) =>
{
AssertEqual(e.OwnedReferenceLeaf, a.OwnedReferenceLeaf);
AssertCollection(e.EntityCollection, a.EntityCollection);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Json_with_projection_of_json_reference_and_entity_collection(bool async)
=> AssertQuery(
async,
ss => ss.Set<JsonEntityBasic>().Select(x => new { x.OwnedReferenceRoot, x.EntityCollection }).AsNoTracking(),
elementAsserter: (e, a) =>
{
AssertEqual(e.OwnedReferenceRoot, a.OwnedReferenceRoot);
AssertCollection(e.EntityCollection, a.EntityCollection);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Json_with_projection_of_multiple_json_references_and_entity_collection(bool async)
=> AssertQuery(
async,
ss => ss.Set<JsonEntityBasic>().Select(x => new
{
Reference1 = x.OwnedReferenceRoot,
Reference2 = x.OwnedCollectionRoot[0].OwnedReferenceBranch,
x.EntityCollection,
Reference3 = x.OwnedCollectionRoot[1].OwnedReferenceBranch.OwnedReferenceLeaf,
Reference4 = x.OwnedCollectionRoot[0].OwnedCollectionBranch[0].OwnedReferenceLeaf,

}).AsNoTracking(),
elementAsserter: (e, a) =>
{
AssertCollection(e.EntityCollection, a.EntityCollection);
AssertEqual(e.Reference1, a.Reference1);
AssertEqual(e.Reference2, a.Reference2);
AssertEqual(e.Reference3, a.Reference3);
AssertEqual(e.Reference4, a.Reference4);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Json_all_types_entity_projection(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,45 @@ FROM [JsonEntitiesBasic] AS [j]
""");
}

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

AssertSql(
"""
SELECT JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch.OwnedReferenceLeaf'), [j].[Id], [j0].[Id], [j0].[Name], [j0].[ParentId]
FROM [JsonEntitiesBasic] AS [j]
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j0] ON [j].[Id] = [j0].[ParentId]
ORDER BY [j].[Id]
""");
}

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

AssertSql(
"""
SELECT [j].[OwnedReferenceRoot], [j].[Id], [j0].[Id], [j0].[Name], [j0].[ParentId]
FROM [JsonEntitiesBasic] AS [j]
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j0] ON [j].[Id] = [j0].[ParentId]
ORDER BY [j].[Id]
""");
}

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

AssertSql(
"""
SELECT [j].[OwnedReferenceRoot], [j].[Id], JSON_QUERY([j].[OwnedCollectionRoot], '$[0].OwnedReferenceBranch'), [j0].[Id], [j0].[Name], [j0].[ParentId], JSON_QUERY([j].[OwnedCollectionRoot], '$[1].OwnedReferenceBranch.OwnedReferenceLeaf'), JSON_QUERY([j].[OwnedCollectionRoot], '$[0].OwnedCollectionBranch[0].OwnedReferenceLeaf')
FROM [JsonEntitiesBasic] AS [j]
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j0] ON [j].[Id] = [j0].[ParentId]
ORDER BY [j].[Id]
""");
}

public override async Task Json_all_types_entity_projection(bool async)
{
await base.Json_all_types_entity_projection(async);
Expand Down

0 comments on commit ae1bc0d

Please sign in to comment.