You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
IDbContextOutbox.ScheduleAsync starts the ef core transaction implicitly #3121
Describe the bug
When "IDbContextOutbox.PublishAsync" is called, an explicit transaction is not started. This is correct, as EF CORE allows you not to run explicit transactions, but only to use SaveChanges.
When calling "IDbContextOutbox.ScheduleAsync" an explicit transaction is running. This is not correct, since EF CORE allows you not to run explicit transactions, but only to use SaveChanges.
This is not expected for client code. It is up to the client code to decide for itself whether it wants to run an explicit transaction or not.
The wolverine source code shows that the "transaction.PersistOutgoingAsync(envelope);" method is being called for "EnvelopeStatus.Outgoing" which does not trigger an explicit transaction.
And for "EnvelopeStatus.Scheduled" the "transaction.PersistIncomingAsync(envelope)" method is called which triggers an explicit transaction:
app.MapGet("/t1", async (IDbContextOutbox outbox, AppDbContext db) =>
{
outbox.Enroll(db);
await outbox.PublishAsync(new TestMsg()); // does not start an ef core transaction implicitly
await db.SaveChangesAsync(); // Saves to the wolverine table wolverine_outgoing_envelopes
});
app.MapGet("/t2", async (IDbContextOutbox outbox, AppDbContext db) =>
{
outbox.Enroll(db);
await outbox.ScheduleAsync(new TestMsg(), TimeSpan.FromMinutes(1)); // starts the ef core transaction implicitly
await db.SaveChangesAsync(); // Wolverine itself implicitly started the transaction 😮, and now it turns out that I must definitely CommitAsync.
});
Expected behavior
ScheduleAsync should not run the ef core transaction itself, because PublishAsync does not run.
@Maflend I take pull requests, and you're using Wolverine non-idiomatically up there, and that makes you hard to support. I think you could have an easier time using Wolverine.HTTP and being more idiomatic. This particular usage got missed in the umpteen times we have to adjust EF Core transactions.
And hey, you're consistently needing a lot of assistance with Wolverine. Please consider the support agreement, or look to contribute some instead.
Thanks for the detailed report — you're correct, and there's a fix on the way in #3122.
Root cause: ScheduleAsync produces an envelope with status Scheduled, which routes to EfCoreEnvelopeTransaction.PersistIncomingAsync. That method was beginning an explicit EF Core transaction unconditionally, whereas PersistOutgoingAsync (used by PublishAsync) only does so for the non-Wolverine-mapped path. The fix makes PersistIncomingAsync mirror the outgoing path, so for a Wolverine-mapped DbContext it just tracks the envelope and lets SaveChangesAsync provide the transaction.
One important note for your usage: this only applies when the DbContext type is enabled for Wolverine — i.e. you've called modelBuilder.MapWolverineEnvelopeStorage(...) in your OnModelCreating so the envelope storage is mapped into that DbContext. For a plain (non-mapped) DbContext, Wolverine has to persist the envelope through a raw ADO command that must share your transaction, so it will still open one (consistent with how PublishAsync behaves in that same raw scenario). So to get the "no implicit transaction" behavior for both PublishAsync and ScheduleAsync, use a Wolverine-mapped DbContext.
@Maflend I take pull requests, and you're using Wolverine non-idiomatically up there, and that makes you hard to support. I think you could have an easier time using Wolverine.HTTP and being more idiomatic. This particular usage got missed in the umpteen times we have to adjust EF Core transactions.
And hey, you're consistently needing a lot of assistance with Wolverine. Please consider the support agreement, or look to contribute some instead.
A small adjustment is coming up in 6.11 soon.
It would be good to start considering this an idiomatic way of applying it.
Using Wolverine exclusively as a messaging framework (similar to EasyNetQ or MassTransit) opens the way for it to projects where they are not ready to change the architecture entirely.
The documentation details the advantages of Wolverine over Mediatr and MVC Controllers, but I understand that refactoring the current code for Wolverine.HTTP or its CQRS is too big a step that not everyone is ready to take.
At the same time, the problem of reliable delivery via buses is acute for most projects. Wolverine offers ready-made solutions for this task: guaranteed Inbox/Outbox delivery, deferred messages, partitioning, and so on. This is exactly the functionality that everyone needs, and it would be great to be able to implement it point-by-point.
At first glance, it seems that a step has already been taken towards this by adding an IDbContextOutbox.
And this is an important step, as it has already allowed us to switch to wolverine on two projects to implement reliable EDA with Kafka.
Activity
jeremydmiller commentedon Jun 16, 2026
@Maflend I take pull requests, and you're using Wolverine non-idiomatically up there, and that makes you hard to support. I think you could have an easier time using Wolverine.HTTP and being more idiomatic. This particular usage got missed in the umpteen times we have to adjust EF Core transactions.
And hey, you're consistently needing a lot of assistance with Wolverine. Please consider the support agreement, or look to contribute some instead.
A small adjustment is coming up in 6.11 soon.
Clarify in code that the mapped-DbContext path defers to the ambient …
jeremydmiller commentedon Jun 16, 2026
Thanks for the detailed report — you're correct, and there's a fix on the way in #3122.
Root cause:
ScheduleAsyncproduces an envelope with statusScheduled, which routes toEfCoreEnvelopeTransaction.PersistIncomingAsync. That method was beginning an explicit EF Core transaction unconditionally, whereasPersistOutgoingAsync(used byPublishAsync) only does so for the non-Wolverine-mapped path. The fix makesPersistIncomingAsyncmirror the outgoing path, so for a Wolverine-mappedDbContextit just tracks the envelope and letsSaveChangesAsyncprovide the transaction.One important note for your usage: this only applies when the
DbContexttype is enabled for Wolverine — i.e. you've calledmodelBuilder.MapWolverineEnvelopeStorage(...)in yourOnModelCreatingso the envelope storage is mapped into thatDbContext. For a plain (non-mapped)DbContext, Wolverine has to persist the envelope through a raw ADO command that must share your transaction, so it will still open one (consistent with howPublishAsyncbehaves in that same raw scenario). So to get the "no implicit transaction" behavior for bothPublishAsyncandScheduleAsync, use a Wolverine-mappedDbContext.Merge pull request #3122 from JasperFx/fix-3121-efcore-schedule-impli…
Maflend commentedon Jun 16, 2026
It would be good to start considering this an idiomatic way of applying it.
Using Wolverine exclusively as a messaging framework (similar to EasyNetQ or MassTransit) opens the way for it to projects where they are not ready to change the architecture entirely.
The documentation details the advantages of Wolverine over Mediatr and MVC Controllers, but I understand that refactoring the current code for Wolverine.HTTP or its CQRS is too big a step that not everyone is ready to take.
At the same time, the problem of reliable delivery via buses is acute for most projects. Wolverine offers ready-made solutions for this task: guaranteed Inbox/Outbox delivery, deferred messages, partitioning, and so on. This is exactly the functionality that everyone needs, and it would be great to be able to implement it point-by-point.
At first glance, it seems that a step has already been taken towards this by adding an IDbContextOutbox.
And this is an important step, as it has already allowed us to switch to wolverine on two projects to implement reliable EDA with Kafka.
Thanks!
Don't start an explicit EF Core transaction when persisting scheduled…