You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:45:59