.NET6+MediatR领域事件处理中ApplicationDbContext已释放错误
问题分析与解决方案
错误根源
你遇到的Cannot access a disposed context instance错误,核心原因是领域事件处理未等待异步操作完成,导致DbContext在事件处理器执行过程中被提前释放:
- 在
SavedChangesAsync方法中,你同步触发了领域事件发布,但没有等待MediatR的异步处理逻辑完成。 - SaveChanges完成后,请求上下文结束,Scoped生命周期的
ApplicationDbContext被DI容器回收,而此时事件处理器中的异步数据库操作(第二次查询)仍在执行,因此抛出DbContext已释放的错误。
具体修复步骤
1. 修改领域事件拦截器,异步等待事件处理完成
将拦截器中的事件发布逻辑改为异步,并在SavedChangesAsync中等待所有事件处理完成:
public class DomainEventPublishingInterceptor : SaveChangesInterceptor { private readonly IMediator _mediator; public DomainEventPublishingInterceptor(IMediator mediator) { _mediator = mediator; } public override async ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default) { // 收集所有聚合根的事件发布任务 var publishTasks = eventData.Context.ChangeTracker.Entries<IAggregateRoot>() .Select(entry => PublishDomainEvents(entry.Entity, cancellationToken)); // 等待所有事件处理完成 await Task.WhenAll(publishTasks); return await base.SavedChangesAsync(eventData, result, cancellationToken); } private async Task PublishDomainEvents(IAggregateRoot aggregateRoot, CancellationToken cancellationToken) { var domainEvents = aggregateRoot.DomainEvents.ToList(); if (!domainEvents.Any()) return; foreach (var domainEvent in domainEvents) { await _mediator.Publish(domainEvent, cancellationToken); } // 清空已发布的领域事件 aggregateRoot.ClearDomainEvents(); } }
2. 调整拦截器的注册方式,通过DI管理生命周期
先将拦截器注册为Scoped服务,再从DI容器中获取实例添加到DbContext:
// 注册拦截器为Scoped服务 services.AddScoped<DomainEventPublishingInterceptor>(); // 修改DbContext注册代码 services.AddDbContext<ApplicationDbContext>((provider, options) => { options.UseSqlServer(configuration["ConnectionStrings:Cnn"], b => { b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName); b.MigrationsHistoryTable("__ApplicationDbContextMigrationHistory", "dbo"); }) .EnableSensitiveDataLogging() .EnableDetailedErrors(); // 从DI容器获取拦截器,确保生命周期与DbContext一致 options.AddInterceptors(provider.GetRequiredService<DomainEventPublishingInterceptor>()); });
3. 验证事件处理器逻辑
确保事件处理器中的数据库操作使用传入的CancellationToken,现有处理器代码逻辑本身无问题,修复上述两点后即可正常执行。
原理说明
- 通过异步等待事件处理完成,确保
ApplicationDbContext在整个事件处理周期内保持活跃,直到所有数据库操作执行完毕。 - 依赖注入管理拦截器生命周期,避免因手动实例化导致的生命周期不匹配问题,保证拦截器与DbContext处于同一个请求上下文。
内容的提问来源于stack exchange,提问作者Ocelot
相关产品推荐
相关产品推荐

