Skip to content

Commit

Permalink
[release/7.0] Fix for #30266 and #30565 - projecting json entity/refe…
Browse files Browse the repository at this point in the history
…rence as well as regular entity collection (#30600)

Fixes #30266
Fixes #30565
  • Loading branch information
maumar authored Apr 5, 2023
1 parent ec314d5 commit e1aacd8
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit
private static readonly bool UseOldBehavior30028
= AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue30028", out var enabled30028) && enabled30028;

private static readonly bool UseOldBehavior30266
= AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue30266", out var enabled30266) && enabled30266;

private static readonly bool UseOldBehavior30565
= AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue30565", out var enabled30565) && enabled30565;

// Reading database values
private static readonly MethodInfo IsDbNullMethod =
typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.IsDBNull), new[] { typeof(int) })!;
Expand Down Expand Up @@ -444,7 +450,17 @@ protected override Expression VisitExtension(Expression extensionExpression)
var visitedShaperResultParameter = Expression.Parameter(visitedShaperResult.Type);
_variables.Add(visitedShaperResultParameter);
_expressions.Add(Expression.Assign(visitedShaperResultParameter, visitedShaperResult));
accessor = visitedShaperResultParameter;

if (!UseOldBehavior30266)
{
accessor = CompensateForCollectionMaterialization(
visitedShaperResultParameter,
entityShaperExpression.Type);
}
else
{
accessor = visitedShaperResultParameter;
}
}
else
{
Expand All @@ -469,18 +485,27 @@ protected override Expression VisitExtension(Expression extensionExpression)

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

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

Expand Down Expand Up @@ -535,7 +560,21 @@ when collectionResultExpression.Navigation is INavigation navigation

var visitedShaperResult = Visit(shaperResult);

return visitedShaperResult;
if (!UseOldBehavior30565)
{
var jsonCollectionParameter = Expression.Parameter(collectionResultExpression.Type);

_variables.Add(jsonCollectionParameter);
_expressions.Add(Expression.Assign(jsonCollectionParameter, visitedShaperResult));

return CompensateForCollectionMaterialization(
jsonCollectionParameter,
collectionResultExpression.Type);
}
else
{
return visitedShaperResult;
}
}

case ProjectionBindingExpression projectionBindingExpression
Expand Down Expand Up @@ -1019,6 +1058,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
115 changes: 115 additions & 0 deletions test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,121 @@ 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_with_projection_of_json_collection_leaf_and_entity_collection(bool async)
=> AssertQuery(
async,
ss => ss.Set<JsonEntityBasic>().Select(x => new { x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf, x.EntityCollection }).AsNoTracking(),
elementAsserter: (e, a) =>
{
AssertCollection(e.OwnedCollectionLeaf, a.OwnedCollectionLeaf, ordered: true);
AssertCollection(e.EntityCollection, a.EntityCollection);
});

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

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

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Json_with_projection_of_mix_of_json_collections_json_references_and_entity_collection(bool async)
=> AssertQuery(
async,
ss => ss.Set<JsonEntityBasic>().Select(x => new
{
Collection1 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf,
x.EntityReference,
Reference1 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf,
x.EntityCollection,
Reference2 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf[0],
Collection2 = x.OwnedReferenceRoot.OwnedCollectionBranch,
Collection3 = x.OwnedCollectionRoot,
Reference3 = x.OwnedCollectionRoot[0].OwnedReferenceBranch,
Collection4 = x.OwnedCollectionRoot[0].OwnedCollectionBranch
}).AsNoTracking(),
elementAsserter: (e, a) =>
{
AssertCollection(e.Collection1, a.Collection1, ordered: true);
AssertCollection(e.Collection2, a.Collection2, ordered: true);
AssertCollection(e.Collection3, a.Collection3, ordered: true);
AssertCollection(e.Collection4, a.Collection4, ordered: true);
AssertCollection(e.Collection1, a.Collection1, ordered: true);
AssertEqual(e.Reference1, a.Reference1);
AssertEqual(e.Reference2, a.Reference2);
AssertEqual(e.Reference3, a.Reference3);
AssertEqual(e.EntityReference, a.EntityReference);
AssertCollection(e.EntityCollection, a.EntityCollection);
});

[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 @@ -819,6 +819,100 @@ 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 JSON_QUERY([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 JSON_QUERY([j].[OwnedReferenceRoot],'$'), [j].[Id], JSON_QUERY([j].[OwnedCollectionRoot],'$'), [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_collection_leaf_and_entity_collection(bool async)
{
await base.Json_with_projection_of_json_collection_leaf_and_entity_collection(async);

AssertSql(
"""
SELECT JSON_QUERY([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedCollectionLeaf'), [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_collection_and_entity_collection(bool async)
{
await base.Json_with_projection_of_json_collection_and_entity_collection(async);

AssertSql(
"""
SELECT JSON_QUERY([j].[OwnedCollectionRoot],'$'), [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_collection_element_and_entity_collection(bool async)
{
await base.Json_with_projection_of_json_collection_element_and_entity_collection(async);

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

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

AssertSql(
"""
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'), JSON_QUERY([j].[OwnedCollectionRoot],'$')
FROM [JsonEntitiesBasic] AS [j]
LEFT JOIN [JsonEntitiesBasicForReference] AS [j0] ON [j].[Id] = [j0].[ParentId]
LEFT JOIN [JsonEntitiesBasicForCollection] AS [j1] ON [j].[Id] = [j1].[ParentId]
ORDER BY [j].[Id], [j0].[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 e1aacd8

Please sign in to comment.