使用TransactionScope和EF6的异步调用出现问题

4
我有以下代码,旨在将大块的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();
}

在这个阶段,我开始聚合各种有趣的错误:

该操作对于事务状态无效。

底层提供程序在打开时失败。

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

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

我不明白的是为什么引入TransactionScope会创建这么多问题。我假设我对异步调用与EF的交互以及TransactionScope如何包装这些调用有基本的误解,但我弄不清楚。而且我真的不知道MSDTC异常涉及什么。

对于如何在对同一数据库进行异步调用时具有回滚功能,您有什么想法吗?有没有更好的处理这种情况的方法?

更新: 在查看此处的文档(链接)后,我发现Database.BeginTransaction()是首选的EF调用。然而,这假定所有更改都将在同一个上下文中发生,但实际情况并非如此。除了创建虚拟上下文并传递事务之外,我认为这不能解决我的问题。

2个回答

3

这与异步无关。您正在使用多个连接进行写入,并希望这是原子性的。这需要分布式事务。没有其他选择。

您也可能会遇到分布式死锁,只有通过超时才能解决。

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

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


感谢您的建议。我们办公室没有专门的数据库程序员,所以所有这些问题对我来说都是新的。我猜TransactionScope是一个误导?似乎多个上下文调用在没有它的情况下也能正常工作,但SQL Server必须在幕后处理它们。我将使用一个连接运行一些测试,并查看效果如何。最坏的情况下,我只插入了大约10,000行,因此我认为处理时间> 60秒是不合理的。 - MadHenchbot
1
不,这里没有使用TransactionScope是不正确的。没有它,更改就不是原子性的。虽然使用它可以使更改具有原子性,但需要MSDTC(由于几个原因最好避免使用)。 - usr
我最多只插入大约10,000行数据,所以我认为处理时间超过60秒是不合理的。如果您希望解决此问题,请提出一个新的问题并在此处留下链接。使用SQL Profiler捕获执行的SQL并发布实际的执行计划。 - usr

1

这怎么样?

这将只创建一个上下文,即将实体附加到上下文中。 查看实体框架批量插入

如果插入过程中出现任何问题,则整个事务将被回滚。如果您想要更多的事务模式,请实现工作单元模式

据我所知,实体框架本身具有工作单元模式。

 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
        }

    }

谢谢您的建议!我会研究一下工作单元模式并尝试一下。 - MadHenchbot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接