Entity Framework Transactions

Home / Entity Framework Transactions

Entity Framework uses the Unit of Work pattern. It’s useful, but it doesn’t quite cover all of the bases.

In using a repository pattern, I rely on EF to simply deal with UOW and have not had occasion to go beyond UOW. However, on a recent project, I found that I needed to manage my own transactions.


My use case was simple. Given a list of entities, I could potentially move a child collection on those entities to be associated with another new, or existing entity. Effectively, my transaction would follow this flow:

  • Iterate over list of entities
  • Select entity
  • Select other entity to move children (fk) to, or create new entity and get its fk
  • If new entity, Save it to get its PK
  • Set FK on children as appropriate
  • Save children

The problem with the way EF works out of the box is that when one calls DbContext.SaveChanges(), it’s also committing to the database. In my use case above, if a new parent entity was created, but for whatever reason, modifying the children fails, I need all of the children to be rolled back as well as rolling back the modified/created parent entity.

This necessitated being able to create a transaction and associating all of my DbContext’s SaveChanges with a single transaction. Fortunately, EF makes this pretty simple. The DbContext class has a “BeginTransaction” method that returns a DbContextTransaction that can be used to facilitate commits/rollbacks explicitly. Alternatively, the DbContext can be joined to an existing transaction.

For implementing this, I created a DbContext with a few helper methods. These methods allow Creating a Transaction, Rolling Back a Transaction, and Committing a Transaction. My IRepository is used to expose these methods without the need to access the DbContext directly. They’re effectively just wrapper methods, though, since my IRepository implementation utilizes the “BaseContext.”

using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;

public abstract class BaseContext : DbContext
{
    /// <summary>
    /// Set this through the Create transaction to allow committing transactions across multiple saves.
    /// </summary>
    private DbContextTransaction _dbTransaction;

    /// <summary>
    /// Helper method to return the ObjectContext of the DbContext.
    /// This is helpful for determining object states in our save/update dates
    /// </summary>
    public ObjectContext ObjectContext
    {
        get { return ((IObjectContextAdapter)this).ObjectContext; }
    }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="connectionStringName">Name of the connection string to retrieve from configuration</param>
    public BaseContext(string connectionStringName) : base(connectionStringName) { }

    public void CreateTransaction()
    {
        _dbTransaction = this.Database.BeginTransaction(System.Data.IsolationLevel.RepeatableRead);
    }

    public void CommitTransaction()
    {
        try
        {
            _dbTransaction.Commit();
            _dbTransaction.Dispose();
            _dbTransaction = null;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public void RollbackTransaciton()
    {
        if (_dbTransaction != null)
        {
            _dbTransaction.Rollback();
            _dbTransaction.Dispose();
            _dbTransaction = null;
        }
    }

    /// <summary>
    /// Save changes to the database
    /// </summary>
    /// <returns></returns>
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException dbEx)
        {
            throw dbEx;
        }
        catch (DbUpdateException dbUex)
        {
            throw dbUex;
        }
        catch (ConstraintException ce)
        {
            throw ce;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

There is one gotcha in this pattern that I intentionally left out as a talking point. Entity Framework’s object state manager is not rollback aware. That is to say, if the code is chugging along calling DbContext.SaveChanges() as needed, and then finally calling Repository.CommitTransaction(), it fails, and then Repository.RollbackTransaciton() is called, object state manager will presume that the objects are still in their Modified/Added/Deleted states. This means that despite rolling back, if SaveChanges were called again, EF would try to, once again, save our rolled back changes.

Fortunately, we can use EF’s DbContext’s ChangeTracker (or ObjectStateManager) to properly reset property values and object states on Rollback. This requires a slight modification to our RollbackTransaction method like so:

public void RollbackTransaciton()
{
    if (_dbTransaction != null)
    {
        _dbTransaction.Rollback();
        _dbTransaction.Dispose();
        _dbTransaction = null;
    }

    // Set the change states back - otherwise, previously rolledback changes will be committed later
    var changedEntries = this.ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged).ToList();

    // Reset changed values and set to Unchanged state
    foreach (var entry in changedEntries.Where(x => x.State == EntityState.Modified))
    {
        entry.CurrentValues.SetValues(entry.OriginalValues);
        entry.State = EntityState.Unchanged;
    }

    // Detach added entries
    foreach (var entry in changedEntries.Where(x => x.State == EntityState.Added))
    {
        entry.State = EntityState.Detached;
    }

    // Undeleted deleted entries
    foreach (var entry in changedEntries.Where(x => x.State == EntityState.Deleted))
    {
        entry.State = EntityState.Unchanged;
    }
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.