在 foreach 循环中回滚或提交事务

Rollback or commit Transactions inside a foreach loop

我在 MVC 应用程序中使用 Entity Framework 6 和 .Net Framework 4.8。我正在尝试对实体列表(发票)做两件事:

当电子邮件发送失败时,我想回滚我对实体所做的所有更改。

这是我的代码:

foreach (var id in listOfIds)
{
  using (var dbContextTransaction = db.Database.BeginTransaction())
  {
    try 
    {
      var invoice = db.Invoices.Find(id);
      MakeChangesToInvoice(invoice);
      var pdf = GeneratePdf(invoice);
      SendEmail(pdf);
      db.SaveChanges();
      dbContextTransaction.Commit();
    }
    catch(SomeEmailException)
    {
      dbContextTransaction.Rollback();
    }
  }
}

这里的问题是,当我在错误的迭代之后有一个成功的迭代时,错误迭代(称为回滚)的更改仍然会被保存。

您修改的实体仍由上下文跟踪,因此更改将在下一次 SaveChanges 调用(在下一次迭代中)传播到数据库,因此您需要重新创建您的 dbcontext(可能会对性能产生明显影响,需要检查)每次迭代或分离实体。像这样:

Invoice invoice = null;
try 
{
  invoice = db.Invoices.Find(id);
  MakeChangesToInvoice(invoice);
  var pdf = GeneratePdf(invoice);
  SendEmail(pdf);
  db.SaveChanges();
  dbContextTransaction.Commit();
}
catch(SomeEmailException)
{
  dbContextTransaction.Rollback();
  // note that if you have complex object graph this possibly will not detach child objects 
  if(invoice != null) dbContext.Entry(invoice).State = EntityState.Detached;
}

或者如果合适的话使用this answer中的DetachAll。另请注意,如果有两个对象,则重新创建上下文可能是更好的选择(或者您可以仅在前面 运行 中出现错误的情况下重新创建它)。