Skip to content

Follow-up to #2919: docs + extended scenario tests for DbContext abstractions#2954

Merged
jeremydmiller merged 1 commit into
mainfrom
feat-2919-followup-docs-tests
May 28, 2026
Merged

Follow-up to #2919: docs + extended scenario tests for DbContext abstractions#2954
jeremydmiller merged 1 commit into
mainfrom
feat-2919-followup-docs-tests

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Reviewer-requested follow-up to #2919 (DbContext abstractions support via WithDbContextAbstraction<TAbstraction, TDbContext>()).

Three new test scenarios

A new file at src/Persistence/EfCoreTests/dbContext_abstraction_scenarios.cs covers contracts that the merged PR's tests don't reach:

  1. multiple_dbcontext_types_one_abstracted_one_direct — one Wolverine host with two DbContexts: OrdersDbContext registered through an IOrderRepository abstraction and CustomersDbContext used directly. Both handlers transact correctly through the EF Core middleware in parallel.

  2. multiple_abstractions_for_same_dbcontext_each_used_independentlyStoreDbContext implements both IItemRepository and IOrderInsightRepository. Two separate handlers, each depending on a different abstraction, both successfully commit through the same physical DbContext.

  3. handler_using_two_abstractions_to_same_dbcontext_uses_one_scoped_instance — the headline scenario. The handler takes BOTH IItemRepository and IOrderInsightRepository, casts both back to StoreDbContext, and asserts ReferenceEquals(itemsCtx, ordersCtx). That's the contract the merged CastDbContextFrame depends on: at runtime, both parameters resolve to the same scoped DbContext, just viewed through different interfaces, so a single SaveChangesAsync commits all writes atomically.

A note about #3: the contract only holds when the abstractions forward to the same scoped DbContext in DI via a factory:

services.AddScoped<IItemRepository>(sp => sp.GetRequiredService<StoreDbContext>());
services.AddScoped<IOrderInsightRepository>(sp => sp.GetRequiredService<StoreDbContext>());

services.AddScoped<IItemRepository, StoreDbContext>() (without the factory) would create a separate DbContext instance per registered interface, and the cast frame would land on whichever the codegen picked first while the handler's other reference points at a different one. The new docs section calls this out explicitly.

Docs

New DbContext Abstractions section at the bottom of docs/guide/durability/efcore/transactional-middleware.md. Five #region sample_* blocks in the test file, expanded by mdsnippets so every code block in the doc lives in a passing test:

  • sample_register_dbcontext_abstraction — the bare single-abstraction registration shape
  • sample_handler_using_dbcontext_abstraction — handler depending on the abstraction
  • sample_register_multiple_dbcontext_abstractions — two abstractions, factory-forwarded to the same scoped DbContext
  • sample_handler_using_multiple_abstractions — handler taking both abstractions; ref-equality contract
  • sample_register_mixed_dbcontexts — multi-DbContext, mixed abstraction registration

The rest of the doc diffs in this PR are mdsnippets regenerations of unrelated snippets it touched while running over the whole repo.

Schema setup detail (mirrors the GH-2618 workaround)

EF Core's EnsureCreatedAsync is a no-op when the database already exists — and the shared Wolverine integration-tests Postgres always does. So each scenario drops + creates its test schemas and then issues raw CREATE TABLE DDL for the user-defined entity tables, the same pattern Bug_DurableLocalQueue_ancillary_store_routing uses for the same reason.

Verification

  • New scenario suite: 3/3 pass against Postgres on port 5433.
  • Full dotnet build wolverine.slnx -c Release clean (0 warnings, 0 errors).
  • mdsnippets expansion of the new doc anchors succeeded and is committed.

🤖 Generated with Claude Code

…ractions

Reviewer-requested follow-up to PR #2919 (DbContext abstractions support
via ).

Adds three new scenarios in
src/Persistence/EfCoreTests/dbContext_abstraction_scenarios.cs:

  1. multi-DbContext, mixed abstraction - one DbContext registered through
     an abstraction (IOrderRepository -> OrdersDbContext) and a second
     DbContext used directly (CustomersDbContext). One Wolverine host;
     both handlers transact through the EF Core middleware in parallel.

  2. multiple abstractions for the SAME DbContext - StoreDbContext
     implements IItemRepository AND IOrderInsightRepository. Two separate
     handlers, each depending on a different abstraction, both
     successfully commit through the same physical DbContext.

  3. multiple abstractions used IN THE SAME handler - the test handler
     takes BOTH IItemRepository and IOrderInsightRepository. The assertion
     proves that at runtime both parameters resolve to the *same scoped*
     StoreDbContext via ReferenceEquals on the casts. That's the contract
     the cast frame the merged PR emits relies on: one DbContext in
     scope, viewed through different interfaces. The forwarding-factory
     DI shape (services.AddScoped<TAbs>(sp => sp.GetRequiredService<TDb>()))
     is what makes this work; AddScoped<TAbs, TImpl>() would create
     separate DbContext instances per registered interface and the cast
     would land on a different one than the handler's reference.

Schemas are dropped + recreated with raw DDL before each scenario starts
(mirroring the workaround documented in
Bug_DurableLocalQueue_ancillary_store_routing for GH-2618 - EF Core's
EnsureCreatedAsync is a no-op when the database already exists, so the
user-defined tables never get materialised on the shared Wolverine
integration-tests Postgres unless we issue DDL ourselves).

The new file lives in a sub-namespace
so its IOrderRepository / OrderEntity / etc. fixtures don't collide with
the unrelated identically-named fixtures in Bug_252_codegen_issue.cs
under the parent namespace.

Also adds documentation:
  - new  section in
    docs/guide/durability/efcore/transactional-middleware.md
  - Five  blocks in the new test file (the registration
    shape, the simple-handler shape, the multi-abstraction registration,
    the multi-abstraction handler, and the mixed-DbContext registration),
    expanded by mdsnippets so every code block in the docs lives in a
    passing test.

Local: full wolverine.slnx -c Release builds clean (0 warnings, 0 errors).
All three scenarios pass (Postgres locally on port 5433).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 3abe3f6 into main May 28, 2026
23 checks passed
jeremydmiller added a commit that referenced this pull request May 28, 2026
Bug-fix + feature release on top of 6.1.0 — 13 PRs.

Notable additions:
- Custom Result<T> handler-return-value support (Phases 0+1+2+3, #2952, refs #2221)
- DbContext abstractions for EF Core transaction middleware (#2919 + docs/tests #2954)
- Outgoing Envelope pooling at MessageRouter.RouteForPublish (#2956, closes #2955)
  — ~-504 B/op on transport-bound sends per the CritterStackScalability
  WolverineTransportBenchmarks harness

Bug fixes: scheduled-cascade loss from [ReadAggregate]/[DocumentExists]
handlers (#2941), ancillary-store inbox routing (#2944), Postgres queue-name
length (#2942), MySQL node-record quoting (#2940), Pulsar batched-partition
ack KeyNotFoundException (#2883/#2950), remote-node agent reply timeout
(#2949), and additional resource-disposal cleanup (#2894 from
dmytro-pryvedeniuk).

Polecat bumped 4.1.1 -> 4.2.1 (#2947); Marten + JasperFx families unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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