EF 6 suspend Execution Strategy and overlapping executions - "does not support user initiated transactions" exception

c# entity-framework-6 transactions

Question

I implemented suspension of the EF 6 execution strategy wherever I need to use distributed transaction to avoid "'SqlAzureExecutionStrategy' does not support user initiated transactions" exceptions, following these examples:

https://romiller.com/2013/08/19/ef6-suspendable-execution-strategy/ https://msdn.microsoft.com/en-us/library/dn307226(v=vs.113).aspx

However, recently I got two failures of this during a bulk data management session, resulting in getting the above exception after all.

If I understand it correctly, the given examples enable/disable the execution strategy on a global level, which would mean that if actions are executed simultaneously on multiple threads one action can end the suspension before another one is completed. The effect is probably most noticeable if using .NET 4.6.1 for transactions spanning multiple SQL Azure DBs, which can take a while to complete.

To avoid this I resorted to creating a global transaction counter, which is incremented and decremented in a thread safe way, and lifting the suspension only if there are no transactions pending anymore, like:

    public class MyConfiguration : DbConfiguration 
    { 
        public MyConfiguration() 
        { 
            this.SetExecutionStrategy("System.Data.SqlClient", () => SuspendExecutionStrategy 
              ? (IDbExecutionStrategy)new DefaultExecutionStrategy() 
              : new SqlAzureExecutionStrategy()); 
        } 

        public static bool SuspendExecutionStrategy 
        { 
            get 
            { 
                return (bool?)CallContext.LogicalGetData("SuspendExecutionStrategy") ?? false; 
            } 
            set 
            { 
                CallContext.LogicalSetData("SuspendExecutionStrategy", value); 
            } 
        } 
    } 

And then:

public class ExecutionHelper
{
    private static int _pendingTransactions;

    public void ExecuteUsingTransaction(Action action)
    {
        SuspendExeutionStrategy();
        try
        {
            using (var transaction = new TransactionScope())
            {
                action();
                transaction.Complete();
            }
            ResetSuspension();
        }
        catch (Exception ex)
        {
            ResetSuspension();
            throw ex;
        }
    }

    private void SuspendExeutionStrategy()
    {
        Interlocked.Increment(ref _pendingTransactions);           
        MyConfiguration.SuspendExecutionStrategy = true;
    }


    private void ResetSuspension()
    {
        Interlocked.Decrement(ref _pendingTransactions);
        if (_pendingTransactions < 1 )
        {
            MyConfiguration.SuspendExecutionStrategy = false;
        }
    }
}

I am still puzzled though that the example given on MSDN does not take this into account. Is there something I overlooked?

1
3
3/27/2017 7:30:34 AM

Popular Answer

Update:

Turns out the CallContext is thread specific anyway, so you don't have to worry about multi-threading in this scenario anyway. (see remarks: https://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext(v=vs.110).aspx)


Came across this whilst i was trying to get something similar to work.

Pretty sure your solution there isn't actually thread safe. A thread can still come in and set the suspension to true between the reset decrementing the number and setting the suspension to false, meaning the new thread wouldn't have the suspension set to true and would fail.

You would need a ReaderWriterLock to ensure you blocked any new threads setting the suspension to true whilst you decremented, checked and "falsed" the suspension in the reset method. Like:

   private ReaderWriterLock _readerWriterLock = new ReaderWriterLock();

    private void SuspendExeutionStrategy()
    {
        _readerWriterLock.AcquireReaderLock(_timeout);
        Interlocked.Increment(ref _pendingTransactions);
        MyConfiguration.SuspendExecutionStrategy = true;
        _readerWriterLock.ReleaseReaderLock();
    }


    private void ResetSuspension()
    {
        _readerWriterLock.AcquireWriterLock(_timeout);
        Interlocked.Decrement(ref _pendingTransactions);
        if (_pendingTransactions < 1)
        {
            MyConfiguration.SuspendExecutionStrategy = false;
        }
        _readerWriterLock.ReleaseWriterLock();
    }
2
11/15/2017 4:47:16 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow