在 .NET Core/.NET 5+ 中使用 TransactionScope 和 SQL 连接池时如何避免 PlatformNotSupportedException

Posted

技术标签:

【中文标题】在 .NET Core/.NET 5+ 中使用 TransactionScope 和 SQL 连接池时如何避免 PlatformNotSupportedException【英文标题】:How to avoid PlatformNotSupportedException when using TransactionScope and SQL connection pooling in .NET Core/.NET 5+ 【发布时间】:2021-06-15 04:25:43 【问题描述】:

在 Web 服务中,我正在查询 SQL Server 2016 数据库。使用 .NET TransactionScope 如下在我的服务层中保留事务管理,但在我的数据层(“存储”类)代码中保留数据查询/命令,我们有几个地方遵循这种模式:

using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))

    bool needsInsert = await store1.Exists(request.id);
    if (needsInsert) mainRowsUpdatedCount = await store2.Insert(request);
    transaction.Complete();

这些“存储”方法中的每一个都遵循这种模式(使用 Dapper,虽然我怀疑这并不重要):

const string query = @"SELECT ...";                        // or INSERT or MERGE as the case may be
using IDbConnection connection = new SqlConnection(ConnectionString.Value);
return await connection.QueryAsync<T>(query, new  ... ); // or connection.ExecuteAsync as the case may be

这在大多数调用中都非常有效,但有时我会收到以下信息(尽管很少见):

System.PlatformNotSupportedException:此平台不支持分布式事务。

那么,在上面的例子中,store1.Exists 运行,获取连接,在事务中登记它,运行它的查询,关闭,然后有时在 store2.Insert 可以运行之前,其他一些不相关的线程从已打开事务的连接池获取相同的连接,尝试运行查询并因此引发 PlatformNotSupportedException,因为 .NET Core(或 .NET 5+)不支持分布式事务?

如果是这样,我怎样才能在不传递我的连接的情况下克服这个问题?

如果不是,还有什么可能导致此异常?

【问题讨论】:

尝试升级到分布式事务,也许?如果没有“支持”这种“支持”的 MSDTC,我会期待同样的结果。 “升级为分布式事务”是什么意思? 我想知道是否有一些 TS代码没有使用TransactionScopeAsyncFlowOption.Enabled?如果虔诚地使用RequiresNew,错误可能会“消失”,这将避免加入现有事务。 RequiresNew 可能正是我所需要的。谢谢,我试试看。 这方面有进展吗?我们遇到同样的问题,也很少,但仍然存在。 “RequiresNew”不是一个选项,因为我们想加入任何正在进行的外部交易,如果有的话。但是“RequiresNew”是否解决了您的问题? 【参考方案1】:

我遇到了同样的问题(也很少),最终得出结论,这很可能是 .NET Cores 基础库(SqlClient 或 Transaction)中的错误。

据我了解,.NET 事务和本地 SQL 事务之间是有区别的。创建TransactionScope 时,您实际上启动了一个始终可以通过Transaction.Current 访问的新.NET 事务。在新创建的 SqlConnection 上调用 .Open() 在内部检测此环境 .NET 事务并查询内部连接池(存储实际 SQL 连接的位置)以查找任何合适的现有连接(即same 连接字符串)已经与该环境 .NET 事务相关联。如果有一个可用(即空闲/空闲),它会直接使用那个而不升级到分布式事务。

因此,如果TransactionScope 中的所有代码确保始终在任何给定时间最多打开一个SqlConnection,则应保证相同的实际 SQL 连接和本地 SQL事务在后台被重用,不应尝试升级为分布式事务。

在您的示例中,您使用TransactionScopeAsyncFlowOption.Enabled 来确保通过异步代码流正确维护环境事务(即允许在范围内使用await)。只要你不打破这个流程,一切都会好起来的。

即使在应用程序的某处创建了另一个 TransactionScope(以及因此另一个 .NET 事务),TransactionScopeAsyncFlowOption.Suppress,在 您的 范围之外,也没有正确处理,并且它的关联线程恰好是在 your 范围内继续您的异步代码流的线程,那么不应该有任何混淆,因为您的下一个 SqlConnection 尝试成为泄露的 .NET 事务的一部分,或您的 .NET 事务。无论哪种方式,我都不希望升级。可能这种情况会在早些时候引发不同类型的异常(未测试)。

总而言之,我仍然相信这是深埋在 .NET 事务意大利面条代码中的一些错误。 ;) 我的问题是我只在生产中的极少数情况下遇到异常,我无法使用单元测试或其他东西重现它。

【讨论】:

以上是关于在 .NET Core/.NET 5+ 中使用 TransactionScope 和 SQL 连接池时如何避免 PlatformNotSupportedException的主要内容,如果未能解决你的问题,请参考以下文章

最全.NET Core .NET 5.NET 6和.NET 7简介和区别

[Asp.Net Core]NET5_Autofac控制器属性注入

[Asp.Net Core]NET5_Autofac控制器属性注入

[Asp.Net Core]NET5_Razor扩展01

[Asp.Net Core]NET5_Razor扩展01

[Asp.Net Core]NET5_ActionFilter的多种扩展