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

.NET Core中使用Audit.EntityFramework自定义提供器遇上下文已释放错误

解决方案:Audit.EntityFramework 自定义数据提供器中 DbContext 已释放的问题

我之前在使用Audit.EntityFramework做实体审计时也碰到过一模一样的问题,核心原因很明确:你的EmployerContext在业务层调用CommitAsync()完成后,已经被.NET Core的DI容器按Scoped生命周期规则回收释放了,但Audit.NET的InsertEventAsync方法是在原DbContext的作用域之外执行的,这时候你再用关联原Context的UnitOfWork去操作审计数据,自然就会抛出Cannot access a disposed object错误。

下面是几个经过验证的可行解决方案,按推荐程度排序:

1. 通过IServiceScopeFactory创建独立作用域处理审计

这是最稳妥的方案,利用DI的作用域隔离特性,让审计操作使用全新的DbContext实例:

首先,在你的自定义数据提供器中注入IServiceScopeFactory:

public class EmployerCustomDataProvider : AuditDataProvider
{
    private readonly IServiceScopeFactory _scopeFactory;

    public EmployerCustomDataProvider(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public override async Task InsertEventAsync(AuditEvent auditEvent)
    {
        // 创建新的DI作用域
        using (var scope = _scopeFactory.CreateScope())
        {
            // 从新作用域中获取UnitOfWork(会自动创建新的EmployerContext实例)
            var auditUnitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
            
            // 将审计事件映射为你的审计日志实体
            var auditLog = MapAuditEventToEntity(auditEvent);
            
            auditUnitOfWork.AuditLogs.Add(auditLog);
            await auditUnitOfWork.CommitAsync();
        }
    }

    // 辅助方法:将AuditEvent转换为你的审计实体
    private AuditLog MapAuditEventToEntity(AuditEvent auditEvent)
    {
        // 实现你的映射逻辑
        return new AuditLog
        {
            EventType = auditEvent.EventType,
            CreatedDate = auditEvent.EventDate,
            JsonData = auditEvent.ToJson()
            // 其他字段...
        };
    }
}

这个方案完全隔离了业务操作和审计操作的DbContext生命周期,从根源上避免了上下文释放冲突。

2. 为审计操作手动创建独立的DbContext实例

如果你不想依赖DI的作用域,可以直接在InsertEventAsync中手动实例化DbContext:

public override async Task InsertEventAsync(AuditEvent auditEvent)
{
    // 从配置中获取连接字符串(可以注入IConfiguration来获取)
    var connectionString = "你的数据库连接字符串";
    
    var options = new DbContextOptionsBuilder<EmployerContext>()
        .UseSqlServer(connectionString)
        .Options;

    using (var context = new EmployerContext(options))
    {
        var auditLog = MapAuditEventToEntity(auditEvent);
        context.AuditLogs.Add(auditLog);
        await context.SaveChangesAsync();
    }
}

这种方式简单直接,但缺点是需要手动处理DbContext的配置,不如依赖DI灵活。

3. 确保Audit事件同步执行(应急方案)

如果你的业务操作是在请求作用域内完成的,可以强制Audit.NET同步执行事件插入,确保操作在原DbContext被释放前完成:

在你的Audit配置代码中,设置事件创建策略为同步插入:

Audit.Core.Configuration.Setup()
    .UseCustomProvider(new EmployerCustomDataProvider())
    .WithCreationPolicy(AuditCreationPolicy.InsertOnEnd); // 同步插入事件

同时,确保业务层的CreateUpdateEmployerBranch方法在同一个作用域内完成所有操作(比如Web请求的作用域),这样Audit的插入操作会在原DbContext释放前执行完毕。不过这种方案只适合简单场景,异步操作下可能依然会有问题。

为什么会出现这个问题?

再补充下原因帮你理解:.NET Core中默认的DbContext注册是Scoped生命周期,它会在当前作用域结束时自动释放(比如Web请求结束时)。当你调用UnitOfWork.CommitAsync()后,业务逻辑的DbContext已经完成使命,DI容器会标记它待释放。而Audit.NET默认可能会异步处理事件插入(或者在原作用域之外执行),此时再访问已经被释放的DbContext就会触发异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:51:33