如何在Azure SQL数据库中使用嵌套的TransactionScopes

12

我目前正在尝试使用嵌套事务范围来访问Azure SQL数据库。

我正在使用以下代码(.Net 4.5.1,我的代码是全异步的,这是带有EF6.1的ASP.Net MVC):

public async Task Test()
{
    // In my actual code, the DbContext is injected within the constructor
    // of my ASP.Net MVC Controller (thanks to IoC and dependency injection)
    // The same DbContext instance is used for the whole HttpRequest
    var context = new TestContext();

    using (var t1 = StartTransactionForAsync())
    {
        using (var t2 = StartTransactionForAsync())
        {
            context.Users.Add(new User { Name = Guid.NewGuid().ToString() });
            await context.SaveChangesAsync();

            t2.Complete();
        }
        ... // Some more code here
        t1.Complete();
    }
}

private static TransactionScope StartTransactionForAsync()
{
    return new TransactionScope(
        TransactionScopeOption.Required,
        new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
        TransactionScopeAsyncFlowOption.Enabled);
}

除了有时TransactionScope会升级到MSDTC(这显然)不受Azure SQL Database支持之外,一切都很好。因此,有时我会遇到以下错误:

已禁用分布式事务管理器(MSDTC)的网络访问。请使用组件服务管理工具在MSDTC的安全配置中启用DTC以进行网络访问。

我可以在我的连接字符串中添加Enlist=False,但它会破坏上面的代码,因为即使外部TransactionScope被处理而没有Complete,内部事务仍然会插入到数据库中。

我针对一个单独的数据库,在整个HttpRequest中使用一个单一的Entity Framework上下文,并且始终使用相同的连接字符串

所以我的问题是:

  • Azure SQL Database是否支持嵌套事务?
  • 为什么上面的代码有时会升级到MSDTC?

官方文档如下所述:

  

Microsoft Azure SQL Database不支持分布式事务,即涉及多个资源的事务。有关更多信息,请参阅分布式事务(ADO.NET)。

     

从版本2.0开始,应用程序事务可以自动升级为分布式事务。这适用于使用System.Data.SqlClient类在System.Transactions事务上下文中执行数据库操作的应用程序。

   

当您在TransactionScope中打开到不同服务器或数据库的多个连接,或者使用EnlistTransaction方法将多个连接注册到System.Transactions对象时,会发生事务升级。当您同时在同一TransactionScope中或使用EnlistTransaction方法打开到同一服务器和数据库的多个并发连接时,也会发生事务升级。

   

从版本3.5开始,如果并发连接的连接字符串完全相同,则不会升级事务。有关事务和避免事务升级的更多信息,请参阅System.Transactions与SQL Server(Integration with SQL Server (ADO.NET))。

它没有回答我的任何问题。


TransactionScope是为了隐式/自动事务(利用MSDTC服务)而发明的,而不是为了嵌套事务。看起来你混淆了这些术语。升级到DTC可能是因为你有一些异步操作(这意味着不同的线程,可能意味着不同的连接)。我很难理解为什么你想在这种(服务器?)代码中放置异步操作-除了当前围绕“async”的流行趋势之外:-)。关于升级的更多信息,请参见:https://dev59.com/tuo6XIcBkEYKwwoYTzEw - Simon Mourier
我从未说过TS不支持嵌套事务。我说你混淆了两个概念。嵌套事务早在DTC出现之前就存在了。你可以在没有TS的情况下进行嵌套事务,但你所说的是嵌套作用域。如果在幕后,你正在对一个唯一的连接进行串行化(实际上可能更糟),那么异步在这里不会带来任何可扩展性的好处,我仍然怀疑。在我看来,你应该展示完整的重现代码以避免猜测。 - Simon Mourier
分布式事务 != 嵌套事务,首先为什么需要它?如果嵌套事务已经在事务中了,你可以继续在同一个事务中操作。你可以轻松地改变你的业务逻辑,加入现有的事务而不是创建新的事务,这就是环境事务的概念。 - Akash Kava
我曾经在有打开的读取器并创建新的事务范围时看到过这个错误。如果您使用 .ToListAsync() 来获取所有记录而不是枚举查询,这个错误就会消失。 - Akash Kava
如果您只是设置了 TransactionScopeOption.Required,那么它应该可以工作,尝试查找是否有 foreach 语句尝试查询某些内容,我遇到过类似的错误,当我将它们修改为 for each (var item in itemQuery.ToList()) 而不是 'foreach( var item in itemQuery)' 时,所有这样的嵌套事务错误都消失了。理想情况下,EF 应该检测并给出适当的错误,这是一个误导性的错误。如果仍然存在具有打开阅读器的活动查询,并且在发出 SaveChanges 时,这是 EF 抛出的常见错误。 - Akash Kava
显示剩余4条评论
3个回答

2

我曾经相信MARS已启用,但在仔细检查了我的连接字符串后发现它实际上并没有启用。除非对于Azure DB而言它是默认激活的,否则这可能真的是我的问题。 - ken2k
我时间不多了(赏金结束之前)无法完全测试是否解决了我的问题,但这确实是我的连接字符串有问题(我确定MARS已启用,但实际上并没有)。修复看起来很有希望,所以我会给你赏金+赞,但在我能够适当地测试一切之后,我会稍后接受这个答案。谢谢! - ken2k

1

MSDN: 当在单个事务中关闭并重新打开连接时,可能会将事务提升为 DTC。由于 Entity Framework 自动打开和关闭连接,因此应考虑手动打开和关闭连接以避免事务提升。

为避免这种情况:如何从对象上下文手动打开连接


0

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