Skip to content

Fix SelectionExpressionBuilder failing on types with only private constructors - #9666

Closed
PHILLIPS71 wants to merge 3 commits into
ChilliCream:mainfrom
PHILLIPS71:jp/selection-expression-builder-private-ctor
Closed

PHILLIPS71 wants to merge 3 commits into
ChilliCream:mainfrom
PHILLIPS71:jp/selection-expression-builder-private-ctor

Conversation

@PHILLIPS71

@PHILLIPS71 PHILLIPS71 commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

SelectionExpressionBuilder.ShouldReuseExistingInstance has a condition that checks for a public parameterless constructor via Type.GetConstructor(Type.EmptyTypes). This returns null for types that only expose a private parameterless constructor, which is a common pattern in DDD aggregates that use a private ORM constructor alongside a business constructor that requires parameters.

When the check fails, the builder falls through to the member-init and constructor-matching paths, both of which require public constructors or public writable properties, so it throws: InvalidOperationException: No writable properties or suitable constructor found for type

EF Core supports this pattern natively (private parameterless ctor for materialisation, private setters for properties), so HotChocolate should not attempt to construct the type itself.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates HotChocolate’s projection selector generation to avoid attempting to construct result types that EF Core can materialize (notably DDD-style aggregates with non-public constructors), and adds regression tests around Selection.AsSelector<T>() behavior.

Changes:

  • Adjust SelectionExpressionBuilder.ShouldReuseExistingInstance to treat types with private parameterless constructors as reusable (so the selector can fall back to identity).
  • Add an EF integration test to ensure selection.AsSelector<T>() doesn’t throw for an aggregate with only private constructors.
  • Add a unit-style test asserting POCOs with a public parameterless ctor produce a MemberInitExpression.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs Adds regression coverage for DDD aggregate/private ctor materialization and asserts MemberInitExpression for a POCO selector.
src/HotChocolate/Core/src/Execution.Projections/SelectionExpressionBuilder.cs Broadens the “reuse existing instance” heuristic to include non-public parameterless ctors.
Comments suppressed due to low confidence (1)

src/HotChocolate/Core/src/Execution.Projections/SelectionExpressionBuilder.cs:638

  • ShouldReuseExistingInstance still requires a non-public ctor with parameters. That means a common EF/DDD pattern of private parameterless ctor for materialization + public business ctor with parameters will still not hit the reuse path and can still throw No writable properties or suitable constructor found when properties have private setters and the public ctor doesn’t match the projected members. Consider broadening the second constructor check (e.g., consider public parameterized ctors too) or moving the reuse fallback to the point where member-init/ctor-matching has already failed, so these types don’t throw during selector construction.
    private static bool ShouldReuseExistingInstance(Type type)
        => type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
               .Any(c => c.GetParameters().Length == 0)
            && type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)
                .Any(t =>
                    t.GetParameters().Length > 0
                    && !IsRecordCopyConstructor(t, type));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/HotChocolate/Core/src/Execution.Projections/SelectionExpressionBuilder.cs Outdated
Comment thread src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs Outdated
# Conflicts:
#	src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs
@PHILLIPS71
PHILLIPS71 deleted the jp/selection-expression-builder-private-ctor branch June 29, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants