/// <summary>
        /// call this from your client projects to queue a job
        /// </summary>
        public static async Task <TKey> QueueJobAsync <TJob, TRequest, TKey>(this QueueClient queueClient,
                                                                             string userName, TRequest request,
                                                                             JobRepositoryBase <TJob, TKey> repository,
                                                                             ILogger logger) where TJob : BackgroundJobInfo <TKey>, new()
        {
            // this enables visibility of the job over its lifetime (enabling dashboards, notifications, retry and tracking features)
            var job = await SaveJobAsync(repository, userName, request);

            logger.LogDebug($"Job Id {job.Id} was saved with request data {job.RequestData}");

            try
            {
                // this queues the job for execution
                await SendJsonAsync(queueClient, job.Id);

                logger.LogDebug($"Job Id {job.Id} was added to queue {queueClient.Name}");
            }
            catch (Exception exc)
            {
                // if queuing failed, then we need to indicate that in the stored record
                logger.LogError($"Error queuing Job Id {job.Id} on {queueClient.Name}: {exc.Message}");
                job.Status = JobStatus.Aborted;
                await repository.SaveAsync(job);
            }

            return(job.Id);
        }
        public static async Task <TJob> SaveJobAsync <TJob, TRequest, TKey>(
            this JobRepositoryBase <TJob, TKey> repository, string userName, TRequest request)
            where TJob : BackgroundJobInfo <TKey>, new()
        {
            var job = new TJob();

            job.UserName    = userName;
            job.RequestType = typeof(TRequest).Name;
            job.RequestData = JsonSerializer.Serialize(request);
            job.Status      = JobStatus.Pending;
            job.Started     = null;
            job.Completed   = null;
            job.Created     = DateTime.UtcNow;

            return(await repository.SaveAsync(job));
        }
        /// <summary>
        /// call this in your QueueTrigger Azure Function. The id will be the queue message data
        /// </summary>
        public async Task <TResult> ExecuteAsync(TKey id)
        {
            TResult result = default;
            TJob    job    = default;

            var errorContext = "starting";

            try
            {
                job = await _repository.GetAsync(id);

                if (job == null)
                {
                    throw new Exception($"Job Id {id} not found.");
                }
                if (!job.RequestType.Equals(typeof(TRequest).Name))
                {
                    throw new Exception($"Job Id {id} request type {job.RequestType} does not match job runner request type {typeof(TRequest).Name}");
                }

                var request = JsonSerializer.Deserialize <TRequest>(job.RequestData);

                try
                {
                    errorContext = "executing";
                    job.RetryCount++;
                    job.ExceptionData = null;
                    job.Status        = JobStatus.Running;
                    job.Started       = DateTime.UtcNow;
                    await _repository.SaveAsync(job);

                    if (PostStatusUpdates)
                    {
                        await OnStatusUpdatedAsync(id, job.Status);
                    }

                    result = await OnExecuteAsync(request);

                    job.ResultData = JsonSerializer.Serialize(result);
                    job.Status     = JobStatus.Succeeded;
                }
                catch (Exception exc)
                {
                    errorContext = "failing";
                    Logger.LogError(exc, $"Job Id {job.Id} failed: {exc.Message}");
                    job.Status        = JobStatus.Failed;
                    job.ExceptionData = JsonSerializer.Serialize(new
                    {
                        message    = exc.FullMessage(),
                        data       = exc.Data,
                        stackTrace = exc.StackTrace
                    });
                }
                finally
                {
                    errorContext  = "finishing";
                    job.Completed = DateTime.UtcNow;
                    job.Duration  = Convert.ToInt32(job.Completed.Value.Subtract(job.Started.Value).TotalSeconds);
                    await _repository.SaveAsync(job);

                    if (PostStatusUpdates)
                    {
                        await OnStatusUpdatedAsync(id, job.Status);
                    }
                }
            }
            catch (Exception exc)
            {
                if (job != null)
                {
                    job.Status        = JobStatus.Aborted;
                    job.ExceptionData = JsonSerializer.Serialize(new
                    {
                        errorContext,
                        message = exc.FullMessage()
                    });
                    await _repository.SaveAsync(job);

                    if (PostStatusUpdates)
                    {
                        await OnStatusUpdatedAsync(id, job.Status);
                    }
                }

                Logger.LogError(exc, $"While {errorContext}: {exc.Message}");
            }

            return(result);
        }