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

使用内存与MySQL的Hangfire控制台应用依赖注入异常求助

解决Hangfire任务无法获取依赖注入DbContext的异常

问题概述

开发了一个集成依赖注入和动态循环任务的控制台应用,初始从MySQL仓库加载计划任务到内存,任务触发时会检查数据库计划是否更新并同步内存数据。但任务触发时反复抛出System.MissingMethodException,提示“无参构造函数未定义”。

错误原因

  1. Hangfire默认使用SimpleJobActivator,只能实例化包含无参构造函数的类。而Scheduler类仅提供带HangfireServiceContext参数的构造函数,Hangfire无法通过默认方式解析依赖。
  2. 直接在RecurringJob中绑定Scheduler的方法时,Hangfire会尝试独立实例化Scheduler,无法利用ASP.NET Core的依赖注入容器获取所需的DbContext实例。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 12:50:20