.net core TransactionScope:具有不同数据库连接的嵌套事务(SQL Server和Postgresql)

.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

我正在编写一个SDK方法,其中包含使用NpgsqlConnection的事务,供其他人使用

当他们调用我的方法时,他们使用SqlConnection和另一个事务来包装他们的数据库和我的SDK的数据库

如果我在没有事务的情况下设置SDK方法,那么外部代码很好,我的SDK方法可以回滚。(这也很奇怪。我还在想原因。)

但是,如果我使用事务设置SDK方法,则外部代码会因
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选项时不会登记到任何事务。抑制-可能再次检查代码以确保。