Exception in inner TransactionScope is causing all subsequent inner TransactionScopes to throw TransactionAbortedException

StackOverflow https://stackoverflow.com/questions/19322310

  •  30-06-2022
  •  | 
  •  

Question

I've got an application where I'm wanting to put several database saves into a single transaction. If any of them fail I want to roll the whole thing back. However, I want to know which of them fail (or succeed) before rolling back the transaction.

I've got an outer TransactionScope with an inner loop where each iteration of the loop has its own TransactionScope. I want to run all of them and figure out which ones failed.

For example, if I have 5 things I'd like to try and save, but the first and third would fail, I'd like to know that. That requires that I try all 5 saves and if one fails then roll the whole thing back, but only after all 5 have tried.

What I'm seeing though, is that after the first failing transaction all subsequent uses of TransactionScope are immediately throwing their own TransactionAbortedException and not letting me attempt the save to see if it works.

Here's an example:

using (var scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead}, EnterpriseServicesInteropOption.Full))
{
    var outputStatus = new List<string>();

    for (int i = 0 ; i < 5 ; i++)
    {
        try
        {
            using (var innerScope = new System.Transactions.TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead}, EnterpriseServicesInteropOption.Full))
            {
                // Do work here that causes an exception on first iteration only
                if (i == 0)
                {
                    throw new Exception(string.Format("Iteration {0} has FAILED", i));
                }
                else
                {
                    outputStatus.Add("SUCCESS");
                }
            }
        }
        catch (Exception e)
        {
            outputStatus.Add("ERROR, " + e.Message);
        }
    }

    // Print out outputStatus values here
}

At the end of this code the outputStatus collection looks like this:

  • ERROR, Iteration 0 has FAILED
  • ERROR, The transaction has aborted.
  • ERROR, The transaction has aborted.
  • ERROR, The transaction has aborted.
  • ERROR, The transaction has aborted.

After the first exception, none of the rest are able to get to the succeed statement.

Is there a way to run all inner transactions within the outer transaction scope and allow me to control the rollback of the outer transaction scope?

UPDATE:

In the real code that this sample mimics I cannot make changes to the code that contains the inner TransactionScope. It's in an object that's out of my control. So a solution I'm looking for would need to be able to work with the inner transaction throwing exceptions.

Was it helpful?

Solution

After trying to emulate this myself I found that you can't actually do it this way or with what I proposed originally. If you in anyway relate the transaction scopes and one of them doesn't complete correctly subsequent calls to the constructor would simply result in an exception and abort. If you try to manually change them or nest them without relating them then upon completion Dispose(). An exception will be thrown saying you either nested incorrectly or that the Transaction.Current has changed inside the scope.

It seems to me that you have to chose between having an atomic Transaction, or trying all those things independently, and check for which one it fails and correct after.

Last I found (by using JetBrains dotPeek) that transactions have thread affinity. You might be able to manage your 5 calls by doing them on different threads. For sure you will have to use some sort of barrier http://en.wikipedia.org/wiki/Synchronous_rendezvous , to prevent any thread to complete until all of them are finished. If they are sequential, you will have to use an additional synchronization construct to have them execute in order.

Bear in mind that this will not be atomic, after you decide that you want to complete all transactions, they might still go wrong! They are after all independent. You might get locked if you are not careful, depending of what your real work is supposed to do. Also this might not play very well if your resources are scatter across different machines or databases, this would increase the likelihood that your app issues a complete but the remote resource decides otherwise.

Original Answer:

You should catch your exception before the end of the inner (loop) using.

Reading on (Remarks) : http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx

If no exception occurs within the transaction scope (that is, between the initialization of the TransactionScope object and the calling of its Dispose method), then the transaction in which the scope participates is allowed to proceed. If an exception does occur within the transaction scope, the transaction in which it participates will be rolled back.

I recomend you to read this article as well : http://msdn.microsoft.com/en-us/library/ms172152.aspx

Suppress is useful when you want to preserve the operations performed by the code section, and do not want to abort the ambient transaction if the operations fail. For example, when you want to perform logging or audit operations, or when you want to publish events to subscribers regardless of whether your ambient transaction commits or aborts. This value allows you to have a non-transactional code section inside a transaction scope, as shown in the following example.

Added: I kept on reading on msdn and I think you might be able to do this if you create another level of transactions. My reasoning is :

  • Your transaction is failing because scope (outermost) which you control is the root transaction.
  • The code you don't control does ask for a transaction but not for a New one (Argument TransactionScopeOption.Required.) this means that Inside that library outer is the transaction at play and by failing evertything else will fail.
  • To prevent this from happening you could create another scope before losing control to outside code. but make sure you ask for a RequiredNew scope. This would isolate the code you don't control and give you a chance to catch that exception.

My revised solution would be something like.

  using (var scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required))
  {
var outputStatus = new List<string>();

for (int i = 0 ; i < 5 ; i++)
{
   //Note RequiredNew, rest of the arguments suppressed 
    using (var innerScope = new System.Transactions.TransactionScope(TransactionScopeOption.RequiredNew))
    {
        try
        {

            // Do work here that causes an exception on first iteration only <-- is this really the case or is just an example, if so could you skip the first one?
            SomeService.DoSOmetaskWhichUsesATransactionInsideOfIt(i);
            outputStatus.Add("SUCCESS : " + i );
                            innerScope.Complete();

        }
        catch (Exception e)
        {
            outputStatus.Add("ERROR, "  + i + "   " + e.Message);
        }
    }

}
// IN here you must inspect outputStatus and decide if you want to complete the transaction (all of it , or the parts that didn't fail) or not. 
if(/* all good */) {
    scope.Complete();
}
// Print out outputStatus values here
}

If this doesn't suit for example you needs you might need to look into more advanced transaction topics and do it explicitly. I recommend you read : http://msdn.microsoft.com/en-us/library/ms172146.aspx This is beyond my understanding of transactions, so I am not too sure how you would apply it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top