From f7c5e6b77fa42e46a38fd5e15e98781960119196 Mon Sep 17 00:00:00 2001 From: divyeshio <79130336+divyeshio@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:06:05 +0530 Subject: [PATCH] Fix projecting JSON complex collection together with collection navigation - Return index-based ProjectionBindingExpression for JSON complex collections when in index-based binding mode in RelationalProjectionBindingExpressionVisitor - Add ComplexType regression test projecting a JSON complex collection together with a collection navigation (Sqlite + SqlServer baselines) Fixes #38928 --- ...ionalProjectionBindingExpressionVisitor.cs | 9 +-- .../ComplexTypeQueryRelationalTestBase.cs | 69 +++++++++++++++++++ .../Query/ComplexTypeQuerySqlServerTest.cs | 27 ++++++++ .../Query/ComplexTypeQuerySqliteTest.cs | 29 ++++++++ 4 files changed, 130 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 0d446100372..db4fb1f7e60 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -550,11 +550,12 @@ protected override Expression VisitExtension(Expression extensionExpression) if (_indexBasedBinding) { _clientProjections!.Add(jsonQuery); + + return collectionResult.Update( + new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, collectionResult.Type)); } - else - { - _projectionMapping[_projectionMembers.Peek()] = jsonQuery; - } + + _projectionMapping[_projectionMembers.Peek()] = jsonQuery; return collectionResult.Update( new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), collectionResult.Type)); diff --git a/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs index 58f9817f327..9be297798da 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs @@ -296,6 +296,75 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) #endregion 38077 + #region 38928 + + [Fact] + public virtual async Task Project_json_complex_collection_together_with_collection_navigation() + { + var contextFactory = await InitializeNonSharedTest( + seed: async context => + { + context.Set().Add( + new Context38928.Parent + { + Items = [new Context38928.ChildItem { Value = "/x" }], + Links = [new Context38928.Link()] + }); + + await context.SaveChangesAsync(); + }); + + await using var context = contextFactory.CreateDbContext(); + + var result = await context.Set() + .AsSplitQuery() + .Select(p => new + { + p.Id, + p.Items, + Links = p.Links.Select(l => new { l.Id }).ToList() + }) + .SingleAsync(); + + Assert.NotEqual(0, result.Id); + Assert.NotNull(result.Items); + var item = Assert.Single(result.Items); + Assert.Equal("/x", item.Value); + Assert.Single(result.Links); + } + + private class Context38928(DbContextOptions options) : DbContext(options) + { + public class Parent + { + public int Id { get; set; } + public List? Items { get; set; } + public List Links { get; set; } = []; + } + + public class ChildItem + { + public required string Value { get; init; } + } + + public class Link + { + public int Id { get; set; } + public List Parents { get; set; } = []; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.ComplexCollection(p => p.Items).ToJson(); + b.HasMany(p => p.Links).WithMany(l => l.Parents).UsingEntity("ParentLinks"); + }); + } + } + + #endregion 38928 + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs index d3735ebd0ae..68661059aa4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs @@ -1624,6 +1624,33 @@ FROM [Child] AS [c] """); } + public override async Task Project_json_complex_collection_together_with_collection_navigation() + { + await base.Project_json_complex_collection_together_with_collection_navigation(); + + AssertSql( + """ + SELECT TOP(2) [p].[Id], [p].[Items] + FROM [Parent] AS [p] + ORDER BY [p].[Id] + """, + // + """ + SELECT [s].[Id], [p3].[Id] + FROM ( + SELECT TOP(1) [p].[Id] + FROM [Parent] AS [p] + ORDER BY [p].[Id] + ) AS [p3] + INNER JOIN ( + SELECT [l1].[Id], [p2].[ParentsId] + FROM [ParentLinks] AS [p2] + INNER JOIN [Link] AS [l1] ON [p2].[LinksId] = [l1].[Id] + ) AS [s] ON [p3].[Id] = [s].[ParentsId] + ORDER BY [p3].[Id] + """); + } + #endregion Non-shared test resources [Fact] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs index 110a247bd80..3f7cc7cf43e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs @@ -1454,6 +1454,35 @@ public override async Task Complex_property_on_split_entity() """); } + public override async Task Project_json_complex_collection_together_with_collection_navigation() + { + await base.Project_json_complex_collection_together_with_collection_navigation(); + + AssertSql( + """ + SELECT "p"."Id", "p"."Items" + FROM "Parent" AS "p" + ORDER BY "p"."Id" + LIMIT 2 + """, + // + """ + SELECT "s"."Id", "p3"."Id" + FROM ( + SELECT "p"."Id" + FROM "Parent" AS "p" + ORDER BY "p"."Id" + LIMIT 1 + ) AS "p3" + INNER JOIN ( + SELECT "l1"."Id", "p2"."ParentsId" + FROM "ParentLinks" AS "p2" + INNER JOIN "Link" AS "l1" ON "p2"."LinksId" = "l1"."Id" + ) AS "s" ON "p3"."Id" = "s"."ParentsId" + ORDER BY "p3"."Id" + """); + } + #endregion Non-shared test resources [Fact]