views:

1131

answers:

2

We are using Entity Framework and running unit tests within a transaction scope. We were origianally getting the error in the title.

I have managed to isolate the problem some what.

        using (TransactionScope scope1 = new TransactionScope())
        {

            using (TransactionScope scope2 = new TransactionScope())
            {
                   // Here there is no code
            }

            using (Entities se = new Entities())
            {
                EntityConnection entityConnection = (EntityConnection)se.Connection;
                DbConnection storeConnection = entityConnection.StoreConnection;

                storeConnection.Open(); // On this line the error occurs

                   // Some code that runs a stored procedure
            }

        }

The error that we are currently getting is "The operation is not valid for the state of the transaction.."

If I remove transaction scope2, everything works fine.

If I mark scope 2 as an ambient transaction it also works fine.

+2  A: 

You are creating scope2 without an explicit TransactionScopeOption parameter, which yields a default of TransactionScopeOption.Required, see section Remarks within TransactionScope Constructor

This constructor creates a new transaction scope with the transaction scope option equal to Required. This means that a transaction is required by the new scope and the ambient transaction is used if one already exists. Otherwise, it creates a new transaction before entering the scope.

In your example an ambient TransactionScope does indeed exist already (scope1), consequently the new nested TransactionScope (scope2) with implicit parameter TransactionScopeOption.Required is using the existing ambient transaction rather than creating a new transaction itself.

However, the implicit transaction semantics of scope2 are still in place, consequently the existing ambient transaction created by scope1 is getting aborted because you are not calling Complete at the end of scope2:

Failing to call this method aborts the transaction, because the transaction manager interprets this as a system failure, or equivalent to an exception thrown within the scope of transaction

Of course the problem goes aways immediately if you remove scope2 or change its semantics to TransactionScopeOption.RequiresNew (meaning 'A new transaction is always created for the scope.'), because the existing ambient transaction created by scope1 will not be affected anymore.

See Implementing an Implicit Transaction using Transaction Scope for more details on this.

Steffen Opel
Thanks, that fixed it, I was missing the scope2.complete()
Shiraz Bhaiji
A: 

How about this syntax, this is pretty similar , Scope2 is Completed and Disposed of later.

I'm also intermittently seeing the "cannot access a disposed of object 'Transaction' error. Could it be because I should have created both of these with TransactionScopeOption.RequiresNew instead of TransactionScopeOption.Required?

 TransactionOptions rootOptions = new TransactionOptions();
    rootOptions.IsolationLevel = IsolationLevel.ReadCommitted;
    OtherObject.Scope2 = new TransactionScope(TransactionScopeOption.Required, rootOptions);

    TransactionOptions options = new TransactionOptions();
    options.IsolationLevel = IsolationLevel.ReadCommitted;
    options.Timeout = getTransactionTimeout();
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options))
          {

                                .............
           scope.Complete();
          }
learnerplates