我的方法类似于:
public async Task SaveItemsAsync(IEnumerable<MyItem> items)
{
using (var ts = new TransactionScope())
{
foreach (var item in items)
{
await _repository.SaveItemAsync(item);
}
await _repository.DoSomethingElse();
ts.Complete();
}
}
这当然有问题,因为TransactionScope
与async / await没什么关系。
它失败并显示InvalidOperationException
消息:
&#34; TransactionScope必须放在与其创建的同一个线程上。&#34;
我在TransactionScopeAsyncFlowOption
中读到了this answer,这似乎正是我所需要的。
但是,对于这个特定的项目,我很难支持.Net 4.0,无法升级到4.5或4.5.1。因此,我的项目中的异步/等待行为由Microsoft.Bcl.Async NuGet Package提供。
我似乎无法在此OOB package中找到TransactionScopeAsyncFlowOption
。我在某处错过了吗?
如果没有,是否有其他方法可以达到相同的效果?也就是说 - 我希望事务范围能够正确地完成或回滚,尽管跨越线程有延续。
我在上面的示例中添加了DoSomethingElse
来说明在事务范围内可能有多个调用,因此在一次调用中简单地将所有项目传递给数据库是不可行的选择。
如果重要,存储库使用直接ADO.Net(SqlConnection
,SqlCommand
等)来写入SQL Server。
更新
我以为我有一个解决方案,涉及从.Net 4.5.1中获取System.Transactions.dll并将其包含在我的项目中。但是,我发现这只适用于我的开发盒,因为它已经安装了4.5.1。部署到仅具有.Net 4.0的计算机时,它不起作用。它只给了MissingMethodException
。我正在寻找适用于.Net 4.0安装的解决方案。
答案 0 :(得分:19)
您必须使用TransactionScopeAsyncFlowOption.Enabled
public static TransactionScope CreateAsyncTransactionScope(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
var transactionOptions = new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TransactionManager.MaximumTimeout
};
return new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled);
}
答案 1 :(得分:2)
TransactionScope在框架4.5.1中已得到解决,以处理异步/等待操作。不要与4.5一起使用!!!
使用EF6和DbContextTransaction作为替代。
using (Entities entities = new Entities())
using (DbContextTransaction scope = entities.Database.BeginTransaction())
{
entities.Database.ExecuteSqlCommand("SELECT TOP 1 KeyColumn FROM MyTable)");
scope.Commit();
}
更多信息:
TransactionScope和Async / Await。与潮流合而为一! Daniel Marbach于2015年8月6日撰写 您可能不知道这一点,但是.NET Framework的4.5.0版本包含一个有关System.Transactions.TransactionScope及其在async / await中的行为的严重错误。由于存在此错误,因此TransactionScope无法进入异步继续。这可能会更改事务的线程上下文,从而导致在处置事务范围时引发异常。
这是一个大问题,因为它使编写涉及事务的异步代码极易出错。
好消息是,作为.NET Framework 4.5.1的一部分,Microsoft发布了该“异步延续”错误的修复程序。事实是,像我们这样的开发人员现在需要明确加入才能获得这种新行为。让我们看看如何做到这一点。
TL; DR
如果同时使用TransactionScope和async / await,则应立即真正升级到.NET 4.5.1。 TransactionScope包装的异步代码需要在其构造函数中指定TransactionScopeAsyncFlowOption.Enabled。
答案 2 :(得分:-3)
不确定这是否适合您的方案,但可以在ASP.NET应用程序中使用ConfigureAwait(false)
以确保等待的函数调用重新进入调用请求上下文。
因此,如果此代码在ASP.NET应用程序中运行,则使用以下代码:
await _repository.SaveItemAsync(item).ConfigureAwait(false);
确保在请求线程上继续执行。