EF 6暂停执行策略和重叠执行 - "不支持用户启动的交易"例外

时间:2017-03-24 12:28:39

标签: c# transactions entity-framework-6

我实施了EF 6执行策略的暂停,无论我需要使用分布式事务来避免"' SqlAzureExecutionStrategy'不支持用户发起的交易"例外,遵循以下示例:

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

然而,最近我在批量数据管理会话期间遇到了两次失败,导致最终获得上述异常。

如果我理解正确,给定的示例在全局级别启用/禁用执行策略,这意味着如果在多个线程上同时执行操作,则一个操作可以在另一个操作完成之前结束暂停。如果将.NET 4.6.1用于跨多个SQL Azure DB的事务,则效果可能最明显,这可能需要一段时间才能完成。

为了避免这种情况,我尝试创建一个全局事务计数器,它以线程安全的方式递增和递减,并且只有在没有待处理的事务时才解除挂起,例如:

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

然后:

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

虽然MSDN上给出的示例没有考虑到这一点,但我仍感到困惑。有没有我忽略的东西?

2 个答案:

答案 0 :(得分:2)

<强>更新

原则上CallContext是特定于线程的,所以无论如何你都不必担心这个场景中的多线程。 (见备注:https://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext(v=vs.110).aspx

遇到这种情况的同时,我试图得到类似的东西。

非常确定您的解决方案实际上并不是线程安全的。一个线程仍然可以进入,并在重置递减数字和将暂停设置为false之间将暂停设置为true,这意味着新线程不会将暂停设置为true并且将失败。

你需要一个ReaderWriterLock来确保阻止任何新的线程将暂停设置为true,同时你在reset方法中减少,检查并“甩掉”暂停。像:

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

答案 1 :(得分:0)

我得到了同样的错误:
" 'SqlAzureExecutionStrategy' does not support user initiated transactions"
我重写了这样的代码并且它有效:

var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
  {
    using (var dbContextTransaction = _context.Database.BeginTransaction())
    {
        // Your code here                    
        dbContextTransaction.Commit();
    }
    catch (Exception ex)
    {
        dbContextTransaction.Rollback();
        throw;
    }
  }
});