4

在 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 可以运行之前,其他一些不相关的线程从由于 .NET Core(或 .NET 5+)不支持分布式事务,已打开事务的连接池尝试运行查询并因此引发 PlatformNotSupportedException?

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

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

4

1 回答 1

1

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

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

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

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

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

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

于 2021-08-29T15:21:10.180 回答