diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 5bfeb92a3c6..dcd6e6d5b6a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -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 { @@ -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; @@ -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) diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index fad0d980f82..d4f08930fb3 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -1413,6 +1413,53 @@ public virtual Task Json_with_include_on_entity_collection_and_reference(bool as new ExpectedInclude(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().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().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().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) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index 0eca477540b..d5019533928 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -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);