Skip to content

Commit 5403532

Browse files
Merge pull request #3122 from JasperFx/fix-3121-efcore-schedule-implicit-transaction
Don't start an implicit EF Core transaction on ScheduleAsync for a Wolverine-mapped DbContext (closes #3121)
2 parents 8854710 + 92d22fa commit 5403532

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,37 @@ public void outbox_for_db_context_mapped()
193193
.DbContext.ShouldBeSameAs(context);
194194
}
195195

196+
[Fact]
197+
public async Task persisting_against_mapped_dbcontext_does_not_start_an_explicit_transaction()
198+
{
199+
await Host.ResetResourceState();
200+
201+
var envelope = new Envelope
202+
{
203+
Data = [1, 2, 3, 4],
204+
OwnerId = 5,
205+
Destination = TransportConstants.RepliesUri,
206+
MessageType = "foo",
207+
ContentType = EnvelopeConstants.JsonContentType,
208+
Status = EnvelopeStatus.Scheduled,
209+
ScheduledTime = DateTimeOffset.UtcNow.AddMinutes(1)
210+
};
211+
212+
using var nested = Host.Services.CreateScope();
213+
var messaging = nested.ServiceProvider.GetRequiredService<IDbContextOutbox<SampleMappedDbContext>>()
214+
.ShouldBeOfType<DbContextOutbox<SampleMappedDbContext>>();
215+
216+
// Regression for #3121 -- scheduling/incoming persistence against a Wolverine-mapped
217+
// DbContext must NOT begin an explicit EF Core transaction. SaveChanges provides its
218+
// own implicit transaction, and the outgoing path already behaves this way.
219+
await messaging.Transaction!.PersistIncomingAsync(envelope);
220+
messaging.DbContext.Database.CurrentTransaction.ShouldBeNull();
221+
222+
// Symmetry check: the outgoing path likewise leaves the transaction alone
223+
await messaging.Transaction!.PersistOutgoingAsync(envelope);
224+
messaging.DbContext.Database.CurrentTransaction.ShouldBeNull();
225+
}
226+
196227
[Fact]
197228
public async Task persist_an_outgoing_envelope_raw()
198229
{

src/Persistence/Wolverine.EntityFrameworkCore/Internals/EfCoreEnvelopeTransaction.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,27 @@ public async Task PersistOutgoingAsync(Envelope[] envelopes)
9898

9999
public async Task PersistIncomingAsync(Envelope envelope)
100100
{
101-
if (DbContext.Database.CurrentTransaction == null)
102-
{
103-
await DbContext.Database.BeginTransactionAsync();
104-
}
105-
106101
if (DbContext.IsWolverineEnabled())
107102
{
103+
// For a Wolverine-mapped DbContext the envelope is just tracked and flushed by
104+
// SaveChangesAsync, so don't force an explicit transaction here. We defer to whatever
105+
// the ambient transaction mode established: in Eager middleware mode a transaction was
106+
// already begun (EnrollDbContextInTransaction / StartDatabaseTransactionForDbContext) and
107+
// the tracked entity enrolls into it at SaveChanges; in Lightweight/lazy mode there is no
108+
// transaction and SaveChanges provides its own implicit one. This mirrors
109+
// PersistOutgoingAsync and keeps ScheduleAsync from starting a transaction the caller
110+
// never asked for (see #3121).
108111
DbContext.Add(new IncomingMessage(envelope));
109112
}
110113
else
111114
{
115+
// The raw branch issues an ADO command that has to share the caller's transaction, so
116+
// here we do need an explicit one when none is already in flight.
117+
if (DbContext.Database.CurrentTransaction == null)
118+
{
119+
await DbContext.Database.BeginTransactionAsync();
120+
}
121+
112122
var conn = DbContext.Database.GetDbConnection();
113123
var tx = DbContext.Database.CurrentTransaction!.GetDbTransaction();
114124
var builder = _database.ToCommandBuilder();

0 commit comments

Comments
 (0)