Entity Framework事务异常回滚时未恢复子实体记录问题排查
解决EF事务中子实体回滚异常的问题
问题根源分析
你的问题核心在于:添加LogRecord时触发了子实体Splits的延迟加载,导致EF上下文跟踪了这些子实体;同时事务管理逻辑和EF级联删除的客户端行为冲突,使得回滚时无法正确恢复子实体。移除LogRecord.Add后正常,正是因为没有触发子实体的加载,EF仅通过数据库级联删除处理,回滚逻辑能正常工作。
具体解决方案
1. 修正实体删除逻辑,确保EF正确跟踪状态
检查MoneyTransactions.Delete(id)的实现,必须通过EF的实体状态标记删除,而非直接执行原生SQL。同时提前加载子实体,避免延迟加载带来的跟踪问题:
// MoneyTransactions仓储的Delete方法实现 public void Delete(int id) { // 提前加载关联的Splits,确保EF能正确跟踪所有相关实体 var transaction = _context.MoneyTransactions.Include(t => t.Splits) .FirstOrDefault(t => t.Id == id); if (transaction != null) { _context.MoneyTransactions.Remove(transaction); } }
2. 重构UoW事务管理,避免手动控制的疏漏
将事务管理整合到Save方法中,使用using确保事务自动释放,避免手动调用BeginTransaction和Rollback的潜在错误:
// UoW.cs修改后的Save方法 public bool Save() { using (var transaction = _context.Database.BeginTransaction()) { try { int savedCount = _context.SaveChanges(); transaction.Commit(); return savedCount > 0; } catch (Exception) { transaction.Rollback(); throw; } } }
对应的控制器代码可以简化,无需手动调用BeginTransaction和Rollback:
try { var moneyTransaction = uow.MoneyTransactions.GetById(id); var log = new LogRecord(LogRecordType.Delete) { TransId = moneyTransaction.Id, TransType = moneyTransaction.Type, Data1 = moneyTransaction.Splits[0].Account.ToString(), Data2 = moneyTransaction.Splits[1].Account.ToString(), Notes = moneyTransaction.Notes, }; uow.LogRecords.Add(log); uow.MoneyTransactions.Delete(id); deleted = uow.Save(); } catch (Exception ex) { throw ex; }
3. 调整EF级联删除配置,禁用客户端级联操作
修改OnModelCreating中的配置,让数据库负责级联删除,避免EF客户端跟踪导致的回滚冲突:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<MoneyTransaction>() // 修正为和代码一致的实体类名 .HasMany(t => t.Splits) .WithRequired(s => s.Transaction) .HasForeignKey(s => s.Transaction_Id) .WillCascadeOnDelete(true); // 保留数据库级联删除 // 禁用EF客户端的级联删除逻辑,避免内存中标记子实体删除引发的问题 modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); modelBuilder.Entity<MoneyTransaction>() .Property(t => t.RowVersion) .IsRowVersion(); base.OnModelCreating(modelBuilder); }
4. 确保DbContext的生命周期正确
在依赖注入配置中,将DbContext注册为Scoped(ASP.NET中每个请求一个实例),绝对不能用Singleton,否则上下文会跨请求保留实体状态,导致回滚后仍有残留的删除标记。
关键注意点
- 事务的原子性依赖数据库层面的支持,所有操作必须在同一个事务范围内执行,不能在仓储方法中单独调用
SaveChanges。 - 避免延迟加载关联实体时的上下文跟踪冲突,提前用
Include加载所需的关联数据。
内容的提问来源于stack exchange,提问作者Bakri Alloush
相关产品推荐
相关产品推荐

