Skip to main content

Fixed: "The connection is already in a transaction and cannot participate in another transaction" in EF Core

· 5 min read
Jagdish Kumawat
Founder @ Dewiride

Calling one helper method from inside a transaction failed with The connection is already in a transaction and cannot participate in another transaction. The helper had its own BeginTransactionAsync, which made sense when it was written, and it stopped making sense once something else called it inside a transaction.

What I Was Doing​

In my .NET C# Web API with EF Core 10.0.14, an endpoint started a transaction and called a helper that changes stock:

An endpoint
g.MapPost("/tx/helper-begins", async (InventoryDbContext db, CancellationToken cancellationToken) =>
{
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);

await AdjustStockWithOwnTransactionAsync(db, 1, cancellationToken);

await transaction.CommitAsync(cancellationToken);

return Results.Ok();
});

The helper had a transaction of its own, because it had first been written to stand alone:

The helper
static async Task AdjustStockWithOwnTransactionAsync(InventoryDbContext db, int productId, CancellationToken cancellationToken)
{
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);

await db.Products
.Where(p => p.Id == productId)
.ExecuteUpdateAsync(s => s.SetProperty(p => p.ReorderLevel, p => p.ReorderLevel + 1), cancellationToken);

await transaction.CommitAsync(cancellationToken);
}

What I Expected​

The helper's work to be part of the outer transaction.

What I Got​

Response
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500,
"traceId": "00-b9a6979c1091e22bdda1066822c03c84-d260a45be8968b15-00"
}
API log
System.InvalidOperationException: The connection is already in a transaction and cannot participate in another transaction.

An endpoint answering 500, with the log line System.InvalidOperationException: The connection is already in a transaction and cannot participate in another transaction.

What I Checked​

Which call fails. The second BeginTransactionAsync, in the helper. The log shows the first one beginning, and nothing has run yet:

API log
Beginning transaction with isolation level 'Unspecified'.
Began transaction with isolation level 'Unspecified'.

The EF Core docs. The transactions page shows one transaction per connection, and savepoints for marking points inside it.

What MySQL offers. MySQL has no nested transactions. START TRANSACTION inside a transaction commits the first one, so EF Core stopping this is a favour.

Why It Happens​

A DbContext has one connection, and a connection has one transaction at a time. The message says exactly that. When a helper method and its caller share a scoped DbContext from dependency injection, they share that connection too.

The Fix​

Let one place own the transaction, and let everything it calls work inside it. The helper loses its transaction:

The helper
static async Task AdjustStockAsync(InventoryDbContext db, int productId, CancellationToken cancellationToken)
{
await db.Products
.Where(p => p.Id == productId)
.ExecuteUpdateAsync(s => s.SetProperty(p => p.ReorderLevel, p => p.ReorderLevel + 1), cancellationToken);
}

And the endpoint calls it inside its own transaction:

An endpoint
g.MapPost("/tx/helper-joins", async (InventoryDbContext db, CancellationToken cancellationToken) =>
{
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);

await AdjustStockAsync(db, 1, cancellationToken);

await transaction.CommitAsync(cancellationToken);

return Results.Ok();
});

If the helper must also work alone, its caller starts the transaction in both cases. If you need a part of the work to fail without losing the rest, use a savepoint.

After the Fix​

Response
HTTP/1.1 200 OK
API log
dbug: Microsoft.EntityFrameworkCore.Database.Transaction[20200]
Began transaction with isolation level 'Unspecified'.
dbug: Microsoft.EntityFrameworkCore.Database.Transaction[20202]
Committed transaction.

An endpoint answering 200 OK, with the log showing one Began transaction and one Committed transaction.

Common Mistakes​

MistakeWhat happensDo this instead
Using a second DbContext for the helperThat's a second connection, and a second transactionShare one, unless you mean to
Sending START TRANSACTION as raw SQLMySQL commits the first oneUse one transaction
Committing in the helperThe caller's later work runs outside itCommit in one place

Troubleshooting​

SymptomLikely causeFix
does not support user-initiated transactionsThe transaction is outside the strategyThe fix
Connection must be valid and open to commit transactionA second commitThe fix
It only happens from one callerThat caller has a transaction openCheck db.Database.CurrentTransaction

Conclusion​

A DbContext's connection holds one transaction at a time. Begin it in one place, and let the methods it calls run inside it.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.