使用内存与MySQL的Hangfire控制台应用依赖注入异常求助
解决Hangfire任务无法获取依赖注入DbContext的异常
问题概述
开发了一个集成依赖注入和动态循环任务的控制台应用,初始从MySQL仓库加载计划任务到内存,任务触发时会检查数据库计划是否更新并同步内存数据。但任务触发时反复抛出System.MissingMethodException,提示“无参构造函数未定义”。
错误原因
- Hangfire默认使用
SimpleJobActivator,只能实例化包含无参构造函数的类。而Scheduler类仅提供带HangfireServiceContext参数的构造函数,Hangfire无法通过默认方式解析依赖。 - 直接在
RecurringJob中绑定Scheduler的方法时,Hangfire会尝试独立实例化Scheduler,无法利用ASP.NET Core的依赖注入容器获取所需的DbContext实例。 DbContext是Scoped生命周期服务,直接在Scheduler(Transient)中持有会导致上下文生命周期管理问题,进一步引发依赖解析失败。
解决方案
1. 配置Hangfire使用ASP.NET Core依赖注入容器
替换Hangfire默认的JobActivator,使其能够从ASP.NET Core的DI容器中解析服务实例。
2. 分离任务逻辑到独立Scoped服务
将任务执行逻辑从Scheduler中拆分到单独的Scoped服务,确保每个任务都能获取到正确生命周期的DbContext。
3. 调整RecurringJob注册方式
通过DI容器获取任务服务实例,避免直接绑定Scheduler的方法。
代码修改示例
步骤1:更新Program.cs中的Hangfire配置
在ConfigureServices中配置Hangfire使用ASP.NET Core的JobActivator:
public static IHostBuilder CreateHostBuilder() { return Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hostingContext, config) => { config.SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); }) .ConfigureServices((context, services) => { var connectionString = context.Configuration.GetConnectionString("Default"); services.AddDbContext<HangfireServiceContext>(options => { options.UseMySql(connectionString); }); // 配置Hangfire使用ASP.NET Core依赖注入容器 services.AddHangfire((sp, config) => { config.UseMemoryStorage() .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) .UseColouredConsoleLogProvider() .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseActivator(new AspNetCoreJobActivator(sp)); }); services.AddHangfireServer(); services.AddTransient<IScheduler, Scheduler>(); // 注册任务执行服务 services.AddScoped<IScheduleTaskService, ScheduleTaskService>(); }); }
步骤2:创建独立的任务执行服务
public interface IScheduleTaskService { void PrintCurrentTime(int scheduleId); } public class ScheduleTaskService : IScheduleTaskService { private readonly HangfireServiceContext _context; public ScheduleTaskService(HangfireServiceContext context) { _context = context; } public void PrintCurrentTime(int scheduleId) { Console.WriteLine($"Schedule {scheduleId}: {DateTime.Now.TimeOfDay}"); var dbSchedule = _context.Schedules.FirstOrDefault(s => s.Id == scheduleId); var existingSchedule = GetMemorySchedule(scheduleId); // 需实现从内存获取计划的逻辑(可通过单例管理器) if (dbSchedule != null && existingSchedule != null) { if (dbSchedule.Task != existingSchedule.Task || dbSchedule.Name != existingSchedule.Name) { // 执行内存计划同步逻辑 } } } // 示例:从内存获取计划的方法(需结合实际的内存管理实现) private Schedule GetMemorySchedule(int scheduleId) { // 此处可通过注入单例的ScheduleManager类获取内存中的计划列表 return null; } }
步骤3:修改Scheduler类
public class Scheduler : IScheduler { private List<Schedule> _schedules; private readonly HangfireServiceContext _context; private readonly IServiceProvider _serviceProvider; public Scheduler(HangfireServiceContext context, IServiceProvider serviceProvider) { _context = context; _serviceProvider = serviceProvider; Initialize(); } private void Initialize() { _schedules = _context.Schedules.ToList(); } public void Execute() { foreach (var schedule in _schedules) { // 通过DI容器获取任务服务实例注册循环任务 RecurringJob.AddOrUpdate( $"job-id-{schedule.Id}", () => _serviceProvider.GetRequiredService<IScheduleTaskService>().PrintCurrentTime(schedule.Id), schedule.Cron); } Console.WriteLine("Hangfire服务已启动,按任意键退出..."); Console.ReadLine(); } }
步骤4:调整Program.Main方法
确保Host正确启动并保持运行:
static void Main(string[] args) { var host = CreateHostBuilder().Build(); var scheduler = host.Services.GetRequiredService<IScheduler>(); scheduler.Execute(); host.Run(); }
关键说明
- 使用
AspNetCoreJobActivator让Hangfire完全集成ASP.NET Core的DI系统,确保所有依赖都能被正确解析。 - 将任务逻辑放在Scoped服务中,保证每次任务执行都能获取到全新的
DbContext实例,避免上下文复用导致的问题。 - 通过
IServiceProvider获取任务服务实例,确保Hangfire在执行任务时能正确触发依赖注入流程。
内容的提问来源于stack exchange,提问作者Samuel Marvin Aguilos
相关产品推荐
相关产品推荐

