diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 68a703e8559..97557d659e5 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -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) })!; @@ -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 { @@ -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; + } } } @@ -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 @@ -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) diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index 1be1f284019..76572295e9f 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -763,6 +763,121 @@ 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_with_projection_of_json_collection_leaf_and_entity_collection(bool async) + => AssertQuery( + async, + ss => ss.Set().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().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().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().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) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index e2610de0ca7..0e6af7ab846 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -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);