diff --git a/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs index 36a108fe8a9..8a9aceb0583 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs @@ -1,5 +1,6 @@ using HotChocolate.Execution.Processing; using HotChocolate.Language; +using HotChocolate.Types; namespace HotChocolate.Data.Projections.Optimizers; @@ -38,6 +39,14 @@ public Selection RewriteSelection( } var field = context.TypeContext.Fields[fieldName]; + + // Only inject leaf fields. Forcing internal object projections can create + // invalid EF Core tracking shapes (e.g. owned entities without owner materialization). + if (field.Type.NamedType().IsCompositeType()) + { + continue; + } + var fieldNode = new FieldNode( null, new NameNode(fieldName), diff --git a/src/HotChocolate/Data/test/Data.Projections.SqlServer.Tests/Issue6329ReproTests.cs b/src/HotChocolate/Data/test/Data.Projections.SqlServer.Tests/Issue6329ReproTests.cs new file mode 100644 index 00000000000..262f183232d --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Projections.SqlServer.Tests/Issue6329ReproTests.cs @@ -0,0 +1,71 @@ +using HotChocolate.Execution; +using Microsoft.EntityFrameworkCore; + +namespace HotChocolate.Data.Projections; + +public class Issue6329ReproTests +{ + private readonly SchemaCache _cache = new(); + + private readonly BookWithProjectedAuthor[] _books = + [ + new() + { + Id = 1, + Title = "Book", + Author = new AuthorWithProjectedId + { + Id = 10, + Name = "Author" + } + } + ]; + + [Fact] + public async Task IsProjected_SubObject_Can_Be_Omitted_From_Selection() + { + var tester = _cache.CreateSchema( + _books, + onModelCreating: modelBuilder => + modelBuilder.Entity().OwnsOne(x => x.Author), + usePaging: true); + + var result = await tester.ExecuteAsync( + OperationRequestBuilder.New() + .SetDocument( + """ + { + root { + edges { + node { + id + } + } + } + } + """) + .Build()); + + var operationResult = result.ExpectOperationResult(); + Assert.Empty(operationResult.Errors ?? []); + } + + public class BookWithProjectedAuthor + { + public int Id { get; set; } + + public string Title { get; set; } = string.Empty; + + [IsProjected(true)] + public AuthorWithProjectedId Author { get; set; } = new(); + } + + [Owned] + public class AuthorWithProjectedId + { + [IsProjected(true)] + public int Id { get; set; } + + public string Name { get; set; } = string.Empty; + } +}