TransactionScope 问题和 EF6 异步调用

Issue with TransactionScope and async calls with EF6

我有以下代码,旨在将大容量 EF 保存分成更小的块,表面上是为了提高性能。

var allTasks = arrayOfConfigLists
        .Select(configList =>
            Task.Run(() => SaveConfigurations(configList))
        .ToArray();

Task.WaitAll(allTasks);

每次调用 SaveConfigurations 都会创建一个运行完成的新上下文。

private static void SaveConfigurations(List<Configuration> configs)
{
    using (var dc = new ConfigContext())
    {
        dc.Configuration.AutoDetectChangesEnabled = false;
        dc.SaveConfigurations(configs);
    }
}

就目前而言,代码运行效率相对较高,考虑到这可能不是最佳的处理方式。但是,如果其中一个 SaveConfigurations 失败,我意识到我需要回滚保存到数据库中的任何其他配置。

经过一些研究,我将现有框架升级到 4.5.1 并利用新的 TransactionScopeAsyncFlowOption.Enabled 选项来处理异步调用。我做了以下更改:

using (var scope = 
    new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    //... allTasks code snippet from above
    scope.Complete();
}

至此,我开始汇总各种有趣的错误:

The operation is not valid for the state of the transaction.

The underlying provider failed on Open.

Network access for Distributed Transaction Manager (MSDTC) has been disabled.

The transaction manager has disabled its support for remote/network transactions.

我不明白为什么引入 TransactionScope 会产生这么多问题。我假设我对异步调用如何与 EF 交互以及 TransactionScope 如何包装这些调用存在根本性的误解,但我无法弄清楚。我真的不知道 MSDTC 异常与什么有关。

关于如何通过对同一数据库进行异步调用来实现回滚功能有什么想法吗?有没有更好的方法来处理这种情况?

更新: 查看文档 here 后,我发现 Database.BeginTransaction() 是首选的 EF 调用。但是,这假设我的所有更改都将发生在同一上下文中,而事实并非如此。如果没有创建虚拟上下文并传递事务,我认为这不能解决我的问题。

这与异步无关。您正在写多个连接并希望它是原子的。这就需要分布式事务。没有办法解决这个问题。

您也可能 运行 以这种方式陷入分布式死锁,只能通过超时来解决。

最好的方法可能是停止使用多个连接。如果性能如此重要,请考虑使用一种不涉及 EF 的众所周知的批量 DML 技术进行写入。

您可以使用 MARS 在同一连接上进行并发写入,但它们实际上是在服务器上串行执行的。由于流水线效应,这将提供一个小的加速。可能不值得麻烦

这个怎么样

This will only create one context i.e, and attach entities to the context. See entity framework bulk insertion

如果插入过程中出现任何问题,整个事务将被回滚。如果你想要更多的事务,比如模式实现 Unit of work pattern

据我所知Entity framework本身有工作单元模式。

 public SaveConfigurations(List<Configuration> configs)
    {
        try
        {

            using (var dc = new ConfigContext())
            {
               dc.Configuration.AutoDetectChangesEnabled = false;
               foreach(var singleConfig in configs)
               {
                 //Donot invoke dc.SaveChanges on the loop.
                 //Assuming the SaveConfiguration is your table.
                 //This will add the entity to DbSet<T> , Will not insert to Db until you invoke SaveChanges
                 dc.SaveConfiguration.Add(singleConfig);
               } 
               dc.Configuration.AutoDetectChangesEnabled = true;
               dc.SaveChanges();
            }

        }
        catch (Exception exception)
        {
           throw exception
        }

    }