database
error-handling
transaction-management
connection-issues
.NET-development

Error - The transaction associated with the current connection has completed but has not been disposed

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

This error appears when a database transaction has already been committed or rolled back, but the code still holds on to it and keeps using the same connection as if that transaction were still active. In .NET, the fix is usually straightforward: scope the transaction correctly, dispose it immediately after completion, and do not reuse commands that still point at the finished transaction.

Why the Error Happens

A transaction has a lifecycle:

  1. begin
  2. run commands
  3. commit or roll back
  4. dispose

If step 4 is skipped, the connection can remain associated with a completed transaction object. Later commands then trip over that stale state.

This often happens when:

  • a transaction is committed manually but not wrapped in using
  • a command keeps its Transaction property set after the transaction ended
  • code mixes TransactionScope and explicit transactions incorrectly
  • exception paths roll back but never dispose

A Safe Pattern With SqlTransaction

csharp
1using var connection = new SqlConnection(connectionString);
2connection.Open();
3
4using var transaction = connection.BeginTransaction();
5
6try
7{
8    using var command = connection.CreateCommand();
9    command.Transaction = transaction;
10    command.CommandText = "INSERT INTO Users(Name) VALUES (@name)";
11    command.Parameters.AddWithValue("@name", "Ada");
12    command.ExecuteNonQuery();
13
14    transaction.Commit();
15}
16catch
17{
18    transaction.Rollback();
19    throw;
20}

This pattern works because the using scope guarantees disposal even if an exception occurs.

A Common Broken Pattern

Problems appear when code continues after commit with a command still tied to the old transaction:

csharp
1var command = connection.CreateCommand();
2command.Transaction = transaction;
3
4transaction.Commit();
5
6command.CommandText = "SELECT COUNT(*) FROM Users";
7command.ExecuteScalar();

The transaction is completed, but the command is still associated with it. That is exactly the kind of stale state that leads to this error.

TransactionScope Needs Discipline Too

If you use TransactionScope, complete and dispose the scope cleanly:

csharp
1using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
2{
3    // database work
4    scope.Complete();
5}

Do not keep database objects alive outside the scope as though the ambient transaction still exists.

Also be careful not to mix explicit BeginTransaction() calls with ambient transaction scopes unless you really intend to manage both levels.

Async Code and Lifetime Issues

Async methods can make transaction lifetime bugs easier to hide. If you open a transaction in one method and let commands escape into another layer, the ownership boundaries become unclear.

A good rule is:

  • the method that begins the transaction should also commit or roll back it
  • that same method should own the disposal boundary

That keeps transaction state local and predictable.

Practical Debugging Steps

When you see this error, inspect:

  • where the transaction is created
  • whether it is wrapped in using
  • whether commands are reused after Commit() or Rollback()
  • whether a transaction object survives longer than the code that owns it

In many cases, the root cause is simply one forgotten Dispose boundary.

Common Pitfalls

The biggest mistake is treating Commit() as the end of the transaction story. It is not; disposal still matters.

Another mistake is reusing a DbCommand whose Transaction property still points to a completed transaction.

A third issue is spreading transaction ownership across multiple methods or services so that nobody clearly owns cleanup.

Summary

  • This error means a completed transaction is still hanging around on the connection or command path.
  • Wrap transactions in using so disposal always happens.
  • Do not reuse commands that still reference a committed or rolled-back transaction.
  • Be careful when mixing explicit transactions with TransactionScope.
  • Keep transaction ownership local so begin, commit or rollback, and dispose happen in one coherent scope.

Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design