Entity Framework Core - 事务在提交后无法回滚

问题描述 投票:0回答:2

它正在使用 Entity Framework Core 来更新数据库。

dbContextTransaction.Commit();
工作正常,之后是一些文件操作,结果很糟糕。然后它会抛出一个错误,因此它尝试使用
dbContextTransaction.Rollback();
回滚,但结果是:

此SqlTransaction已完成;已经不能用了。

DbContext dbContext = scope.ServiceProvider.GetService<DbContext>();
IDbContextTransaction dbContextTransaction = dbContext.Database.BeginTransaction();

try
{
    IOrderDao orderDao = scope.ServiceProvider.GetService<IOrderDao>();
    IItemSoldPriceDao itemSoldPriceDao = scope.ServiceProvider.GetService<IItemSoldPriceDao>();

    ItemSoldPrice itemSoldPrice = new ItemSoldPrice
    {
      ...
    };

    itemSoldPriceDao.AddItemSoldPrice(itemSoldPrice);
    order.SoldPriceCaptured = true;

    dbContext.SaveChanges();
    dbContextTransaction.Commit();
    //... some other file operation throws out error
    throw new Exception("aaa");
}
catch (Exception ex)
{
    CommonLog.Error("CaptureSoldPrice", ex);
    dbContextTransaction.Rollback();
}

事务提交后就不能回滚了?

c# entity-framework entity-framework-core
2个回答
1
投票

使用实体框架时,仅当您想要将 DbContext 操作的成功或失败与 DbContext 范围之外的其他操作链接起来时,才需要显式事务。 DbContext 中 SaveChanges 之前的所有操作都已分组到事务中。因此,例如在 DbContext 中跨两个或多个表保存实体不需要设置显式事务,如果 EF 无法保存其中一个或另一个,它们将一起提交或回滚。

使用显式事务时,

Commit()

调用应该是本质上形成工作单元的最后一个操作。这将是确定事务范围内的所有操作是否成功的最后一个操作。因此,作为一般规则,所有操作,无论是基于数据库、基于文件等,都应该注册并侦听事务的成功或失败。

使用事务的示例:假设我们有一个系统,通过两个单独的 DbContext 访问两个数据库。一种是跟踪订单并拥有客户记录的订单系统,另一种是跟踪客户信息的 CRM。当我们接受新客户的新订单时,我们会检查 CRM 系统中是否有客户,如果是新客户,则在两个系统中创建客户记录。

using (var orderContext = new OrderDbContext()) { var transaction = orderContext.Database.BeginTransaction(); try { var order = new Order { // TODO: Populate order details.. } if(customerDetails == null && registerNewCustomer) // Where customerDetails = details loaded if an existing customer logged in and authenticated... { using(var crmContext = new CrmDbContext()) { crmContext.Database.UseTransaction(transaction); var customer = new Customer { UserName = orderDetails.CustomerEmailAddress, FirstName = orderDetails.CustomerFirstName, LastName = orderDetails.CustomerLastName, Address = orderDetails.BillingAddress }; crmContext.Customers.Add(customer); crmContext.SaveChanges(); var orderCustomer = new Orders.Customer { CustomerId = customer.CustomerId, FirstName = customer.FirstName, LastName = customer.LastName } orderContext.Customers.Add(orderCustomer); } } order.CustomerId = crmContext.Customers .Select(c => c.CustomerId) .Single(c => c.UserName == customerDetails.UserName); orderContext.Orders.Add(order); orderContext.SaveChanges(); transaction.Commit(); } catch(Exception ex) { // TODO: Log exception.... transaction.Rollback(); } }

订单数据库客户只是 CRM 客户的一个薄包装,我们将在其中获取所有客户详细信息。 CRM 客户管理与订单客户记录相关的客户 ID。这绝不是生产代码类型示例,而只是概述事务在使用时如何协调多个操作。

通过这种方式,如果在任何时候出现任何异常,例如在创建并保存新的客户记录之后,所有保存的更改都将回滚,我们可以检查任何记录的异常详细信息以及记录的值,以确定发生了什么错了。

在处理 DbContext 操作和其他操作的组合时,我们可能希望在失败时支持回滚流程,然后我们可以利用像

TransactionScope

包装器这样的结构。然而,应该谨慎使用,并且仅在您明确需要结合这些操作的情况下使用,而不是尝试使用该模式作为所有操作的标准。在大多数情况下,您不需要显式交易。

    


0
投票

// create transaction await using var transaction = await _db.Database.BeginTransactionAsync(); try { // do some database updates await DatabaseUpdate(); // commit transaction await transaction.CommitAsync(); // call third party API await _externalService.CallThirdPartyAsync(); } catch (Exception ex) { // rollback await transaction.RollbackAsync(); throw; }

问题

如果提交事务后(例如,在第三方 API 调用期间)抛出异常,就会出现此问题。在 catch 块中,代码尝试回滚已提交的事务。这将导致错误,因为事务上下文在成功提交后不再有效。

这里是抛出的异常 - 由

ZombieCheck

可知:

This SqlTransaction has completed; it is no longer usable.
at Microsoft.Data.SqlClient.SqlTransaction.ZombieCheck()
at Microsoft.Data.SqlClient.SqlTransaction.Rollback()
at System.Data.Common.DbTransaction.RollbackAsync(CancellationToken cancellationToken)

解决方案

要解决这个问题,您应该将事务提交移至 try 块的最后一行。这可确保仅在所有操作(包括第三方 API 调用)成功时才提交事务。如果任何操作失败,事务将被适当回滚。这是修改后的代码:

await using var transaction = await _db.Database.BeginTransactionAsync(); try { // Do some database updates await DatabaseUpdate(); // Call third party API await _externalService.CallThirdPartyAsync(); // Commit transaction await transaction.CommitAsync(); } catch (Exception ex) { // Rollback await transaction.RollbackAsync(); throw; }

希望有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.