Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested transaction scopes constructed with object initialization results in error

In my C# code I am using nested transaction scopes. I have a utility class that creates TransactionScope objects identically. Both the outer scope and the inner scope are constructed in exactly the same way.

If I construct the TransactionScope objects like the first example below, the nested transaction scopes play well together:

public static TransactionScope CreateTransactionScope()
{
   var transactionOptions = new TransactionOptions();
   transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
   transactionOptions.Timeout = TransactionManager.MaximumTimeout;
   return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
}

However, I get an exception if I construct the TransactionScope objects like this:

public static TransactionScope CreateTransactionScope()
{
   var transactionOptions = new TransactionOptions
   {
      IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
      Timeout = TransactionManager.MaximumTimeout
   };
   return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
}

The error reads: "The transaction specified for TransactionScope has a different IsolationLevel than the value requested for the scope. Parameter name: transactionOptions.IsolationLevel".

Can anyone explain to me why using object initialization results in this behavior?

like image 809
Doodly Studmuffin Avatar asked Oct 04 '22 19:10

Doodly Studmuffin


2 Answers

This error occur when the outer transaction Isolation Level is different from the one you want to assign to the transaction scope. This means that calling your method and trying to assign the Isolation you want, the transaction manager detects that an existing transaction with a different isolation level exists, and throws an exception.

var transactionOptions = new TransactionOptions
{
   IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
   Timeout = TransactionManager.MaximumTimeout
};

return new TransactionScope(TransactionScopeOption.RequiredNew,TransactionOptions);

RequireNew forces to create a new inner transaction. I suggest to make this dynamic checking which is the current isolation with something similar to this:

if (Transaction.Current != null && Transaction.Current.IsolationLevel != myIsolationLevel)
{
   scopeOption = TransactionScopeOption.RequiresNew;
}

EDIT: As suggested in comments, is worth to mention that RequiresNew, does indeed create a new transaction and as by nature this is Isolated from the outer transaction. I found a very nice reading which explains very well how transactions works in here: https://www.codeproject.com/articles/690136/all-about-transactionscope

like image 92
Norcino Avatar answered Oct 10 '22 02:10

Norcino


Are you absolutely sure that only swapping out above methods lead to the exception? They should be functionally 100% equivalent.

Just dry-running both variants is fine:

using (var aa1 = CreateTransactionScopeGood())
    using (var aa2 = CreateTransactionScopeGood())
        Console.WriteLine("this will be printed");

using (var aa1 = CreateTransactionScopeBad())
    using (var aa2 = CreateTransactionScopeBad())
        Console.WriteLine("this will be printed");

Could you provide a way to reproduce it?

I can however reproduce your exception only when mixing different IsolationScopes in the same transaction, and that should indeed result in an exception:

using (new TransactionScope(TransactionScopeOption.Required, new
        TransactionOptions { IsolationLevel = IsolationLevel.Chaos }))
    using (new TransactionScope(TransactionScopeOption.Required, new
            TransactionOptions { IsolationLevel = IsolationLevel.Serializable }))
        Console.WriteLine("this will not be printed");
like image 36
Evgeniy Berezovsky Avatar answered Oct 10 '22 01:10

Evgeniy Berezovsky