EF 6 暂停执行策略和重叠执行 - “不支持用户发起的交易”异常

bgx*_*bgx 5 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); 
            } 
        } 
    } 
Run Code Online (Sandbox Code Playgroud)

进而:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管 MSDN 上给出的示例没有考虑到这一点,但我仍然感到困惑。有什么我忽略的吗?

小智 5

我得到了同样的错误:

“SqlAzureExecutionStrategy”不支持用户启动的事务

我像这样重写了我的代码并且它有效:

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


Mat*_*een 2

更新:

事实证明 CallContext 无论如何都是特定于线程的,因此您不必担心这种情况下的多线程。(参见备注:https://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext (v=vs.110).aspx )


当我试图获得类似工作的东西时遇到了这个。

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

您需要 ReaderWriterLock 来确保在重置方法中减少、检查和“假”暂停时阻止任何将暂停设置为 true 的新线程。喜欢:

   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();
    }
Run Code Online (Sandbox Code Playgroud)