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

c# entity-framework-6 transactions

Question

In order to prevent "'SqlAzureExecutionStrategy' does not allow user started transactions" problems, I implemented suspension of the EF 6 execution strategy anywhere I needed to utilise distributed transactions. For example:

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

Recently, however, I had two failures of this during a bulk data management session, which ultimately led to the aforementioned exception.

If I'm understanding the examples properly, one action may terminate the suspension before another one is finished if activities are run concurrently on several threads since the examples activate or disable the execution strategy globally. The impact is probably most apparent when using.NET 4.6.1 for lengthy transactions involving many SQL Azure DBs.

In order to prevent this, I turned to building a global transaction counter that is increased and decreased in a thread-safe manner and only lifting the suspension if there are no more outstanding transactions, like in:

    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); 
            } 
        } 
    } 

After that:

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'm still perplexed, however, by the fact that the MSDN example leaves this out. Is there anything I missed?

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

Popular Answer

Update:

You don't need to worry about multi-threading in this situation since it turns out that the CallContext is thread specific regardless. (Notes: https://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext(v=vs.110).aspx)


I stumbled onto this when attempting to make something similar function.

I'm quite sure that isn't a thread-safe solution of yours. Between the reset decrementing the number and setting the suspension to false, a thread may still enter and set the suspension to true. If this happens, the new thread won't have the suspension set to true and will fail.

To guarantee that you stopped any new threads while you decremented, checked, and "falsed" the suspension in the reset procedure, you would require a ReaderWriterLock. 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