C# NetCore 事务空

C# NetCore Transaction null

我有很多 类 都与特定的 table 相关联。在更高层次上,我有调用各种方法的父事务,因此子方法必须能够在瞬态事务中工作,并且通常事先不知道某个阶段的方法是否会成为事务的一部分,因为我们尝试保持通用。

然后在某些情况下我们需要 Dapper 查询,例如打开/关闭身份。我知道 Dapper 需要传递一个交易作为参数,否则它不会被纳入交易(结果我错了,见下文)。

DbContext(Pooling) 是根据“component/dll”设置的,因此由于连接仅在其在事务中打开时才被征用,因此使用上下文范围来确保它为此事务打开。此外,这有助于从例如调用这些相同的方法。 HealthChecks 否则会抱怨打开的连接太多,因为它们中的许多调用服务打开的相同连接。在方法中使用此范围也有助于在并行工作中调用这些方法,以便它们在并行线程中更好地 运行。

换句话说,通过这种方式,可以从这些父级调用这些方法,这些父级可以是并行作业父级或需要服务范围的单例或需要瞬态层次结构的父事务。

问题是:出于某种原因,以下事务中的事务始终为空。

try {
using TransactionScope scope = new TransactionScope(TransactionScopeOption.Required,
            System.TimeSpan.FromMinutes(10), TransactionScopeAsyncFlowOption.Enabled);

using Context localcontext = new Context(new DbContextOptionsBuilder<Context>()
                .UseSqlServer(_options.ConnectionString).Options);
// just for safety:
localcontext.Database.GetDbConnection().Open(); 

 // the following line is only for dapper input:
IDbContextTransaction transaction = localcontext.Database.CurrentTransaction;

await localcontext.Database.GetDbConnection()
  .ExecuteAsync("SET IDENTITY_INSERT [dbo].[Whatever] ON",
                null, (System.Data.IDbTransaction)transaction);
}

(我从这里拿的: and here https://github.com/zzzprojects/Dapper.Transaction

更新/解决方案:

好的。所以......当使用事务范围时,不必将事务参数传递给 Dapper 以确保它在事务中登记。这就是线索。

通过显示代码应该是什么更容易解释问题所在:

using(var connection=new SqlConnection(_connectionString))
{
    await connection.ExecuteAsync("SET IDENTITY_INSERT [dbo].[Whatever] ON");
}

其中 ExecuteAsync 来自 Dapper。

没有理由创建事务,更不用说事务范围来执行单个命令了。

没有理由仅仅为了打开与数据库的连接或执行原始 SQL 命令而创建 DbContext。 DbContext 不是数据库连接,它的工作是将对象映射到关系数据。这里没有涉及对象。

要执行多个命令,没有理由使用多个连接。只需一个接一个地执行命令。如果确实有必要,请围绕这些命令使用显式数据库事务。或者在单个事务范围内创建连接。

假设您有一个包含这些命令的数组,例如从脚本文件中读取的内容:

string[] commands=new[]{...};
using(var connection=new SqlConnection(_connectionString))
{
    await connection.OpenAsync();

    using (var transaction = connection.BeginTransaction())
    {
        foreach(var sql in commands)
        {
            await connection.ExecuteAsync(sql,transaction:transaction);
        }
        transaction.Commit();
    }
}

使用 TransactionScope 做同样的事情只需要在事务范围内打开连接。

string[] commands=new[]{...};

using( var scope = new TransactionScope(TransactionScopeOption.Required,
            System.TimeSpan.FromMinutes(10), TransactionScopeAsyncFlowOption.Enabled)
using(var connection=new SqlConnection(_connectionString))
{
    await connection.OpenAsync();

    foreach(var sql in commands)
    {
        await connection.ExecuteAsync(sql);
    }
    scope.Complete();
}

删除此行

IDbContextTransaction transaction = localcontext.Database.CurrentTransaction;

如果有一个活动的 TrasnactionScope,您的 SqlConnection 将自动加入其中。 TransactionScope 的全部要点在于您的数据访问方法可以完全免费 事务处理。然后在一些外部业务层或控制器方法中,事务被编排。

CurrentTransaction 为空的原因是有两种不同的方式来处理事务。如果你想要当前的 System.Transactions.Transaction,你可以用 System.Transactions.Transaction.Current.

得到它

退一步说,有 3 种不同的方法可以使用 SqlConnection 管理事务。

  1. TSQL Transactions: 可以使用TSQLAPI直接发出BEGIN TRAN,COMMIT TRAN

  2. ADO.NET 事务:SqlConnection.BeginTrasaction、IDbTransaction、SqlTransaction 等。这是对 TSQL API,并且是 PITA,因为它 引入了 将 SqlTransaction 传递给要在事务中登记的每个 SqlCommand 的无用要求。但是在当前事务中加入 TSQL 命令不是可选的,而且从来都不是。这很痛苦,因为用户 SqlCommand 的方法可能不知道是否存在事务。 Dapper 和 EF 都将此 API 包装在它们的事务处理方法中。

  3. System.Transactions Transactions:部分原因是 System.Transactions 在 .NET 2.0 中被引入作为一种新的统一方式来处理.NET 中的事务,并且 SqlClient 添加了对它的支持。 System.Transactions 的主要创新是添加“环境”交易。因此,代码可能无法判断是否存在交易,而正确的事情只会发生。当打开一个SqlConnection时,如果有一个当前的Transaction,SqlConnection将被登记在其中,并且在提交Transaction之前不会提交使用SqlConnection所做的更改。并且您的 ADO.NET 代码无需了解交易。 Dapper 和 EF 都构建在 ADO.NET 和 SqlClient 之上,因此一切正常。