Skip to content

Fix #4787 — consume JasperFx 2.18.1 (DI-projection partial-class dispatch)#4794

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/4787-di-projection-via-jasperfx-2.18.1
Jun 28, 2026
Merged

Fix #4787 — consume JasperFx 2.18.1 (DI-projection partial-class dispatch)#4794
jeremydmiller merged 1 commit into
masterfrom
fix/4787-di-projection-via-jasperfx-2.18.1

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Consumes the JasperFx 2.18.1 source-generator fix from jasperfx#481 that lands the partial-class override emission path for DI-only-ctor projections.

What this closes

A partial SingleStreamProjection<T, TId> registered via AddProjectionWithServices with constructor-injected dependencies and conventional instance methods (Create / Apply / ShouldDelete) had its injected fields silently NULL inside every convention method on Marten 9.11+ / JasperFx.Events 2.14.x+. The reporter's _sender?.SendAsync(...) null-conditional masked the resulting NRE as silent zero events.

How JasperFx 2.18.1 fixes it

The 2.14.1 fallback in EvolverCodeEmitter closed CS7036 for DI-only-ctor projections at compile time but left every injected field NULL at runtime — the file-scoped Evolver built its private shadow projection via RuntimeHelpers.GetUninitializedObject and dispatched every Apply / Create / ShouldDelete through it, so the DI-resolved projection that AddProjectionWithServices supplied was never used for event dispatch.

2.18.1 emits a [GeneratedCode]-attributed override (Evolve / EvolveAsync / DetermineActionAsync — picked by HasShouldDelete / HasAnyAsync exactly like the existing file-scoped Evolver) directly into the user's partial class. Body uses bare convention-method calls so dispatch binds to this — the DI-built instance with all injected fields populated. The [assembly: GeneratedEvolver(...)] attribute and the file sealed class Evolver are NOT emitted for this case — the runtime's isOverridden(...) precedence picks up the partial-class override before tryUseAssemblyRegisteredEvolver ever runs.

Stateless / parameterless-ctor / aggregate-only / static-only projections keep the file-scoped Evolver verbatim — no perf or shape change for cases that don't need DI.

Changes

Test plan

  • 9/9 Bug_4787 cases green on net10 against published JasperFx 2.18.1.
  • CI matrix (PG 15/latest × net8/net10 × Newtonsoft/STJ).

Closes #4787

🤖 Generated with Claude Code

…atch)

The 2.14.1 GetUninitializedObject fallback in EvolverCodeEmitter
(#470) closed CS7036 for DI-only-ctor projections at compile time
but left every injected field NULL at runtime — the file-scoped Evolver
built its private shadow projection via RuntimeHelpers.GetUninitializedObject
and dispatched every Apply/Create/ShouldDelete through it, so the
DI-resolved projection that AddProjectionWithServices supplied was never
used for event dispatch. Reporter's null-conditional masked the resulting
NRE as silent zero events.

JasperFx 2.18.1 (#481) fixes this in the source generator: when a
projection has at least one instance-on-projection convention method AND
no public parameterless ctor AND is partial, the generator now emits a
[GeneratedCode]-attributed override (Evolve / EvolveAsync /
DetermineActionAsync — picked by HasShouldDelete / HasAnyAsync exactly like
the existing file-scoped Evolver) directly into the user's partial class.
Body uses bare convention-method calls so dispatch binds to `this` — the
DI-built instance with all injected fields populated. The
[assembly: GeneratedEvolver(...)] attribute and the file sealed class
Evolver are NOT emitted for this case — the runtime's isOverridden(...)
precedence picks up the partial-class override before
tryUseAssemblyRegisteredEvolver ever runs, so the shadow-instance code
path (and the GetUninitializedObject line that introduced the regression
in 2.14.1) goes away for DI projections entirely. Stateless /
parameterless-ctor / aggregate-only / static-only projections keep the
file-scoped Evolver verbatim — no perf or shape change for the cases that
don't need DI.

Marten side:
- Bump JasperFx + JasperFx.Events + both source generators 2.18.0 → 2.18.1.
- Promote Bug_4787_di_projection_with_instance_apply (originally on the
  repro branch repro/4787-di-projection-ctor-skipped) into the regression
  suite. 9 cases — Apply/Create/ShouldDelete × Singleton/Scoped/Transient —
  ALL FAILED with NRE through the shadow instance on 2.18.0, ALL PASS on
  2.18.1. The ContainerScopedProjectionTests project is the right home
  (it's the only suite that exercises AddProjectionWithServices end-to-end).

Closes #4787

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit a09be05 into master Jun 28, 2026
9 checks passed
@jeremydmiller
jeremydmiller deleted the fix/4787-di-projection-via-jasperfx-2.18.1 branch June 28, 2026 21:49
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.

Marten 9.11: certain Apply signatures silently stop working — EvolveAsync override required

1 participant