使用EF6进行TransactionScope和异步调用时出现问题

时间:2015-11-09 23:01:25

标签: c# entity-framework asynchronous transactions rollback

我有以下代码,旨在将批量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();
}

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

  

该操作对交易状态无效。

     

底层提供程序在Open上失败。

     

分布式事务管理器(MSDTC)的网络访问已被禁用。

     

事务管理器已禁用其对远程/网络事务的支持。

我不明白为什么引入TransactionScope会产生很多问题。我假设我对异步调用如何与EF交互以及TransactionScope如何包装这些调用有一个基本的误解,但我无法弄清楚。而且我真的不知道MSDTC例外的含义。

有关如何使用对同一数据库进行异步调用的回滚功能的任何想法?有没有更好的方法来处理这种情况?

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

2 个答案:

答案 0 :(得分:3)

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

您也可能以这种方式遇到分布式死锁,只能通过超时解决。

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

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

答案 1 :(得分:1)

这个怎么样

  

这只会创建一个上下文,即上下文attach entities。   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
        }

    }