29

I am trying to nest TransactionScopes (.net 4.0) as you would nest Transactions in SQL Server, however it looks like they operate differently. I want my child transactions to be able to rollback if they fail, but allow the parent transaction to decide whether to commit/rollback the whole operation. The problem is when the first complete occurs, the transaction is rolled back. I realize that complete is different to commit.

A greatly simplified example of what I am trying to do:

static void Main(string[] args)
{
    using(var scope = new TransactionScope()) // Trn A
    {
        // Insert Data A

        DoWork(true);
        DoWork(false);

        // Rollback or Commit
    }
}

// This class is a few layers down
static void DoWork(bool fail)
{
    using(var scope = new TransactionScope()) // Trn B
    {
        // Update Data A

        if(!fail)
        {
            scope.Complete();
        }
    }
}

I can't use the Suppress or RequiresNew options as Trn B relies on data inserted by Trn A. If I do use those options, Trn B is blocked by Trn A.

Any ideas how I would get it to work, or if it is even possible using the System.Transactions namespace?

Thanks

Robert Wagner
  • 17,515
  • 9
  • 56
  • 72

1 Answers1

51

You're probably not going to like this answer, but...

Voting inside a nested scope

Although a nested scope can join the ambient transaction of the root scope, calling Complete in the nested scope has no affect on the root scope. Only if all the scopes from the root scope down to the last nested scope vote to commit the transaction, will the transaction be committed.

(From Implementing an Implicit Transaction using Transaction Scope)

The TransactionScope class unfortunately doesn't provide any mechanism (that I know of) for segregating units of work. It's all or nothing. You can prevent any transaction from occurring on a specific unit of work by using TransactionScopeOption.Suppress, but that is probably not what you want, as you would then lose atomicity for whatever is inside that scope.

There is only one "ambient" transaction when you use a TransactionScope. Once a TransactionScope is disposed or gets collected without Complete being executed, the whole ambient transaction gets rolled back; that's it, game over.

In fact, SQL Server doesn't support true nested transactions at all, although it is possible (though somewhat unintuitive) to achieve the same end result with appropriate use of SAVE TRAN statements. Re-implementing this logic as a Stored Procedure (or several of them) might be your best option if you require this particular behaviour.

Community
  • 1
  • 1
Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • Thanks for that, I was afraid of an answer like this. – Robert Wagner Jun 10 '10 at 05:05
  • 1
    I did not get it: why the answer is disappointing? Robert wants that any inner scope be able to rollback, while only the outmost one should be able to commit. A simple solution will be: in each scope, call complete() if you don't want to rollback. Then the outmost scope has the option to commit, only if all inner ones choose not to rollback. – Alireza Feb 16 '13 at 10:57
  • 4
    @Alireza: The OP wants to selectively commit certain inner transactions while rolling back everything *else* which wasn't explicitly committed. Your "simple solution" is exactly how the `TransactionScope` works and is documented to work and clearly explained by this very answer, but it will not actually commit the changes made in any of the inner scopes if the outer scope does not complete. That's simply not possible using `System.Transactions`. – Aaronaught Feb 16 '13 at 16:43
  • 2
    Thanks, this explains it. Honestly, I still get the wrong meaning from Robert's question!! – Alireza Feb 17 '13 at 06:21