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

DbContext注册完成后如何传递事件处理服务方法?解决DI异常问题

问题:DbContext与AuditingSupport循环依赖导致DI解析失败,如何绑定SavingChanges事件?

我需要注册DbContext服务,在实例创建后将AuditingSupport的UpdateAuditableEntity方法绑定到它的SavingChanges事件。但AuditingSupport依赖IAppUserService,而AppUserService又依赖DbContext,形成了循环依赖。用Autofac的OnActivated()尝试绑定事件时,运行时抛出异常:

Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'DIT.Persistence.EF.Repositories.Domain.AppUserService'.

现有代码实现

AuditingSupport.cs

public class AuditingSupport : IAuditingSupport
{
    public AuditingSupport(IAppUserService userService)
    {
        this.userService = userService;
    }
    
    // 希望绑定到dbContext.SavingChanges事件的处理方法
    public void UpdateAuditableEntity(object sender, SavingChangesEventArgs e)
    {
        // ... 业务逻辑
    }
}

AppUserService.cs

public class AppUserService : IAppUserService
{
    // 通过DI注入DbContext
    public AppUserService(DbContext context)
    {
       // ... 初始化逻辑
    } 
    // ... 其他方法
}

DbContextModule.cs(Autofac模块)

public class DcContextModule : Module
{
    private readonly IConfiguration configuration;
    private readonly IServiceCollection services;

    public DcContextModule (IConfiguration configuration, IServiceCollection services)
    {
        this.configuration = configuration;
        this.services = services;
    }

    protected override void Load(ContainerBuilder builder)
    {
        // 尝试在OnActivated()中获取Auditing服务并绑定事件处理方法
        builder.RegisterType<EUDbContext>()
               .As<DbContext>()
               .WithParameter("options", this.GetOptions())
               .InstancePerLifetimeScope()
               .OnActivated(x => x.Instance.SavingChanges += services.BuildServiceProvider().GetRequiredService<IAuditingSupport>().UpdateAuditableEntity);
    }    
}

Program.cs服务注册

//注册服务
// ... 其他配置
builder.Services.AddTransient<IAuditingSupport, AuditingSupport>();
builder.Services.AddSingleton<IAppUserService, AppUserService>();
builder.Host.ConfigureContainer<ContainerBuilder>(builder0 => builder0.RegisterModule(new DbContextModule(configuration, builder.Services)));
// ... 其他配置

// 调用服务
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        // 抛出异常
        var audit = services.GetRequiredService<IAuditingSupport>();
        var context = services.GetRequiredService<DbContext>();
    }
}

解决方案

1. 修复Autofac现有方案的问题

当前方法的核心错误是在OnActivated中调用services.BuildServiceProvider(),这会创建一个新的服务提供者,而非使用当前生命周期范围内的容器,导致循环依赖无法被正确处理。

修改DbContextModule.cs的Load方法,改用Autofac提供的当前上下文解析服务:

protected override void Load(ContainerBuilder builder)
{
    builder.RegisterType<EUDbContext>()
           .As<DbContext>()
           .WithParameter("options", this.GetOptions())
           .InstancePerLifetimeScope()
           .OnActivated(x => 
           {
               // 从当前激活的生命周期范围内解析IAuditingSupport
               var auditingSupport = x.Context.Resolve<IAuditingSupport>();
               x.Instance.SavingChanges += auditingSupport.UpdateAuditableEntity;
           });
}

同时调整服务生命周期,避免单例依赖范围服务的问题:

  • AppUserService当前是Singleton,但它依赖的DbContext是InstancePerLifetimeScope,会导致DbContext被捕获在单例中引发问题,将其改为Scoped:
// Program.cs中修改
builder.Services.AddScoped<IAppUserService, AppUserService>();

2. 不使用Autofac的实现方案

如果不想依赖Autofac,可以通过以下两种方式实现:

方案A:自定义DbContext工厂

创建工厂类负责创建DbContext并绑定事件:

public class EUDbContextFactory : IDbContextFactory<EUDbContext>
{
    private readonly IServiceProvider _serviceProvider;
    private readonly DbContextOptions<EUDbContext> _options;

    public EUDbContextFactory(IServiceProvider serviceProvider, DbContextOptions<EUDbContext> options)
    {
        _serviceProvider = serviceProvider;
        _options = options;
    }

    public EUDbContext CreateDbContext()
    {
        var context = new EUDbContext(_options);
        // 从服务提供者解析IAuditingSupport并绑定事件
        var auditingSupport = _serviceProvider.GetRequiredService<IAuditingSupport>();
        context.SavingChanges += auditingSupport.UpdateAuditableEntity;
        return context;
    }
}

在Program.cs中注册:

// 注册DbContext选项
builder.Services.AddDbContext<EUDbContext>(options => 
{
    options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
});

// 注册工厂和相关服务
builder.Services.AddScoped<IDbContextFactory<EUDbContext>, EUDbContextFactory>();
builder.Services.AddScoped<DbContext>(sp => sp.GetRequiredService<IDbContextFactory<EUDbContext>>().CreateDbContext());
builder.Services.AddScoped<IAuditingSupport, AuditingSupport>();
builder.Services.AddScoped<IAppUserService, AppUserService>();

方案B:重写DbContext构造函数绑定事件

在DbContext中直接注入IAuditingSupport并绑定事件,需确保DI容器支持循环依赖(默认Microsoft.Extensions.DependencyInjection不支持,需手动启用):

public class EUDbContext : DbContext
{
    private readonly IAuditingSupport _auditingSupport;

    public EUDbContext(DbContextOptions<EUDbContext> options, IAuditingSupport auditingSupport) : base(options)
    {
        _auditingSupport = auditingSupport;
        SavingChanges += _auditingSupport.UpdateAuditableEntity;
    }

    // ... DbSet和其他配置
}

在Program.cs中注册:

builder.Services.AddDbContext<EUDbContext>(options => 
{
    options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
});

// 启用循环依赖支持(仅在必要时使用)
builder.Services.AddOptions<ServiceProviderOptions>().Configure(o => o.ValidateScopes = false);
builder.Services.AddScoped<DbContext, EUDbContext>();
builder.Services.AddScoped<IAuditingSupport, AuditingSupport>();
builder.Services.AddScoped<IAppUserService, AppUserService>();

关键注意事项

  • 生命周期一致性:确保所有相关服务的生命周期匹配,避免单例服务依赖范围服务的情况,这是引发DI异常的常见原因。
  • 循环依赖处理:如果无法避免循环依赖,可以考虑使用属性注入、延迟解析(注入IServiceProvider在需要时再解析),或者重构代码拆分逻辑打破循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 04:35:34