Search code examples
.net.net-4.0entity-framework-4sql-server-2008-r2savepoints

Entity Framework and SQL Server Savepoints


I asked a different question about Nested Transactions and the answer to my question educated me enough to help me realize I was asking the question poorly. So here's a better question.

How can I effectively implement SQL Server Savepoints (link 1, link 2) with a DAL built upon Entity Framework 4.0?

I would LIKE to write the following code and have it work in a way that SQL Server's SAVEPOINTS

public void Bar()
{
  using (var ts = new TransactionScope())
  {
    var ctx = new Context();
    DoSomeStuff(ctx);

    bool isSuccessful;

    using (var spA = new SavePoint("A")) // <-- this object doesn't really exist, I don't think
    {
      isSuccessful = DoSomeOtherStuff(ctx);
      if (isSuccessful)
        spA.Complete(); // else rollback bo prior to the beginning of this using block
    }

    Log(ctx, isSuccessful);

    ts.Complete();
  }
}

Is there such a way to do anything even close to resembling this, or something else that plays nicely with EF4? (we use custom self-tracking POCO entities)


Solution

  • This isn't quite a full answer, but I suspect something like this might be going down the right path. My problem is that I'm not entirely sure how to get a SqlTransaction while in a TransactionScope.

    /// <summary>
    /// Makes a code block transactional in a way that can be partially rolled-back. This class cannot be inherited.
    /// </summary>
    /// <remarks>
    /// This class makes use of SQL Server's SAVEPOINT feature, and requires an existing transaction.
    /// If using TransactionScope, utilize the DependentTransaction class to obtain the current Transaction that this class requires.
    /// </remarks>
    public sealed class TransactionSavePoint : IDisposable
    {
        public bool IsComplete { get; protected set; }
        internal SqlTransaction Transaction { get; set; }
        internal string SavePointName { get; set; }
    
        private readonly List<ConnectionState> _validConnectionStates = new List<ConnectionState>
                                                                            {
                                                                                ConnectionState.Open
                                                                            };
    
        public TransactionSavePoint(SqlTransaction transaction, string savePointName)
        {
            IsComplete = false;
            Transaction = transaction;
            SavePointName = savePointName;
    
            if (!_validConnectionStates.Contains(Transaction.Connection.State))
            {
                throw new ApplicationException("Invalid connection state: " + Transaction.Connection.State);
            }
    
            Transaction.Save(SavePointName);
        }
    
        /// <summary>
        /// Indicates that all operations within the savepoint are completed successfully.
        /// </summary>
        public void Complete()
        {
            IsComplete = true;
        }
    
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (!IsComplete)
            {
                Transaction.Rollback(SavePointName);
            }
        }
    }
    

    This would be consumed as such, very similarly to a TransactionScope:

    SqlTransaction myTransaction = Foo();
    
    using (var tsp = new TransactionSavePoint(myTransaction , "SavePointName"))
    {
      try
      {
        DoStuff();
        tsp.Complete
      }
      catch (Exception err)
      {
        LogError(err);
      }
    }