我们将大型DbContext拆分为更小的上下文,每个上下文都负责一个小的领域边界上下文。这些上下文的保存操作由工作单元进行编排,如下所示。
该领域有两个边界上下文:合作伙伴和员工。工作单元管理两个DbContext:
简化版本的问题可以在github上找到。
以下代码可以正常工作。所有更改都在单个事务中执行。
然而,当尝试第二次保存更改时,以下代码会抛出异常。
该领域有两个边界上下文:合作伙伴和员工。工作单元管理两个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();
});
}
}
以下代码可以正常工作。所有更改都在单个事务中执行。
var unitOfWork = new unitOfWork();
... perform updates to both contexts
await unitOfWork.SaveChanges();
然而,当尝试第二次保存更改时,以下代码会抛出异常。
var unitOfWork = new unitOfWork();
... perform updates to both contexts
await unitOfWork.SaveChanges(); <-- Work fine
... doing a bit more work
await unitOfWork.SaveChanges(); <-- Crashes
上述代码行出现了错误信息:连接已经处于一个事务中,无法参与另一个事务。
结果产生的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.
请问有人知道第二个 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();
}
}
第一次调用过了,数据库也被更新了,但第二次调用却失败了,出现以下错误。
更新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();
});
await strategy.ExecuteAsync
中的await
将控制权返回给调用者,并允许在主线程上继续执行。因此,当第一次到达unitOfWork.SaveChanges();
时,它将触发事务,然后立即尝试执行您的 "... doing a bit more work",然后再次立即调用unitOfWork.SaveChanges();
。 - Ibrennan208strategy.ExecuteAsync
也会出现上述情况,因为你在其中使用了await
。你可能需要捕获正在保存的任务,并确保在下一次保存之前该任务已经完成。 - Ibrennan208public async Task SaveChangesAsync()
而不是public void SaveChanges()
。 - Alexander Petrov