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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 02:50:39