问题描述
我正在使用 NpgsqlConnection 编写一个带有事务的 SDK 方法供其他人使用。
当他们调用我的方法时,他们使用 sqlConnection 和另一个事务来包装他们的数据库内容和我的 SDK 的数据库内容。
如果我在没有事务的情况下设置我的 SDK 方法,则外部代码很好,我的 SDK 方法可以回滚。 (这也很奇怪。仍在弄清楚原因。)
如果我使用事务设置我的 SDK 方法,外部代码会因 TransactionAbortedException
崩溃:
System.Transactions.TransactionAbortedException : The transaction has aborted.
---- Npgsql.PostgresException : 55000: prepared transactions are disabled
目前我们在 SDK 的连接字符串中使用 enlist=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();
}
}
我使用 SdkMethodTodoStuffWithNpgsql()
来模拟存储库中注入 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)分别提交,那么选择退出登记是正确的做法。您还应该能够使用 TransactopScopeOption.Suppress。
请注意,.NET Core 目前不支持分布式事务,仅在 .NET Framework (see this issue) 中支持。因此,除非您使用 .NET Framework,否则即使您在 PostgreSQL 中启用准备好的事务,这也不起作用。