DbContext注册完成后如何传递事件处理服务方法?解决DI异常问题
我需要注册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

