Microsoft Docs

EF Core Transactions

This document provides a comprehensive guide to managing transactions in Entity Framework Core (EF Core). Understanding and correctly implementing transactions is crucial for maintaining data integrity and ensuring atomicity in your database operations.

EF Core simplifies transaction management through its DbContext and related APIs. You can control transactions explicitly or leverage automatic transaction management provided by EF Core.

Understanding Transactions

A database transaction is a sequence of operations performed as a single logical unit of work. The key properties of a transaction are:

  • Atomicity: All operations within a transaction are completed successfully, or none of them are.
  • Consistency: A transaction brings the database from one valid state to another.
  • Isolation: Concurrent transactions do not interfere with each other.
  • Durability: Once a transaction is committed, its changes are permanent, even in the event of system failures.

Explicit Transaction Management

For finer control over transactions, you can use the explicit transaction management APIs provided by EF Core.

Starting a Transaction

You can start a transaction using the BeginTransaction() method on the DbContext:


using (var context = new MyDbContext())
{
    using (var transaction = context.Database.BeginTransaction())
    {
        try
        {
            // Perform database operations here
            var user = new User { Name = "Alice" };
            context.Users.Add(user);
            context.SaveChanges();

            var order = new Order { UserId = user.Id, Amount = 100.0m };
            context.Orders.Add(order);
            context.SaveChanges();

            // If all operations are successful, commit the transaction
            transaction.Commit();
            Console.WriteLine("Transaction committed successfully.");
        }
        catch (Exception ex)
        {
            // If an error occurs, roll back the transaction
            transaction.Rollback();
            Console.WriteLine($"Transaction rolled back due to error: {ex.Message}");
            // Re-throw the exception if needed
            throw;
        }
    }
}
                

Transaction Options

The BeginTransaction() method accepts optional arguments to configure the transaction, such as the isolation level:


using (var context = new MyDbContext())
{
    var transactionOptions = new TransactionOptions
    {
        IsolationLevel = IsolationLevel.Serializable // Or ReadCommitted, RepeatableRead, etc.
    };
    using (var transaction = context.Database.BeginTransaction(transactionOptions))
    {
        // ... operations
        transaction.Commit();
    }
}
                

Committing and Rolling Back

  • transaction.Commit(): Finalizes the transaction, making all changes permanent.
  • transaction.Rollback(): Discards all changes made within the transaction.
Important: Always wrap your transaction operations in a try-catch block to ensure that you rollback the transaction in case of any errors. Using the using statement for the transaction ensures that Rollback() is called if Commit() is not reached.

Automatic Transaction Management

By default, when you call SaveChanges() multiple times within the same DbContext instance, EF Core will attempt to wrap these operations in a single database transaction. This is known as automatic transaction management.

Consider the following code:


var user = new User { Name = "Bob" };
context.Users.Add(user);

var order = new Order { UserId = user.Id, Amount = 250.0m };
context.Orders.Add(order);

// EF Core will attempt to save both changes within a single transaction
context.SaveChanges();
                
Tip: Automatic transaction management is convenient for simple scenarios. However, for complex operations or when you need specific control over isolation levels or transaction scopes, explicit transaction management is recommended.

Concurrency Conflicts

Transactions are essential for handling concurrency. When multiple users or processes try to modify the same data simultaneously, transactions help maintain data integrity.

EF Core offers various strategies for handling concurrency conflicts, which often work in conjunction with transactions.

Optimistic Concurrency

Optimistic concurrency assumes that conflicts are rare. EF Core detects conflicts during SaveChanges() and throws a DbUpdateConcurrencyException, which you can catch and handle.

Pessimistic Concurrency

Pessimistic concurrency locks data to prevent other users from modifying it. This is typically managed at the database level and can be influenced by transaction isolation levels.

Choosing the Right Isolation Level

The isolation level of a transaction determines how much one transaction is isolated from the effects of other concurrent transactions. Common isolation levels include:

Isolation Level Description
ReadUncommitted The lowest level. Transactions can read uncommitted data ("dirty reads").
ReadCommitted Transactions can only read data that has been committed. Prevents dirty reads, but can suffer from non-repeatable reads and phantom reads.
RepeatableRead Guarantees that if a transaction reads a row, subsequent reads of that same row will return the same data. Prevents dirty reads and non-repeatable reads, but can still suffer from phantom reads.
Serializable The highest level. Transactions are executed as if they were executed one after another ("serially"). Prevents dirty reads, non-repeatable reads, and phantom reads, but can significantly reduce concurrency.

The default isolation level varies by database provider but is often ReadCommitted.

Best Practices for EF Core Transactions

  • Keep Transactions Short: Long-running transactions can tie up database resources and increase the likelihood of deadlocks.
  • Handle Exceptions Gracefully: Always implement proper error handling and rollback mechanisms.
  • Understand Isolation Levels: Choose the isolation level that best balances data consistency requirements with concurrency needs.
  • Use using Statements: Ensure that transactions and contexts are properly disposed of.
  • Avoid Mixing Automatic and Explicit Management: If you start an explicit transaction, manage it fully. Don't rely on EF Core's automatic management within that explicit scope.