Skip to content

fix(efcore): run domain-event scrapers on managed multi-tenancy commit path#3060

Merged
jeremydmiller merged 1 commit into
JasperFx:mainfrom
outofrange-consulting:fix/multitenancy-domain-events-scrape
Jun 9, 2026
Merged

fix(efcore): run domain-event scrapers on managed multi-tenancy commit path#3060
jeremydmiller merged 1 commit into
JasperFx:mainfrom
outofrange-consulting:fix/multitenancy-domain-events-scrape

Conversation

@outofrange-consulting

Copy link
Copy Markdown
Contributor

Problem

Under managed multi-tenancy (AddDbContextWithWolverineManagedMultiTenancy<T>), domain events registered via PublishDomainEventsFromEntityFrameworkCore<T>(x => x.Events) are silently never published. A handler can mutate/persist an entity that raises a domain event, the transaction commits, but the event never reaches the outbox.

Root cause

The EF Core transactional middleware has two codegen paths:

  • The single-DbContext path commits through EfCoreEnvelopeTransaction.CommitAsync() (Internals/EfCoreEnvelopeTransaction.cs), which runs the registered IDomainEventScrapers — emitted by CommitEfCoreEnvelopeTransaction (Codegen/EnrollDbContextInTransaction.cs).
  • The managed-multitenancy path commits the raw EF transaction directly in CommitTenantedDbContextTransaction.GenerateCode (Codegen/StartDatabaseTransactionForDbContext.cs) and never invokes CommitAsync(). The enlisted EfCoreEnvelopeTransaction is built (Internals/TenantedDbContextBuilderByConnectionString.cs BuildAndEnrollAsync), but at runtime inside the DbContext builder — so it is never surfaced as a codegen variable and its scraper loop is unreachable from the generated chain.

Fix

Inline the scrape loop into CommitTenantedDbContextTransaction, resolving IEnumerable<IDomainEventScraper> exactly as EnrollDbContextInTransaction.FindVariables does and running it before the commit + flush — mirroring EfCoreEnvelopeTransaction.CommitAsync(). The GH-2917 ordering (commit + flush in the postprocessor before the HTTP response writer) and the IFlushesMessages contract are preserved, so no duplicate flush is introduced.

Surfacing the enlisted EfCoreEnvelopeTransaction as a codegen variable (the cleaner alternative) is not feasible on this path: the DbContext and its transaction are constructed at runtime inside BuildAndEnrollAsync, never returned as a codegen variable, so FindVariable(typeof(EfCoreEnvelopeTransaction)) would throw at generation time.

Test

Adds domain_events_scraping_with_managed_multi_tenancy under EfCoreTests.MultiTenancy, the managed-multitenancy counterpart of the single-DbContext scraper tests in EfCoreTests/DomainEvents/configuration_of_domain_events_scrapers.cs. It approves an entity under a tenant and asserts the scraped domain event is published via a tracked session. The test fails on main and passes with this change. Existing PostgreSQL multitenancy suites (cascading messages, HTTP posts, sagas, outbox) still pass; the empty-scraper case resolves to an empty enumerable, identical to the single-DbContext path.

Affected versions

V6.0.0 – V6.5.1 and main. The omission predates and is independent of #2920 / GH-2917, which only concern commit/flush ordering relative to the HTTP response writer.

…t path

The EF Core transactional middleware has two codegen paths. The single-DbContext
path commits through EfCoreEnvelopeTransaction.CommitAsync(), which runs the
IDomainEventScraper instances registered by
PublishDomainEventsFromEntityFrameworkCore<T>(x => x.Events). The managed
multi-tenancy path (StartDatabaseTransactionForDbContext /
CommitTenantedDbContextTransaction) committed the raw EF transaction directly
and never invoked CommitAsync(), so the scrapers never ran and a mutated
entity's domain events were silently dropped under managed multi-tenancy.

The enlisted EfCoreEnvelopeTransaction is built at runtime inside
IDbContextBuilder<T>.BuildAndEnrollAsync, so it is never surfaced as a codegen
variable on this path and its CommitAsync() cannot be called from generated
code. Inline the scrape loop into CommitTenantedDbContextTransaction instead,
resolving IEnumerable<IDomainEventScraper> exactly as EnrollDbContextInTransaction
does and running it before the commit + flush. JasperFxGH-2917 ordering and the
IFlushesMessages contract are preserved.

Adds a managed-multi-tenancy regression test mirroring the single-DbContext
domain-event scraper tests.
@outofrange-consulting outofrange-consulting force-pushed the fix/multitenancy-domain-events-scrape branch from 2284746 to 9da4354 Compare June 9, 2026 19:42
@jeremydmiller

Copy link
Copy Markdown
Member

@outofrange-consulting Why are you even using the domain event scraping? Was that legacy code you retrofitted to Wolverine?

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.

2 participants