.NET Core中使用Audit.EntityFramework自定义提供器遇上下文已释放错误
我之前在使用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

