Skip to content

Explicit transactional DbContext selection for multi-DbContext handlers (supersedes #3284)#3295

Merged
jeremydmiller merged 1 commit into
mainfrom
feat/transactional-dbcontext-selection-explicit
Jul 5, 2026
Merged

Explicit transactional DbContext selection for multi-DbContext handlers (supersedes #3284)#3295
jeremydmiller merged 1 commit into
mainfrom
feat/transactional-dbcontext-selection-explicit

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Supersedes #3284 (by @KhaledZaabat, preserved as a co-author). Same core need — let a handler that depends on more than one DbContext say which one is transactional — but reworked to be explicit only, no magic, per review.

Problem

A handler / HTTP endpoint / gRPC endpoint can legitimately depend on two DbContext-shaped services: one it writes through (must be enrolled in the transaction + outbox) and one it only reads from. EFCorePersistenceFrameProvider used to throw the moment it saw a second one, with no way to disambiguate.

Solution — explicit designation, no inference

Two equivalent, EF-Core-agnostic markers designate the transactional context:

  • [Transactional(typeof(MyDbContext))] — carried as a chain tag. The attribute lives in the core Wolverine assembly and only stores a System.Type, so neither it nor your handler assembly references EF Core.
  • [Storage(typeof(MyDbContext))] — the provider-agnostic storage attribute (the same one used for Marten/Polecat ancillary stores), now taught to designate an EF Core DbContext via a new EFCoreDbContextStorageFrameProvider (IAncillaryStoreFrameProvider). It's read directly off the handler so it's honored even under AutoApplyTransactions, whose policy runs before AncillaryStoreType is populated.

Either may name a concrete DbContext or a registered abstraction (WithDbContextAbstraction<TAbstraction, TDbContext>()), so Clean Architecture handlers never touch the concrete EF type.

When a chain has >1 DbContext and no designation, Wolverine fails fast at startup:

Cannot determine the DbContext type for <handler>, multiple DbContext types detected: … Wolverine will not guess which one owns the transaction. Either remove the automatic transactional middleware from this handler (e.g. with [NonTransactional] or by not calling AutoApplyTransactions), or explicitly designate the transactional DbContext with [Transactional(typeof(YourDbContext))] or [Storage(typeof(YourDbContext))] on the handler.

A designation naming a type the chain doesn't actually depend on also fails loudly.

What changed vs #3284

  • Kept the [Transactional(typeof(X))] tag mechanism and its tests (transactional_dbcontext_selection_tests.cs).
  • Dropped the registration-side WithTransactionalDbContext<T>() selection and the automatic "single Wolverine-enabled candidate" inference — that's the "magic" the review asked to remove.
  • Added [Storage(typeof(X))] support + the ambiguity-validation test (storage_dbcontext_selection_tests.cs).
  • Docs: new "Selecting the Transactional DbContext" section (applies uniformly to message/HTTP/gRPC) + a cross-link from Auto-Apply.

Verification (local, net9.0, Postgres + SQL Server)

  • transactional_dbcontext_selection_tests (3) + storage_dbcontext_selection_tests (3): 6/6.
  • transaction_middleware_mode_tests: 8/8.
  • Full EfCoreTests: 183/186; the 3 stragglers are pre-existing environmental flakiness (verified: one passes in isolation, one fails on clean main, one flakes) — none touch DbContext selection.
  • Release builds of Wolverine + Wolverine.EntityFrameworkCore clean.

Closing #3284 in favor of this. Full credit to @KhaledZaabat.

🤖 Generated with Claude Code

…Context handlers

A handler/HTTP/gRPC chain can legitimately depend on more than one DbContext — one
written through, one read-only. Previously EFCorePersistenceFrameProvider threw the
moment it saw a second DbContext-shaped dependency with no way to disambiguate.

Now the transactional DbContext can be designated explicitly — no "magic", no
automatic/registration-side inference:

  * [Transactional(typeof(MyDbContext))] — carried as a chain tag; the attribute lives
    in core Wolverine and only stores a System.Type, so it never references EF Core.
  * [Storage(typeof(MyDbContext))] — the provider-agnostic storage attribute, now taught
    to designate an EF Core DbContext via a new EFCoreDbContextStorageFrameProvider
    (IAncillaryStoreFrameProvider). Read directly off the handler so it is honored even
    under AutoApplyTransactions, whose policy runs before AncillaryStoreType is populated.
  * Either may name a concrete DbContext or a registered DbContext abstraction
    (WithDbContextAbstraction<TAbstraction, TDbContext>()).

When a chain has more than one DbContext dependency and no designation, Wolverine fails
fast at startup with a message telling the user to remove the automatic transactional
middleware or designate the context with [Transactional]/[Storage]. A designation that
names a non-dependency also fails loudly.

Supersedes #3284: keeps that PR's [Transactional]-attribute tests and drops its
registration-side auto-selection (WithTransactionalDbContext / Wolverine-enabled
inference) per the "explicit only" design. Adds [Storage] + ambiguity-validation tests
and docs.

Co-authored-by: KhaledZaabat <khaled.zaabat@ensia.edu.dz>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit f549035 into main Jul 5, 2026
26 checks passed
This was referenced Jul 9, 2026
This was referenced Jul 13, 2026
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