DbContext abstractions with ef core transactions#2919
Conversation
|
@XL1TTE Going to lead with:
And also the very antithesis of how Wolverine is meant to be used. As a bridge for existing code though, sure, I get that. |
|
@jeremydmiller It's primarily about Repository abstraction rather than Unit of Work for me. The challenge arises when packages have intrusive designs that force you to pollute your domain models with persistence-related attributes. This creates a design dilemma:
If you choose option 3, you inevitably need a bidirectional mapping layer (domain ↔ persistence), which introduces complexity and maintenance overhead. The real pain point emerges when you use var orderEntity = db.Orders.Find(id);
var order = orderEntity.ToDomain(); // Mapping
// business operations on order
orderEntity.ApplyUpdate(order); // Mapping
db.SaveChanges();I get it why repository abstraction is opinionated with modern efCore, but the cases like this one above is actually kind a difficult for me to work around w/o them. P.S: P.P.S: Would be happy to hear some insights about that. |
|
@XL1TTE I'll take this in, but to be more idiomatic in Wolverine, you could use the EF Core transaction middleware and other integration and write code like this: public static void Handle(UpdateOrder command, [Entity] Order orderEntity)
{
// do whatever to mutate the Order
}
// Wolverine takes care of both loading the Order and SaveChangesAsync & the outbox for that matterThe Wolverine way leads to simpler code, business logic being decoupled from infrastructure, more testable code, and less layering hoops to jump through |
Follow-up to #2919: docs + extended scenario tests for DbContext abstractions
…ext abstractions Reviewer-requested follow-up to PR JasperFx#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 JasperFxGH-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>
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>
Enable developers to use an abstraction layer above
DbContextby callingWithDbContextAbstraction<TAbstraction, TDbContext>().This approach allows you to implement
IUnitOfWorkwith any number ofIRepositoryinterfaces per DbContext and use this abstraction in handlers instead of directly depending onDbContext.