简体   繁体   English

C# NetCore 事务 null

[英]C# NetCore Transaction null

I have many classes all associated to a specific table.我有许多类都与特定表相关联。 On a higher level i have parent transactions that call various methods so the child methods must be able to work inside a transient transaction and often it is not known beforehand if a method at some stage will be part of a transaction or not since we try to keep them generic.在更高级别上,我有调用各种方法的父事务,因此子方法必须能够在瞬态事务中工作,并且通常事先不知道某个阶段的方法是否会成为事务的一部分,因为我们尝试保持通用。

Then in some cases we need Dapper queries eg to turn identity on / off.然后在某些情况下,我们需要 Dapper 查询,例如打开/关闭身份。 I understood that Dapper requires passing a Transaction as parameter otherwise it will not be enlisted in the transaction (turns out i was wrong see below).我知道 Dapper 需要传递一个事务作为参数,否则它将不会被纳入事务(结果我错了,见下文)。

The DbContext(Pooling) is set per "component/dll" so since a connection is only enlisted when it its opened inside a transaction a scope is used of context to ensure it is opened for this transaction. DbContext(Pooling) 是按“组件/dll”设置的,因此由于连接仅在其在事务中打开时才被登记,因此 scope 用于上下文以确保为该事务打开它。 Furthmore that helps when calling these same methods from eg HealthChecks who otherwise will complain about too many open connections when many of them call the same connections opened by services.此外,当从例如 HealthChecks 调用这些相同的方法时会有所帮助,否则当它们中的许多调用由服务打开的相同连接时,它们会抱怨打开的连接太多。 Have this scope in methods helps also with calling these methods in parallel work so that they are more nicely run in parallel threads.在方法中使用此 scope 也有助于在并行工作中调用这些方法,以便它们更好地在并行线程中运行。

In other words in this way these methods can be called from these parents which can be parallel job parents or singletons requiring a service scope or parent transactions that require a transients hierarchy.换句话说,以这种方式可以从这些父级调用这些方法,这些父级可以是并行作业父级或需要服务 scope 或需要瞬态层次结构的父事务的单例。

The problem was: For some reason transaction in the following transaction is always null.问题是:由于某种原因,以下事务中的事务始终是 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);
}

(which i took from here: Pass current transaction to DbCommand and here https://github.com/zzzprojects/Dapper.Transaction ) (我从这里获取: 将当前事务传递给 DbCommand和这里https://github.com/zzzprojects/Dapper.Transaction

UPDATE / SOLUTION:更新/解决方案:

Ok.好的。 So... when using transaction scope the transaction parameter does not have to be passed to Dapper to ensure it enlists in the transaction.所以...当使用事务 scope 时,不必将事务参数传递给 Dapper 以确保它参与事务。 That was the clue.这就是线索。

It's easier to explain what's wrong by showing what the code should be:通过显示代码应该是什么来更容易解释什么是错误的:

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

Where ExecuteAsync comes from Dapper. ExecuteAsync来自 Dapper。

There's no reason to create a transaction, much less a transaction scope, to execute a single command.没有理由创建事务,更不用说事务 scope 来执行单个命令。

There's no reason to create a DbContext just to open a connection to the database either, or to execute raw SQL commands.没有理由仅仅为了打开与数据库的连接或执行原始 SQL 命令而创建 DbContext。 DbContext isn't a database connection, it's job is to Map Objects to Relational data. DbContext 不是数据库连接,它的工作是将 Map 对象转换为关系数据。 There are no objects involved here.这里不涉及任何对象。

To execute multiple commands there's no reason to use multiple connections.要执行多个命令,没有理由使用多个连接。 Just execute the commands one after the other.只需一个接一个地执行命令。 If it's really necessary, use an explicit database transaction around those commands.如果确实有必要,请围绕这些命令使用显式数据库事务。 Or create the connection inside a single transaction scope.或者在单个事务 scope 中创建连接。

Let's say you have an array with those commands, eg something read from a script file:假设您有一个包含这些命令的数组,例如从脚本文件中读取的内容:

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

Doing the same thing using a TransactionScope only requires opening the connection inside the transaction scope.使用 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();
}

Remove this line删除此行

IDbContextTransaction transaction = localcontext.Database.CurrentTransaction;

If there's an active TrasnactionScope your SqlConnection will be automatically enlisted in it.如果有一个活动的 TrasnactionScope,您的 SqlConnection 将自动加入其中。 The whole point of TransactionScope is that your data access methods can be completely free of transaction handling. TransactionScope 的全部意义在于您的数据访问方法可以完全没有事务处理。 Then in some outer business layer or controller method, the transaction is orchestrated.然后在一些外层业务层或者controller方法中,对事务进行编排。

The reason CurrentTransaction is null is that there are two different ways to handle transactions. CurrentTransaction是 null 的原因是有两种不同的方式来处理事务。 If you want the current System.Transactions.Transaction, you get it with System.Transactions.Transaction.Current .如果您想要当前的 System.Transactions.Transaction,您可以使用System.Transactions.Transaction.Current获得它。

Stepping back, there are 3 separate ways to manage transactions with SqlConnection.退一步说,使用 SqlConnection 管理事务有 3 种不同的方法。

  1. TSQL Transactions : You can use TSQL API directly issuing BEGIN TRAN , COMMIT TRAN , etc. 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. 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. But enlisting TSQL commands in the current transaction is not optional, and never has been.但是在当前事务中加入 TSQL 命令不是可选的,而且从来都不是。 And that's a pain because methods that user SqlCommand may not know whether there is a transaction.这很痛苦,因为用户 SqlCommand 的方法可能不知道是否存在事务。 Dapper and EF both wrap this API in their transaction handling methods. Dapper 和 EF 都在他们的事务处理方法中包装了这个 API。

  3. System.Transactions Transactions : Partly because of this System.Transactions was introduced in .NET 2.0 as a new and unified way to handle transactions in .NET, and SqlClient added support for it. System.Transactions Transactions :部分原因是在 .NET 2.0 中引入了System.Transactions作为一种新的统一方式来处理 .NET 中的事务,并且 SqlClient 增加了对它的支持。 The main innovation of System.Transactions was adding "ambient" transactions. System.Transactions 的主要创新是添加“环境”事务。 So code could be agnositc about whether there's a transaction and the right thing will just happen.所以代码可能不知道是否有交易,正确的事情就会发生。 When opening a SqlConnection if there is a current Transaction, the SqlConnection will be enlisted in it, and the changes made using the SqlConnection will not be committed until the Transaction is committed.如果当前存在 Transaction,则打开 SqlConnection 时,SqlConnection 将被列入其中,并且在提交 Transaction 之前,不会提交使用 SqlConnection 所做的更改。 And there is no need for your ADO.NET code to know about the Transaction.您的 ADO.NET 代码无需了解交易。 Dapper and EF are both built on top of ADO.NET and SqlClient, so this all just works. Dapper 和 EF 都建立在 ADO.NET 和 SqlClient 之上,所以这一切都可以正常工作。

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

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