Transactions
A transaction makes several changes succeed or fail together. Without one, a crash halfway through a money transfer loses money.
On this page · 5 min · Intermediate
On this page
- In simple words
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Related tutorials
In simple words#
Like a fixed-price thali: either you get the whole meal or you pay nothing. A transaction applies all changes or none.
- BeginTransaction
- Start the group of changes.
- Commit / Rollback
- Save all / undo all.
- cmd.Transaction
- Every command must join the transaction.
Learning objectives#
- Use SqlTransaction with Commit and Rollback
- Enlist every command in the transaction
- Choose an isolation level
- Use TransactionScope correctly with async code
Explanation#
connection.BeginTransaction() starts a transaction. Every command that should be part of it must have its Transaction property set.
Commit makes the changes permanent. If an exception occurs, call Rollback — or simply let the transaction be disposed without Commit, which rolls it back.
Isolation levels control what other sessions can see: ReadCommitted is the default; Snapshot avoids blocking readers; Serializable is the strictest.
TransactionScope is an ambient alternative. In async code create it with TransactionScopeAsyncFlowOption.Enabled, or it will not flow across await.
Code example: Place an order atomically#
await using var conn = new SqlConnection(cs);
await conn.OpenAsync();
await using var tx = (SqlTransaction)await conn.BeginTransactionAsync();
try
{
await using var stock = new SqlCommand(
"UPDATE Products SET Stock = Stock - @qty WHERE Id = @id AND Stock >= @qty", conn, tx);
stock.Parameters.Add("@qty", SqlDbType.Int).Value = 2;
stock.Parameters.Add("@id", SqlDbType.Int).Value = 1;
if (await stock.ExecuteNonQueryAsync() == 0)
throw new InvalidOperationException("Not enough stock");
await using var order = new SqlCommand(
"INSERT INTO Orders (ProductId, Quantity) OUTPUT INSERTED.Id VALUES (@id, @qty)", conn, tx);
order.Parameters.Add("@id", SqlDbType.Int).Value = 1;
order.Parameters.Add("@qty", SqlDbType.Int).Value = 2;
int orderId = (int)(await order.ExecuteScalarAsync())!;
await tx.CommitAsync();
Console.WriteLine(quot;Order #{orderId} committed");
}
catch (Exception ex)
{
await tx.RollbackAsync();
Console.WriteLine(quot;Rolled back: {ex.Message}");
}
Order #1002 committed — or, if stock is too low: "Rolled back: Not enough stock"
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- The transaction is started on an open connection.
- Both commands receive the transaction through their constructor.
- If stock cannot be reduced, an exception aborts the whole operation.
- Commit saves both changes; Rollback undoes both.
Real-world use case#
Transferring money between accounts: the debit and the credit must both happen, or neither.
Common mistakes#
SqlClient throws “ExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction.”
Locks are held until Commit. Never wait for user input or call slow web services inside a transaction.
Best practices#
- Keep transactions short
- Pass the transaction to every command
- Use await using so uncommitted transactions roll back
- Consider Snapshot isolation for read-heavy systems
Remember this#
BeginTransaction → every command → Commit / Rollback.
Dispose without Commit = rollback.
Async TransactionScope needs AsyncFlowOption.Enabled.
Practice exercise#
Your task: Rewrite the order with TransactionScope for async code.
Show solution
using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
await using var conn = new SqlConnection(cs);
await conn.OpenAsync(); // enlists automatically
// … run the two commands without setting Transaction …
scope.Complete(); // commit when disposed
Mini quiz#
What happens if a SqlTransaction is disposed without Commit?
Want more? Practise all ADO.NET MCQs · ADO.NET interview questions
My notes#
Saved in this browser.