Skip to content

fix(generator): register published types via a PublishedTypes() override (marten#5192) - #637

Merged
jeremydmiller merged 2 commits into
mainfrom
fix/5192-published-types-override
Aug 6, 2026
Merged

fix(generator): register published types via a PublishedTypes() override (marten#5192)#637
jeremydmiller merged 2 commits into
mainfrom
fix/5192-published-types-override

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Fixes the source generator break reported in marten#5192.

The bug

An EventProjection's discovered published document types were registered by emitting a parameterless constructor into the user's partial class:

partial class MyProjection
{
    public MyProjection() { RegisterPublishedType(typeof(Thing)); }
}

A constructor is the wrong extension point, and it failed two ways.

1. It broke the build (the reported symptom). On a type that declares a primary constructor — partial class MyProjection(ILogger<MyProjection> logger) : EventProjection — C# requires every other constructor to chain through the primary one, so the generated file failed the whole compilation with CS8862.

2. It silently did nothing, which is worse. A projection that takes dependencies has to be registered through Marten's AddProjectionWithServices, so the container calls the dependency-taking constructor and the generated parameterless one never ran. Published types went unregistered, and #626's teardown registration — which reads PublishedTypes() — had nothing to register. Verified against Marten 9.22.4:

new MyProjection(logger).PublishedTypes()  ->  []

The fix

Emit an override of the virtual ProjectionBase.PublishedTypes() instead:

public override IEnumerable<Type> PublishedTypes()
{
    var publishedTypes = new List<Type>(base.PublishedTypes());
    if (!publishedTypes.Contains(typeof(Thing))) publishedTypes.Add(typeof(Thing));
    return publishedTypes;
}

An override does not care how the instance was constructed, and chaining through base.PublishedTypes() keeps hand-written RegisterPublishedType calls and Options.StorageTypes flowing. The generator yields when the author already wrote their own override. Both emission sites are covered: EmitEventProjectionTypeRegistrationPartial (an ApplyAsync override) and the conventional-method path.

The emitted code deliberately avoids LINQ — CompileWithGenerator in the test harness does not reference System.Linq, and generated code should not assume a consumer's reference set either.

Regression window

Neither defect was reachable before 2.38.0. Discovery used to be syntactic, so only an explicit Store<Doc>(x) produced a registration and the far more common Store(doc) produced none at all. #611 made discovery semantic and both spellings started emitting the constructor. Bisected on the consumer side: Marten 9.22.2 builds, Marten 9.22.3 does not.

Behavior change worth a second look

The old guard skipped registration entirely when the class already had an explicit parameterless constructor — it existed only because you cannot add a second one. An override has no such conflict, so those projections now get their published types registered too. That is the intended #4166 behavior, but it means an upgrade can newly provision document storage, and newly register teardown targets under #626, for a projection that was quietly getting neither.

Verification

  • SG tests 34/34, including four new ones: primary-constructor projection compiles clean, container-built projection actually registers, generator defers to a hand-written override, and the conventional-method emission site.
  • EventTests 730/730 on net9.0 and net10.0; full solution builds.
  • End-to-end through the real delivery path: packed this generator locally, packed a Marten that bundles it, and pointed the reproduction at that package. All three previously-broken shapes compile and report PublishedTypes: [Thing]. Full Marten.slnx builds and Marten's EventSourcingTests runs 1627 passed / 0 failed / 7 skipped.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VpDCvJcBDZerieJB4JEHde

jeremydmiller and others added 2 commits August 6, 2026 04:17
…ide (marten#5192)

An EventProjection's discovered published document types were registered by emitting
`public MyProjection() { RegisterPublishedType(...); }` into the user's partial class.
A constructor is the wrong extension point, and it failed two ways.

It is illegal on a type that declares a primary constructor --
`partial class MyProjection(ILogger logger) : EventProjection` -- because C# requires
every other constructor to chain through the primary one, so the generated file broke
the build outright with CS8862. That is what marten#5192 reported.

Worse, and silent: a projection that takes dependencies has to be registered through
Marten's AddProjectionWithServices, so the container calls the dependency-taking
constructor and the generated parameterless one never ran. Published types went
unregistered, which also left #626's teardown registration -- it reads
PublishedTypes() -- with nothing to register.

Emit an override of the virtual ProjectionBase.PublishedTypes() instead. It does not
care how the instance was constructed, and chaining through base.PublishedTypes()
keeps hand-written RegisterPublishedType calls and Options.StorageTypes flowing. The
generator yields when the author already wrote their own override.

Both emission sites are covered: EmitEventProjectionTypeRegistrationPartial (an
ApplyAsync override) and the conventional-method path.

Neither defect was reachable before 2.38.0: discovery was syntactic, so only an
explicit `Store<Doc>(x)` produced a registration and the far more common `Store(doc)`
produced none. #611 made discovery semantic and both spellings started
emitting the constructor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpDCvJcBDZerieJB4JEHde
@jeremydmiller
jeremydmiller merged commit 97d88fd into main Aug 6, 2026
1 check passed
@jeremydmiller
jeremydmiller deleted the fix/5192-published-types-override branch August 6, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant