HangFire использует класс, отличный от того, в котором представлен метод для повторяющихся заданий.

Мы используем HangFire для одного из наших приложений для планирования отчетов.

CreateJobsOpenBidListReportHandler — это класс, наследуемый от класса AbstractCreateReportHandler.

      public class CreateJobsOpenBidListReportHandler : AbstractCreateReportHandler<CreateJobsOpenBidListReportCommand>
{
    public CreateJobsOpenBidListReportHandler(IJobManagementDBContext context, IMapper mapper,
        IJwtAuthenticationManager jwtAuthenticationManager, IErpRepository erpRepository, IStringLocalizer<Resource> localizer)
        : base(context, mapper, jwtAuthenticationManager, erpRepository, localizer)
    {
    }

    protected override ReportTypeEnum GetReportType()
    {
        return ReportTypeEnum.OPEN_BID_LIST;
    }

    protected override async Task<string> GenerateReportResults(string reportRequestJson, CancellationToken cancellationToken)
    {
        var selectionCriteria = JsonConvert.DeserializeObject<JobSelectionCriteria>(reportRequestJson);
        var jmasDbData = await ReportsProcessingRepository.GetJobsOpenBidListData(selectionCriteria, _context, cancellationToken);

        JobOpenBidListReportResultsDto responseDto = new();
        var resultsLines = responseDto.OpenBidListReportLines = _mapper.Map<List<JobOpenBidListReportResultLineDto>>(jmasDbData);

        // Query for all unique BillTo Customers
        var customerResults = resultsLines.Select(
            jobLine =>
                jobLine.BillToCustomerList.Select(cusLine => cusLine)
            ).SelectMany(cusLine => cusLine); // Flattens the list
        var customerErpIds = customerResults.Select(cusLine => cusLine.ErpId).Distinct();
        ErpCustomersReportHelper customerRecordsHelper = await ErpCustomersReportHelper.GetHelperInstance(customerErpIds, _erpRepo, cancellationToken);

        // Populate unmapped, derivative data
        foreach (var jobResultLine in resultsLines)
        {
            customerRecordsHelper.PopulateBillToCustomers(jobResultLine.BillToCustomerList,_erpRepo);
        }

        return JsonConvert.SerializeObject(responseDto);
    }
}

И это класс AbstractCreateReportHandler

      public abstract class AbstractCreateReportHandler<TCreateCommand> : IRequestHandler<TCreateCommand, ReportStatusDto> where TCreateCommand : IRequest<ReportStatusDto>
{
    protected readonly IJobManagementDBContext _context;
    protected readonly IMapper _mapper;
    protected readonly IJwtAuthenticationManager _jwtAuthenticationManager;
    protected readonly IReportBackgroundQueue _reportQueue;
    protected readonly IErpRepository _erpRepo;
    protected readonly IStringLocalizer<Resource> _localizer;

    // This class must be able to provide any service that might be used in generating a Report
    protected AbstractCreateReportHandler(IJobManagementDBContext context, IMapper mapper,
        IJwtAuthenticationManager jwtAuthenticationManager, IErpRepository erpRepo, IStringLocalizer<Resource> localizer)
    {
        _context = context;
        _mapper = mapper;
        _jwtAuthenticationManager = jwtAuthenticationManager;
        _erpRepo = erpRepo;
        _localizer = localizer;
    }

    // Returns the implementation's Report type
    protected abstract ReportTypeEnum GetReportType();

    // NOTE: Override this method with the actual specifics of your Report generation
    // It must take the serialized request for the report, and then return the serialized results.
    // These results will go into the DB's [Report].[Result] column, and must deserialize into the contents of the <xxxReportResponse><Report> property of its corresponding GET endpoint response
    protected abstract Task<string> GenerateReportResults(string reportRequestJson, CancellationToken cancellationToken);


    /********************** COMMON  REPORT  FUNCTIONALITY ****************************/

    public async Task<ReportStatusDto> Handle(TCreateCommand request, CancellationToken cancellationToken)
    {
        string requestString = JsonConvert.SerializeObject(request);
        ReportStatusDto response = new ReportStatusDto();
        CommonScheduleCommand scheduleDetails =  checkForScheduleDetails(request, cancellationToken);
        if (scheduleDetails!= null)
        {
            //Update this in the report schedule table
            string userId = _jwtAuthenticationManager.GetUser();
            ReportScheduleDetails details = _mapper.Map<ReportScheduleDetails>(scheduleDetails);
            details.CreatedByUser = userId;
            details.CreateDate = DateTimeOffset.Now;
            details.ReportTypeFK = Convert.ToInt32(scheduleDetails.ReportType);
            details.ReportScheduleTypeFK = Convert.ToInt32(scheduleDetails.ScheduleType);
            _context.ReportScheduleDetails.Add(details);
            await _context.SaveChangesAsync(cancellationToken);
            //Create an identifier for the newly created schedule.
            string reportId = details.Id.ToString();
            RecurringJob.AddOrUpdate(reportId+5, ()=> CreateNewReport(requestString, cancellationToken), "*/1 * * * *");
            response = new ReportStatusDto {ScheduleId = details.Id.ToString() };
        }
        else
        {
            response = await CreateNewReport(requestString, cancellationToken);
        }
        return response;
    }
    public async Task<ReportStatusDto> CreateNewReport(string requestString,CancellationToken cancellationToken)
    {
        // Initialize report record
        Report reportRecord = new(requestString, GetReportType(), _jwtAuthenticationManager.GetUser());
        await ReportsRepository.CreateReport(reportRecord, true, _context, cancellationToken);

        // Run the report synchronously and wait for it
        await RunReport(reportRecord.Id, cancellationToken);

        // Return updated report record
        Report finishedReportRecord = _context.Reports.AsNoTracking()
            .Include(report => report.ReportStateFKNavigation)
            .FirstOrDefault(r => r.Id == reportRecord.Id);
        ReportStatusDto responseDto = _mapper.Map<ReportStatusDto>(finishedReportRecord);

        return responseDto;
    }

    private CommonScheduleCommand checkForScheduleDetails(TCreateCommand request, CancellationToken cancellationToken)
    {
        string requestString = JsonConvert.SerializeObject(request);
        Type type = request.GetType();
        CommonScheduleCommand response = null;
        if(type == typeof(CreateJobsOpenBidListReportCommand))
        {
            CreateJobsOpenBidListReportCommand requestObject = JsonConvert.DeserializeObject<CreateJobsOpenBidListReportCommand>(requestString);
            response = requestObject.ScheduleDetails;

        }
        return response;
    }

    private async Task RunReport(int reportId, CancellationToken cancellationToken)
    {
        var reportRecord = (await ReportsRepository.GetReports(r => r.Id == reportId, _context, cancellationToken)).FirstOrDefault();
        if (reportRecord is null)
        {
            // Report not found
            // TODO: throw an error
            return;
        }

        // First, update the report to a RUNNING status
        await ReportsRepository.UpdateStartedReport(reportId, _context, cancellationToken);

        // Generate the report results
        string results;
        try
        {
            results = await GenerateReportResults(reportRecord.Request, cancellationToken);
        }
        catch (Exception ex)
        {
            // Report failed
            Exception innerException = ex;
            while (ex.InnerException is not null)
            {
                ex = ex.InnerException;
            }

            results = innerException.Message;
            await ReportsRepository.UpdateReportWithError(reportRecord, results, _context, cancellationToken);
            return;
        }

        // Report succeeded
        await ReportsRepository.UpdateFinishedReport(reportId, results, _context, cancellationToken);
    }
}

Каждый класс, унаследованный от «AbstractCreateReportHandler», имеет метод GenerateReportResults() для создания отчета о выигрыше.

Для каждого запроса отчета, поступающего через , сначала выполняется метод Handle в «AbstractCreateReportHandler», который внутренне вызывает метод CreateNewReport(..). Этот метод внутренне вызывает «GenerateReportResults()» соответствующих классов отчетов (в данном случае CreateJobsOpenBidListReportHandler)

Таким образом, этот «CreateNewReport ()» помечен как повторяющийся метод, который работает в соответствии с CRON.

Но HangFire хранит метод CreateNewReport() как часть CreateJobsOpenBidListReportHandler.cs и показывает InvalidOperationException

{"t":"Application.ApplicationBusiness.Reports.JobOpenBidList.Commands.CreateJobsOpenBidListReport.CreateJobsOpenBidListReportHandler, Application","m":"CreateNewReport","p":["System.String","System.Threading.CancellationToken, mscorlib "]}

HangFire выдает InvalidOperationException, как показано ниже.

System.InvalidOperationException: ТипApplication.ApplicationBusiness.Reports.JobOpenBidList.Commands.CreateJobsOpenBidListReport.CreateJobsOpenBidListReportHandlerне содержит метод с сигнатурой `CreateNewReport(String, CancellationToken)

0 ответов

Другие вопросы по тегам