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

Audit.Net问题:主记录保存抛出DbUpdateException时审计记录未回滚

问题原因分析

你遇到的这个情况核心原因是主数据上下文(DGDBContext)和审计数据上下文(DGAuditDBContext)不在同一个事务范围内。

当你调用SaveChanges时,Audit.EntityFramework默认会先执行主上下文的保存操作,然后再独立提交审计上下文的操作。如果主操作因为数据库约束冲突抛出DbUpdateException,此时主上下文的事务会回滚,但审计上下文的操作已经完成了独立提交,所以审计记录会被保留下来,而主记录却没有保存。

解决方案

针对这个问题,有两种常见的解决思路,你可以根据自己的架构选择:

方案1:将审计操作与主操作纳入同一个事务(推荐)

如果必须保留独立的审计DbContext,你需要用事务范围来包裹两个上下文的操作,确保任何一方失败时都能一起回滚。

步骤1:安装必要的包

首先确保你的项目引用了System.Transactions包(.NET Core 2.2需要手动安装这个NuGet包)。

步骤2:修改主DbContext的SaveChanges方法

调整SaveChanges和SaveChangesAsync方法,使用TransactionScope来统一事务:

public override int SaveChanges()
{
    using (var scope = new TransactionScope(TransactionScopeOption.Required,
        new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
    {
        try
        {
            var result = _helper.SaveChanges(_auditContext, () => base.SaveChanges());
            scope.Complete();
            return result;
        }
        catch
        {
            // 异常时事务自动回滚
            throw;
        }
    }
}

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
    using (var scope = new TransactionScope(TransactionScopeOption.Required,
        new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
        TransactionScopeAsyncFlowOption.Enabled))
    {
        try
        {
            var result = await _helper.SaveChangesAsync(_auditContext, () => base.SaveChangesAsync(cancellationToken));
            scope.Complete();
            return result;
        }
        catch
        {
            throw;
        }
    }
}

这样一来,主上下文和审计上下文的操作都会在同一个事务里,一旦主操作抛出异常,整个事务会回滚,审计记录也不会被插入。

方案2:将审计表移到主DbContext中

如果你的架构允许,可以把审计表整合到主DGDBContext里,这样审计记录的保存会和主记录在同一个DbContext的事务中,异常时自动一起回滚。

步骤1:修改Audit配置

把审计配置中的DbContext改成主上下文:

Audit.Core.Configuration.Setup()
    .UseEntityFramework(ef => ef
        .UseDbContext<DGDBContext>() // 使用主上下文替代独立的审计上下文
        .AuditTypeExplicitMapper(m => m
            .Map<AdmissionType, Audit_AdmissionType>()
            .AuditEntityAction((evt, entry, auditEntity) => {
                ((dynamic)auditEntity).AuditDate = DateTime.UtcNow;
                ((dynamic)auditEntity).UserID = user_name;
                ((dynamic)auditEntity).AuditAction = entry.Action;
            })
        )
    );

步骤2:确保主DbContext包含审计表的DbSet

你的DGDBContext已经有Audit_AdmissionType的DbSet,这一步已经满足,无需额外修改。

这样调整后,审计记录会和主记录在同一个SaveChanges调用中提交,当主操作因为约束异常回滚时,审计记录的插入也会被一并回滚。

补充说明

需要注意的是,Audit.EntityFramework的默认行为是在主操作成功后才保存审计记录,但如果主操作的异常是在数据库层面触发(比如检查约束冲突),主上下文的SaveChanges会直接抛出异常,此时如果审计操作已经提前执行(或者在独立事务中),就会出现不一致的情况。通过上述两种方案,就能确保审计记录和主记录的一致性。

内容的提问来源于stack exchange,提问作者endlesscalm

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 17:47:35