EF Core - 多个上下文和事务

GET*_*Tah 7 c# entity-framework-core

我们将大型 DbContext 拆分为较小的上下文,每个上下文负责一个小的域边界上下文。上下文保存操作由工作单元编排,如下所示。

该域有两个有界上下文:合作伙伴和员工。工作单元管理两个 DbContext,PartnerContext并且EmployeeContext. 我们在事务中运行所有保存操作以确保操作是原子的。

github上提供了该问题的简化版本

public class UnitOfWork {

  public Task SaveChanges(){
      // EmployeeContext begins a transaction and shares it with other contexts
      var strategy = employeeContext.Database.CreateExecutionStrategy();
      return strategy.ExecuteAsync(async () =>
      {
        await using var transaction = await employeeContext.Database.BeginTransactionAsync();
        await partnerContext.Database.UseTransactionAsync(transaction.GetDbTransaction());
        await partnerContext.SaveChangesAsync();
        await employeeContext.SaveChangesAsync();
        await transaction.CommitAsync();
      });
  }
}
Run Code Online (Sandbox Code Playgroud)

下面的代码工作正常。更改全部在一个事务中执行

   var unitOfWork = new unitOfWork();
   ... perform updates to both contexts
   await unitOfWork.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

但是,当尝试第二次保存更改时,以下代码会抛出异常。

   var unitOfWork = new unitOfWork();
   ... perform updates to both contexts
   await unitOfWork.SaveChanges(); <-- Work fine
   ... doing a bit more work
   await unitOfWork.SaveChanges(); <-- Crashes
Run Code Online (Sandbox Code Playgroud)

上面的行失败并显示The connection is already in a transaction and cannot participate in another transaction.错误消息。

生成的 SQL 日志为:

**** The first save operation logs start here:
    SET NOCOUNT ON;
    INSERT INTO [Person] ([Discriminator], [ManagerId], [Name])
    VALUES (@p0, @p1, @p2);
    SELECT [Id]
    FROM [Person]
    WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
    Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Committing transaction.
    Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Disposing transaction.

**** The second save operation logs start here:
    
    Microsoft.EntityFrameworkCore.Database.Connection: Debug: Opening connection to database 'EF_DDD' on server 'localhost'.
    Microsoft.EntityFrameworkCore.Database.Connection: Debug: Opened connection to database 'EF_DDD' on server 'localhost'.
    Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Beginning transaction with isolation level 'Unspecified'.
    Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Began transaction with isolation level 'ReadCommitted'.
    Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Disposing transaction.
Run Code Online (Sandbox Code Playgroud)

unitOfWork.SaveChanges()尽管第一笔交易已提交并处理(正如您从上面的日志中看到的那样),有人知道第二笔抱怨开放交易背后的原因吗?

更新

我删除了所有异步代码和执行策略(重试)以缩小问题范围,代码现在如下所示:

    static void Main(string[] args)
    {
        var employeeContext = new EmployeeContext(ConnectionString);
        var partnersContext = new PartnersContext(employeeContext.Database.GetDbConnection());
        var unitOfWork = new UnitOfWork();
        unitOfWork.Update(employeeContext, partnersContext, 1);
        unitOfWork.Update(employeeContext, partnersContext, 2);

    }

    public class UnitOfWork
    {
        public void Update(EmployeeContext employeeContext, PartnersContext partnerContext, int count)
        {
            partnerContext.Partners.Add(new Partner($"John Smith {count}"));
            employeeContext.Persons.Add(new Person() { Name = $"Richard Keno {count}" });

            using var trans = employeeContext.Database.BeginTransaction();
            partnerContext.Database.UseTransaction(trans.GetDbTransaction());
            partnerContext.SaveChanges();
            employeeContext.SaveChanges();
            trans.Commit();
        }
    }
Run Code Online (Sandbox Code Playgroud)

第一个调用完成,数据库已更新,但第二个调用失败并出现以下错误。

在此输入图像描述

更新2

使用TransactionScope代替BeginTransaction似乎有效。以下代码有效并相应地更新数据库。

        var strategy = employeeContext.Database.CreateExecutionStrategy();
        await strategy.ExecuteAsync(async () =>
        {
            using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
            partnerContext.Partners.Add(new Partner($"John Smith {count}"));
            employeeContext.Persons.Add(new Person() { Name = $"Richard Keno {count}" });
            await partnerContext.SaveChangesAsync();
            await employeeContext.SaveChangesAsync();
            scope.Complete();
        });
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 4

看起来您遇到了一些内部 EF Core 3 实现缺陷/错误,该缺陷/错误已在未来修复,因为该问题不会在目前最新的 EF Core 6.0 中重现(但在 EF Core 3.1 中重现)。

问题在于共享底层数据库连接和数据库事务的清理。可以通过处理调用IDbContextTransaction返回的 EF Core 事务包装器 ( )来解决(这也有助于未来的 EF Core 版本) UseTransaction{Async},例如

using var trans2 = await partnerContext.Database.UseTransactionAsync(transaction.GetDbTransaction());
Run Code Online (Sandbox Code Playgroud)

或者

using var trans2 = partnerContext.Database.UseTransaction(trans.GetDbTransaction());
Run Code Online (Sandbox Code Playgroud)