如何向Job注入服务并正确配置IOC容器解决DI生命周期异常
问题根源
核心问题不是Dependency Injection生命周期配置错误,是Quartz.NET默认使用内置实例化逻辑创建Job,不会主动从ASP.NET Core的DI容器解析构造函数依赖:
- 无参构造的Job可以被Quartz直接new出来,Execute方法正常执行
- 带构造函数依赖注入的Job会因为Quartz找不到对应传参,实例化失败,无法触发执行
- 全Singleton配置下Job能运行属于特殊情况:根容器可以直接解析单例实例,但这个方案完全违反EF Core DbContext的使用规范,会引发缓存脏读、并发操作冲突等问题,不能用于生产环境。
修复步骤
1. 配置Quartz使用DI容器解析Job
Quartz默认的Job工厂不会走DI容器,需要自定义实现支持DI的工厂替换默认实现。
首先在Startup.cs的ConfigureServices方法中注册自定义Job工厂和相关服务,原有服务的生命周期配置不需要修改:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDbContext<EFDbContext>(options => options .UseSqlServer(Configuration.GetConnectionString("Cnn")) .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking), ServiceLifetime.Transient); services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); // 注册自定义Job工厂,替换Quartz默认实现 services.AddSingleton<IJobFactory, ScopedJobFactory>(); services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>(); // 原有Job、服务注册保留,不需要改生命周期 services.AddTransient<CheckTransactionConfirmJob>(); services.AddTransient<CheckPaymentConfirmationScheduler>(); services.AddTransient<IPaymentRepository, EfPlanRepository>(); }
添加自定义Job工厂实现,负责从DI容器解析Job实例:
public class ScopedJobFactory : IJobFactory { private readonly IServiceProvider _rootServiceProvider; public ScopedJobFactory(IServiceProvider rootServiceProvider) { _rootServiceProvider = rootServiceProvider; } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { var jobType = bundle.JobDetail.JobType; return (IJob)_rootServiceProvider.GetRequiredService(jobType); } public void ReturnJob(IJob job) { if (job is IDisposable disposable) { disposable.Dispose(); } } }
在调度器初始化逻辑中,给Scheduler实例指定自定义的Job工厂(在CheckPaymentConfirmationScheduler初始化Scheduler的位置添加代码即可):
var scheduler = await schedulerFactory.GetScheduler(); // 赋值DI Job工厂 scheduler.JobFactory = _rootServiceProvider.GetRequiredService<IJobFactory>();
2. 修正Job内的依赖解析方式
Quartz执行Job时没有ASP.NET Core请求管道的天然服务Scope,直接在Job构造函数注入Transient/Scoped生命周期的服务(比如DbContext、依赖DbContext的Repository),会导致服务从根容器解析、长期不释放,完全违背Transient生命周期的设计预期。
正确的做法是仅在Job构造函数注入根IServiceProvider,每次执行Execute方法时手动创建独立服务Scope,在Scope内解析需要的服务,执行完成后Scope自动释放所有相关服务(包括DbContext)。
修改后的CheckTransactionConfirmJob代码如下:
public class CheckTransactionConfirmJob : IJob { private readonly IServiceProvider _rootServiceProvider; // 仅注入根服务提供者,不要直接注入Repository、DbContext这类短生命周期服务 public CheckTransactionConfirmJob(IServiceProvider rootServiceProvider) { _rootServiceProvider = rootServiceProvider; } public async Task Execute(IJobExecutionContext context) { try { // 每次执行创建独立服务域,执行完自动释放所有域内服务 using var scope = _rootServiceProvider.CreateScope(); var paymentRepository = scope.ServiceProvider.GetRequiredService<IPaymentRepository>(); // 从JobDataMap读取传入参数 var paymentId = context.JobDetail.JobDataMap.GetGuid("paymentId"); var userId = context.JobDetail.JobDataMap.GetGuid("userId"); // 执行业务逻辑 } catch (Exception e) { Console.WriteLine(e); } } }
注意事项
- 原有
RunPaymentCheckerJob方法中创建Job、Trigger的逻辑不需要修改,Quartz会自动通过配置的DI工厂解析Job实例 - 不需要修改DbContext的Transient生命周期配置,符合EF Core的最佳实践
- 所有短生命周期服务(DbContext、Repository、业务服务等)都要在Execute方法的Scope内解析,不要直接注入Job构造函数,避免服务长期复用引发问题
内容的提问来源于stack exchange,提问作者Sasan
相关产品推荐
相关产品推荐

