.net core TransactionScope:具有不同数据库连接的嵌套事务(SQL Server和Postgresql)
我正在编写一个SDK方法,其中包含使用NpgsqlConnection的事务,供其他人使用 当他们调用我的方法时,他们使用SqlConnection和另一个事务来包装他们的数据库和我的SDK的数据库 如果我在没有事务的情况下设置SDK方法,那么外部代码很好,我的SDK方法可以回滚。(这也很奇怪。我还在想原因。) 但是,如果我使用事务设置SDK方法,则外部代码会因.net core TransactionScope:具有不同数据库连接的嵌套事务(SQL Server和Postgresql),.net-core,transactions,npgsql,sqlconnection,transactionscope,.net Core,Transactions,Npgsql,Sqlconnection,Transactionscope,我正在编写一个SDK方法,其中包含使用NpgsqlConnection的事务,供其他人使用 当他们调用我的方法时,他们使用SqlConnection和另一个事务来包装他们的数据库和我的SDK的数据库 如果我在没有事务的情况下设置SDK方法,那么外部代码很好,我的SDK方法可以回滚。(这也很奇怪。我还在想原因。) 但是,如果我使用事务设置SDK方法,则外部代码会因TransactionBortedException而崩溃: System.Transactions.TransactionAborte
TransactionBortedException
而崩溃:
System.Transactions.TransactionAbortedException : The transaction has aborted.
---- Npgsql.PostgresException : 55000: prepared transactions are disabled
目前,我们在SDK的连接字符串中使用inclist=false
,以防止内部事务加入外部事务,但我想知道这种行为背后的原因
下面是我重现问题的代码:
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
},
TransactionScopeAsyncFlowOption.Enabled))
{
await using (var conn = new SqlConnection(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"))
using (var cmd = new SqlCommand("insert into [Test].[dbo].[Test] (Id, \"Name\") values (1, 'A')", conn))
{
await conn.OpenAsync();
var result = await cmd.ExecuteNonQueryAsync();
await SdkMethodToDoStuffWithNpgsql(1);
scope.Complete();
}
}
我让sdkmethodToToStuffWithNPGSQL()
在注入Postgres上下文的存储库中模拟一个方法
public async Task SdkMethodToDoStuffWithNpgsql(long id)
{
var sqlScript = @"UPDATE test SET is_removal = TRUE WHERE is_removal = FALSE AND id = @id;
INSERT INTO log(id, data) SELECT id, data FROM log WHERE id = @id";
using (var scope = new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
},
TransactionScopeAsyncFlowOption.Enabled))
{
await using (var conn = new NpgsqlConnection(this._context.ConnectionString))
{
await conn.OpenAsync();
using (var cmd = new NpgsqlCommand(sqlScript, conn))
{
cmd.Parameters.Add(new NpgsqlParameter("id", NpgsqlDbType.Bigint) { Value = id });
await cmd.PrepareAsync();
var result = await cmd.ExecuteNonQueryAsync();
if (result != 2)
{
throw new InvalidOperationException("failed");
}
scope.Complete();
}
}
}
}
以上是预期的行为-在同一TransactionScope中登记两个连接会触发“分布式事务”;这在PostgreSQL术语中被称为“准备好的事务”,您必须在配置中启用它(这就是您在上面看到的错误的原因)。如果打算让两个单独的事务(一个用于SQL Server,一个用于PostgreSQL)分别提交,那么选择退出登记是正确的做法。您还应该能够使用Transact-OpScopeOption.Suppress
请注意,.NETCore当前不支持分布式事务,只有.NETFramework()支持分布式事务。因此,除非您使用的是.NET Framework,否则即使您在PostgreSQL中启用了准备好的事务,这也不会起作用。谢谢您的解释!但是如果我将TransactionScopeOption更改为Suppress(对于使用PostgreSQL的“内部”方法),它仍然会抛出您提到的异常。您知道是什么原因导致设置enlist=false和使用TransactionScopeOption.Suppress之间的差异吗?不应该是这种情况;我可以看到Npgsql在TransactionScope中使用TransactionScope选项时不会登记到任何事务。抑制-可能再次检查代码以确保。