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

Quartz.Net数据库作业仅单例添加可用,动态添加触发空引用异常求助

问题描述

我开发了一个将作业存入数据库并按需调度的程序。此前在Program.cs中以单例方式添加作业可正常运行,但每个调度器仅能添加一个作业,且无法检查作业是否已存在。因此我将作业添加逻辑移至调度器中,先检查数据库中是否存在目标作业,不存在则添加。

作业可成功添加,但完全无法运行,调用时会触发来自JobRunShell.Run()的NullReferenceException。作业在数据库中的状态看起来完全正常,代码也未发现错误。这种添加作业的方式是否不正确?

以单例添加作业的Program.cs代码

builder.Services.AddSingleton<IJobFactory, JobFactory>();
builder.Services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
builder.Services.AddSingleton<MailHRNewEmployee>();
builder.Services.AddSingleton(new JobMetadata(Guid.NewGuid(), typeof(MailHRNewEmployee), "HR First Contact", "job blueprint"));
builder.Services.AddHostedService<MyScheduler>();

MyScheduler.cs代码

public async Task StartAsync(CancellationToken cancellationToken)
{
    //Creating Scheduler
    Scheduler = await schedulerFactory.GetScheduler();
    Scheduler.JobFactory = jobFactory;

    //Start Scheduler
    await Scheduler.Start(cancellationToken);

    //Create Jobs
    var istrue = await Scheduler.CheckExists(new JobKey("HR First Contact", "DEFAULT"));
    if (!(bool)istrue)
    {
        jobMetaData = new JobMetadata(Guid.NewGuid(), typeof(MailHRNewEmployee), "HR First Contact", "job blueprint");
        IJobDetail jobDetail = CreateJob(jobMetaData);
        await Scheduler.AddJob(jobDetail, true);
    }

...(checking all the other jobs)

private IJobDetail CreateJob(JobMetadata jobMetadata)
{
    return JobBuilder.Create(jobMetaData.JobType).WithIdentity(jobMetaData.JobName.ToString()).StoreDurably(true).WithDescription(jobMetaData.JobDescription).Build();
}

NullReferenceException异常栈

[10:43:31 ERR] Job DEFAULT.HR First Contact threw an unhandled Exception: 
System.NullReferenceException: Object reference not set to an instance of an object.
   at Quartz.Core.JobRunShell.Run(CancellationToken cancellationToken)
[10:43:32 ERR] Job DEFAULT.HR First Contact threw an exception.
Quartz.SchedulerException: Job threw an unhandled exception.
 ---&gt; System.NullReferenceException: Object reference not set to an instance of an object.
   at Quartz.Core.JobRunShell.Run(CancellationToken cancellationToken)
   --- End of inner exception stack trace --- [See nested exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at Quartz.Core.JobRunShell.Run(CancellationToken cancellationToken)]

补充说明:
我尝试使用MS DI集成库配合Quartz托管服务,但无法使其与持久化作业存储及MySQL配合工作。

builder.Services.Configure<QuartzOptions>(builder.Configuration.GetSection("Quartz"));
builder.Services.Configure<QuartzOptions>(options =>
{
    options.Scheduling.IgnoreDuplicates = true;
    options.Scheduling.OverWriteExistingData = true;
});

builder.Services.AddQuartz(q =>
{
    q.SchedulerId = "Job Creator";
    q.UseMicrosoftDependencyInjectionJobFactory();


    var jobKey = new JobKey("HR First Contact", "DEFAULT");
    q.AddJob<MailHRNewEmployee>(jobKey, j => j
        .StoreDurably()
        .WithDescription("job blueprint"));

    q.UsePersistentStore(s =>
    {
        s.PerformSchemaValidation = true;
        s.UseProperties = true;
        s.RetryInterval = TimeSpan.FromSeconds(15);
        //Neither .UseMySql nor UseMySqlConnector work
        //MySqlConnector should be the one I want though
        s.UseMySqlConnector(MySql =>
        {
            //I am loading the configuration above but don't know how to use the connection string here?
            //I thought it would maybe be Configuration.GetConnectionString("xyz");
            MySql.ConnectionString = "MyConnectionString";
            MySql.TablePrefix = "QRTZ_";
        });
        s.UseJsonSerializer();
    });
});
builder.Services.AddTransient<MailHRNewEmployee>();

builder.Services.AddQuartzHostedService(options =>
{
    options.WaitForJobsToComplete = true;
});

解决方案

一、调度器内添加作业导致空引用的修复

1. 修正CreateJob方法的参数错误

你的CreateJob方法参数是jobMetadata,但方法内误用了类成员jobMetaData(大小写不一致),如果类成员未初始化,会导致JobDetail关联错误的类型信息,直接引发空引用。修复代码:

private IJobDetail CreateJob(JobMetadata jobMetadata)
{
    return JobBuilder.Create(jobMetadata.JobType)
        .WithIdentity(jobMetadata.JobName)
        .StoreDurably(true)
        .WithDescription(jobMetadata.JobDescription)
        .Build();
}

2. 确保作业实例能被DI容器正确解析

动态添加作业时,必须保证作业类型(如MailHRNewEmployee)已注册到DI容器,且自定义JobFactory能正确从容器获取实例:

  • 注册作业:使用AddTransient或AddScoped(单例仅适用于无状态作业):
    builder.Services.AddTransient<MailHRNewEmployee>();
    
  • 检查自定义JobFactory实现,确保正确解析实例:
    public class JobFactory : IJobFactory
    {
        private readonly IServiceProvider _serviceProvider;
    
        public JobFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
    
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
        }
    
        public void ReturnJob(IJob job)
        {
            if (job is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }
    }
    

二、MS DI集成库配合MySQL持久化的配置修复

1. 安装正确的NuGet包

确保已安装:

  • Quartz.Extensions.DependencyInjection
  • Quartz.Persistence.MySqlConnector(使用官方MySqlConnector驱动,而非旧版MySql.Data)

2. 正确读取连接字符串

通过配置文件读取连接字符串,避免硬编码:

q.UsePersistentStore(s =>
{
    s.PerformSchemaValidation = true;
    s.UseProperties = true;
    s.RetryInterval = TimeSpan.FromSeconds(15);
    s.UseMySqlConnector(MySql =>
    {
        MySql.ConnectionString = builder.Configuration.GetConnectionString("QuartzMySQL");
        MySql.TablePrefix = "QRTZ_";
        MySql.MySqlVersion = new Version(8, 0, 28); // 指定MySQL版本避免兼容性问题
    });
    s.UseJsonSerializer();
});

在appsettings.json中添加连接字符串配置:

"ConnectionStrings": {
    "QuartzMySQL": "server=localhost;database=quartz;user=root;password=你的密码;SslMode=None;"
}

3. 动态添加作业的适配

如果需要动态检查并添加作业,不要在AddQuartz中硬编码作业注册,而是通过IScheduler在托管服务中动态处理,避免重复注册冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 21:25:17