What
StartDatabaseTransactionForDbContext.GenerateCode (the multi-tenant counterpart to EnrollDbContextInTransaction) emits the eager idempotency check twice, under identical conditions:
https://github.com/JasperFx/wolverine/blob/main/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/StartDatabaseTransactionForDbContext.cs#L32-L50
writer.Write("BLOCK:try");
// EF Core can only do eager idempotent checks
if (_idempotencyStyle == IdempotencyStyle.Eager || _idempotencyStyle == IdempotencyStyle.Optimistic)
{
writer.Write($"await {_context!.Usage}.AssertEagerIdempotencyAsync(...);"); // (1)
}
writer.Write($"BLOCK:if ({_dbContext.Usage}.Database.CurrentTransaction == null)");
writer.Write($"await {_dbContext.Usage}.Database.BeginTransactionAsync(...);");
writer.FinishBlock();
// EF Core can only do eager idempotent checks
if (_idempotencyStyle == IdempotencyStyle.Eager || _idempotencyStyle == IdempotencyStyle.Optimistic)
{
writer.Write($"await {_context!.Usage}.AssertEagerIdempotencyAsync(...);"); // (2) - identical
}
Both blocks sit at the top level of the same try, separated only by the begin-transaction if. Same guard, same emitted statement.
Which one is the duplicate
The non-tenanted sibling EnrollDbContextInTransaction.GenerateCode emits it once, after BeginTransactionAsync and inside the try:
https://github.com/JasperFx/wolverine/blob/main/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollDbContextInTransaction.cs#L43-L49
So (2) matches the established shape and (1) looks like the copy-paste leftover to remove.
Impact
Mostly wasted work rather than wrong behavior, but not free:
- Where an
IEnvelopeTransaction is attached, call (1) sets Envelope.WasPersistedInInbox = true, so (2) short-circuits on the guard clause at the top of AssertEagerIdempotencyAsync. Cheap.
- Where
Transaction == null || Transaction is MessageContext, AssertEagerIdempotencyAsync takes the Runtime.Storage.Inbox.ExistsAsync(...) branch, which does not set WasPersistedInInbox. That path runs two identical inbox existence queries per message.
Ordering also differs slightly between the two placements — (1) runs the check before the tenant database transaction is opened, (2) after — which is a real semantic difference for the multi-tenant case even if only one call survives.
Found via
Auditing the wolverine-messaging-handler-idempotency AI skill against the source while documenting IdempotencyStyle. Related docs issue: #4127.
What
StartDatabaseTransactionForDbContext.GenerateCode(the multi-tenant counterpart toEnrollDbContextInTransaction) emits the eager idempotency check twice, under identical conditions:https://github.com/JasperFx/wolverine/blob/main/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/StartDatabaseTransactionForDbContext.cs#L32-L50
Both blocks sit at the top level of the same
try, separated only by the begin-transactionif. Same guard, same emitted statement.Which one is the duplicate
The non-tenanted sibling
EnrollDbContextInTransaction.GenerateCodeemits it once, afterBeginTransactionAsyncand inside thetry:https://github.com/JasperFx/wolverine/blob/main/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollDbContextInTransaction.cs#L43-L49
So (2) matches the established shape and (1) looks like the copy-paste leftover to remove.
Impact
Mostly wasted work rather than wrong behavior, but not free:
IEnvelopeTransactionis attached, call (1) setsEnvelope.WasPersistedInInbox = true, so (2) short-circuits on the guard clause at the top ofAssertEagerIdempotencyAsync. Cheap.Transaction == null || Transaction is MessageContext,AssertEagerIdempotencyAsynctakes theRuntime.Storage.Inbox.ExistsAsync(...)branch, which does not setWasPersistedInInbox. That path runs two identical inbox existence queries per message.Ordering also differs slightly between the two placements — (1) runs the check before the tenant database transaction is opened, (2) after — which is a real semantic difference for the multi-tenant case even if only one call survives.
Found via
Auditing the
wolverine-messaging-handler-idempotencyAI skill against the source while documentingIdempotencyStyle. Related docs issue: #4127.