MSDN Documentation

Entity Framework Core Transactions

Understanding Transactions in Entity Framework Core

This document provides a comprehensive guide to managing transactions within Entity Framework Core (EF Core). Proper transaction management is crucial for ensuring data integrity, atomicity, and consistency in your applications, especially when performing multiple database operations that must succeed or fail together.

What are Database Transactions?

A database transaction is a sequence of database operations that are performed as a single, logical unit of work. Transactions follow the ACID properties:

EF Core's Default Transaction Behavior

By default, EF Core automatically manages transactions for you. When you call SaveChanges() or SaveChangesAsync() on your DbContext instance, EF Core attempts to wrap these operations within a single database transaction. If all operations within SaveChanges() succeed, the transaction is committed. If any operation fails, the transaction is rolled back, and the database is returned to its state before the SaveChanges() call.

Note: This default behavior is sufficient for many common scenarios where a single SaveChanges() call encompasses all related database modifications.

Explicitly Managing Transactions

There are scenarios where you need more control over transaction boundaries, such as when you need to perform multiple DbContext operations across different contexts or when you need to integrate with external transaction management systems. EF Core provides mechanisms for explicit transaction management.

Using BeginTransaction()

You can manually start a transaction by calling the BeginTransaction() method on the DbContext.Database property. This method returns a IDbContextTransaction object that you can use to control the transaction.


using (var context = new YourDbContext())
{
    using (var transaction = context.Database.BeginTransaction())
    {
        try
        {
            // Perform first operation
            var product1 = new Product { Name = "Laptop" };
            context.Products.Add(product1);
            context.SaveChanges();

            // Perform second operation
            var order = new Order { CustomerId = 1, OrderDate = DateTime.UtcNow };
            context.Orders.Add(order);
            context.SaveChanges();

            // If all operations succeed, commit the transaction
            transaction.Commit();
        }
        catch (Exception ex)
        {
            // If any operation fails, rollback the transaction
            transaction.Rollback();
            // Log or re-throw the exception
            Console.WriteLine($"An error occurred: {ex.Message}");
            throw;
        }
    }
}
        

Transaction Isolation Levels

When starting a transaction explicitly, you can specify the isolation level to control how transactions are isolated from each other. EF Core supports standard SQL Server isolation levels.


using (var context = new YourDbContext())
{
    using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
    {
        // ... your transaction logic ...
    }
}
        

Common isolation levels include:

Tip: Choose an isolation level that balances data consistency requirements with performance considerations. Higher isolation levels provide stronger consistency but can reduce concurrency.

IDbContextTransaction Methods

The IDbContextTransaction interface provides the following key methods:

Advanced Scenarios

Distributed Transactions

For scenarios involving multiple databases or external resource managers, you might need distributed transactions. EF Core can integrate with the .NET TransactionScope class to participate in distributed transactions.


using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeAsyncFlowOption.Enabled))
{
    using (var context1 = new DbContext1())
    {
        // Perform operations using context1
        context1.SaveChanges();
    }

    using (var context2 = new DbContext2())
    {
        // Perform operations using context2
        context2.SaveChanges();
    }

    // If everything succeeds, complete the transaction scope
    scope.Complete();
}
        
Important: Distributed transactions can have performance implications and require careful configuration of your database systems and application environment (e.g., MSDTC service).

Transactions and Multiple DbContext Instances

If you need to perform operations that span multiple DbContext instances within a single logical transaction, you must use explicit transaction management. Each DbContext instance needs to be configured to use the same underlying transaction.


using (var context1 = new YourDbContext())
using (var context2 = new YourDbContext())
{
    using (var transaction = context1.Database.BeginTransaction())
    {
        try
        {
            // Associate context2 with the same transaction
            context2.Database.UseTransaction(transaction.GetDbTransaction());

            // Perform operations on context1
            var item1 = new Item { Name = "Widget" };
            context1.Items.Add(item1);
            context1.SaveChanges();

            // Perform operations on context2
            var item2 = new Item { Name = "Gadget" };
            context2.Items.Add(item2);
            context2.SaveChanges();

            transaction.Commit();
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }
}
        

Best Practices

Effective transaction management is a cornerstone of building robust and reliable data-driven applications with Entity Framework Core. By understanding and applying these principles, you can ensure data integrity and prevent data corruption.