我有以下代码,旨在将批量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调用。但是,这假设我的所有更改都将在同一个上下文中发生,而不会发生。如果没有创建虚拟上下文并传递事务,我不相信这会解决我的问题。
答案 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
}
}