繁体   English   中英

C# NetCore 事务 null

[英]C# NetCore Transaction null

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

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

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

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

问题是:由于某种原因,以下事务中的事务始终是 null。

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);
}

(我从这里获取: 将当前事务传递给 DbCommand和这里https://github.com/zzzprojects/Dapper.Transaction

更新/解决方案:

好的。 所以...当使用事务 scope 时,不必将事务参数传递给 Dapper 以确保它参与事务。 这就是线索。

通过显示代码应该是什么来更容易解释什么是错误的:

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

ExecuteAsync来自 Dapper。

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

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

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

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

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 做同样的事情只需要打开事务 scope 内的连接。

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 的全部意义在于您的数据访问方法可以完全没有事务处理。 然后在一些外层业务层或者controller方法中,对事务进行编排。

CurrentTransaction是 null 的原因是有两种不同的方式来处理事务。 如果您想要当前的 System.Transactions.Transaction,您可以使用System.Transactions.Transaction.Current获得它。

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

  1. TSQL 事务:您可以使用 TSQL API 直接发出BEGIN TRANCOMMIT TRAN等。

  2. ADO.NET Transactions : SqlConnection.BeginTrasaction, IDbTransaction, SqlTransaction, etc. This is a wrapper over the TSQL API, and is a PITA because it introduces a useless requirement to pass the SqlTransaction to each SqlCommand that you want to enlist in the Transaction. 但是在当前事务中加入 TSQL 命令不是可选的,而且从来都不是。 这很痛苦,因为用户 SqlCommand 的方法可能不知道是否存在事务。 Dapper 和 EF 都在他们的事务处理方法中包装了这个 API。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM