entity-framework-coreabp-frameworkquartz

abp quartz dynamic job,unitofwork attribute not working


I need to read Quartz job configurations from a JSON file and generate job instances using a specific job template (configuring trigger and IJobDetail). Here is the code for dynamically creating jobs:

public class JobRunner
{
    private readonly IConfiguration _configuration;
    private readonly ISchedulerFactory _jobSchedularFactory;
    private readonly IJobFactory _jobFactory;
    public JobRunner(IConfiguration configuration, ISchedulerFactory jobSchedularFactory, IJobFactory jobFactory)
    {
        _configuration = configuration;
        _jobSchedularFactory = jobSchedularFactory;
        _jobFactory = jobFactory;
    }


    public async Task StartAsync()
    {
        IScheduler scheduler=await _jobSchedularFactory.GetScheduler();
        scheduler.JobFactory=_jobFactory;
        await CreateJob(scheduler);
    }

    private async Task CreateJob(IScheduler scheduler)
    {
        var jobs= _configuration.GetSection("Jobs").Get<List<JobOptions>>()??new ();
        foreach (var item in jobs)
        {
            if (!item.Enabled)
            {
                continue;
            }
            
            var jobData=new JobDataMap();
            jobData.Add("LineId",item.LineId);
            jobData.Add("DataSourceName",item.DataSourceName);
            IJobDetail job=JobBuilder.Create<DemoJob>()
                .WithIdentity(item.LineId!)
                .SetJobData(jobData)
                .Build();

            ITrigger trigger=TriggerBuilder.Create()
                .WithIdentity(item.LineId)
                .WithCronSchedule(item.Corn)
                .Build();

           
            await scheduler.ScheduleJob(job,trigger);
            await scheduler.Start();
        }
    }
}

The configuration in the module is as follows:

[DependsOn(
    typeof(AbpQuartzModule)
)]
public class BackJobWorkerModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var config= context.Services.GetConfiguration();
        context.Services.AddSingleton<IConfiguration>(config);
        context.Services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
        context.Services.AddSingleton<IJobFactory, LineJobFactory>();
        context.Services.AddSingleton<JobRunner>();
    }


    /// <inheritdoc />
    public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
    {
        await base.OnPostApplicationInitializationAsync(context);
        var runner = context.ServiceProvider.GetRequiredService<JobRunner>();
        await runner.StartAsync();
    }
}

The job JSON information configured in the configuration file was correctly used by the runner to generate job instances, and a custom JobFactory was implemented. The Job template code was extracted from the container. Everything runs smoothly, but when trying to access the database using EF Core, an error of being unable to access the disposed DbContext occurred. Using UnitOfWorkAttribute did not have any effect; only manually using UnitOfWorkManager.Begin works correctly. Is there any other way to resolve this issue?

I searched through relevant documents and forums, but couldn't find a good solution.


Solution

  • Make it a virtual method:

    [UnitOfWork]
    public virtual async Task Execute(IJobExecutionContext context)
    {
        // ...
    }
    

    From https://docs.abp.io/en/abp/8.1/Unit-Of-Work:

    • If you are not injecting the service over an interface (like IMyService), then the methods of the service must be virtual (otherwise, dynamic proxy / interception system can not work).