Entity Framework employs and facilitates the use of transactions, as a fundamental tool to maintain data integrity.
A transaction is a unit of work that groups several operations that must be executed atomically, meaning:
Either all succeed, or none is executed.
They are one of the most important mechanisms we have to maintain data integrity.
Let’s see how to manage them with Entity Framework automatically or manually.
Automatic Transactions
Entity Framework Core uses automatic internal transactions by default. Every time we call SaveChanges()
or SaveChangesAsync()
, EF Core creates and wraps that operation within a transaction.
This means that if we make multiple changes to entities and then call SaveChanges()
, EF Core will apply everything within a single transaction.
using (var context = new AppDbContext())
{
var client = new Client { Name = "Carlos" };
var order = new Order { Date = DateTime.Now };
context.Clients.Add(client);
context.Orders.Add(order);
context.SaveChanges(); // All this is a single transaction
}
Manual Transactions
Entity Framework Core allows us to manually control transactions through IDbContextTransaction
.
using (var context = new AppDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
var client = new Client { Name = "Ana" };
context.Clients.Add(client);
context.SaveChanges();
var order = new Order { ClientId = client.Id, Date = DateTime.Now };
context.Orders.Add(order);
context.SaveChanges();
transaction.Commit(); // Everything went well, changes are confirmed
}
catch (Exception ex)
{
transaction.Rollback(); // Something went wrong, changes are undone
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Rollback when not confirming the transaction
If an exception occurs before calling Commit()
, EF Core will revert the transaction by disposing of the object (for example, when exiting the using
).
try
{
using var tx = context.Database.BeginTransaction();
// operations...
tx.Commit(); // only if everything went well
}
catch (Exception ex)
{
// implicit rollback when exiting the block
Console.WriteLine("Transaction error.");
}
Transactions with Multiple DbContexts
Sometimes, we need to use multiple DbContext
s within the same transaction. For this, they must share the same connection:
using var connection = new SqlConnection(connectionString);
connection.Open();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(connection)
.Options;
using var context1 = new AppDbContext(options);
using var context2 = new AnotherDbContext(options);
using var transaction = connection.BeginTransaction();
context1.Database.UseTransaction(transaction);
context2.Database.UseTransaction(transaction);
try
{
context1.Something.Add(new Entity1());
context1.SaveChanges();
context2.AnotherThing.Add(new Entity2());
context2.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
Nested Transactions with Savepoints
EF Core allows creating save points within a transaction for partial rollback:
await using var tx = await context.Database.BeginTransactionAsync();
try
{
// step 1
context.Something.Add(new EntityA());
await context.SaveChangesAsync();
await tx.CreateSavepointAsync("Step1");
// step 2
context.Something.Add(new EntityB());
await context.SaveChangesAsync();
await tx.RollbackToSavepointAsync("Step1");
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
}
When to use manual transactions in EF Core?
Although Entity Framework handles automatic transactions with each SaveChanges()
, there are situations where it is beneficial to use explicit transactions:
- When you need to group multiple
SaveChanges()
calls into a single unit of work. - If you need to perform a partial rollback (using savepoints).
- When working with multiple
DbContext
s or databases. - If you combine access with ADO.NET or other external operations.