I have a hard time using Quartz 3.0.7 with ASP.NET Core 2.2 after I have defined two jobs that rely on a scoped service (ScopedDataAccess) that is a wrapper upon my database context:
services.AddScoped<IScopedDataAccess, ScopedDataAccess>();
services.AddDbContext<AggregatorContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("Default")));
The issue is that both jobs receive the same instance of the scoped service (and thus the same database context), thus crashing the context due to parallel usage.
My code is as follows:
Startup.cs
Jobs are defined as "scoped" and my expectation is for each instance to run in its own "scope"
private void ConfigureQuartz(IServiceCollection services, params Type[] jobs)
{
services.AddSingleton<IJobFactory, QuartzJobFactory>();
services.Add(jobs.Select(jobType => new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)));
services.AddSingleton(provider =>
{
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler().Result;
scheduler.JobFactory = provider.GetService<IJobFactory>();
scheduler.Start();
return scheduler;
});
}
protected void StartJobs(IApplicationBuilder app, IApplicationLifetime lifetime)
{
var scheduler = app.ApplicationServices.GetService<IScheduler>();
var configService = app.ApplicationServices.GetService<IConfigurationService>();
QuartzServicesUtilities.StartJob<ArticleXUserDataRefresherJob>(scheduler,
TimeSpan.FromSeconds(configService.ArticleXUserDataRefresherJobPeriod));
QuartzServicesUtilities.StartJob<LinkDataFetchJob>(scheduler,
TimeSpan.FromSeconds(configService.LinkDataJobPeriod));
lifetime.ApplicationStarted.Register(() => scheduler.Start());
lifetime.ApplicationStopping.Register(() => scheduler.Shutdown());
}
QuartzServicesUtilities
public class QuartzServicesUtilities
{
public static void StartJob<TJob>(IScheduler scheduler, TimeSpan runInterval)
where TJob : IJob
{
var jobName = typeof(TJob).FullName;
var job = JobBuilder.Create<TJob>()
.WithIdentity(jobName)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{jobName}.trigger")
.StartNow()
.WithSimpleSchedule(scheduleBuilder =>
scheduleBuilder
.WithInterval(runInterval)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
QuartzJobFactory
public class QuartzJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var jobDetail = bundle.JobDetail;
var job = (IJob)_serviceProvider.GetService(jobDetail.JobType);
return job;
}
public void ReturnJob(IJob job) { }
}
Is there a way to use Quartz.NET to obtain different scopes for different jobs?
As i know, this is not possible with Quartz, i struggled with the same issues and the only solution i found was to use a ServiceLocator and create the scope explicitly in the Job.
I ended with something like that:
In this case, your worker is still scoped but the job isn't anymore. So you can still use your Worker in other places in your solution and the scope still works. You need to implement the ServiceLocator by yourself depending on the DI do you use and
IServiceLocator
must also be defined by you.Edit
In one of our projects we use this:
We use mostly SimpleInjector with this implementation:
As you can see, this is just a simple wrapper but helps to hide the real DI Framework from the consumers. I hope this helps a little bit to understand your needed implementation.