I saw similar problems on the internet, but couldn't really fit any of them to my code.
I'm running two hangfire jobs and sometimes they have to run at the same time.
RecurringJob.AddOrUpdate(()=>abill.CheckUserPayment(),Cron.Minutely);
RecurringJob.AddOrUpdate("CalculateUserCharge",()=>abill.CalculateUserCharge(DateTime.Today.AddDays(-1)),Cron.Daily(12,37),TimeZoneInfo.Utc);
RecurringJobs are launched from Configure() method from startup and I pass AccountBilling abill as a parameter.
AccountBilling.cs (whole code is not present)
public readonly EntityContext context;
private TBCPaymentOptions _tbcPaymentOptions = null;
public AccountBilling (EntityContext _context) {
context = _context;
}
public AccountBilling (EntityContext _context, IOptions<TBCPaymentOptions> tbcPaymentOptions) {
context = _context;
this._tbcPaymentOptions = tbcPaymentOptions.Value;
}
public void Save () {
try {
context.SaveChanges ();
} catch (Exception e) {
Console.WriteLine (e);
throw;
}
}
public void CalculateUserCharge (DateTime date) {
var latestJob = context.JobLogs.Include (c => c.JobStatus).OrderByDescending (c => c.StartDate).Where (c => c.JobId == (int) JobEnum.CloseDay).FirstOrDefault ();
var jobLog = new JobLog ();
jobLog.JobId = (int) JobEnum.CloseDay;
jobLog.JobStatusID = (int) JobStatusEnum.Active;
jobLog.StartDate = DateTime.Now;
context.Add (jobLog);
this.Save ();
Console.WriteLine ("Starting...");
if (latestJob != null && latestJob.JobStatusID == (int) JobStatusEnum.Active) {
jobLog.JobStatusID = (int) JobStatusEnum.Canceled;
jobLog.EndDate = DateTime.Now;
context.Update (jobLog);
context.SaveChanges ();
} else {
try {
var result = new List<GetActiveUserPackagesForOpenBillingPeriodResult> ();
using (var conn = new NpgsqlConnection (context.ConnectionString)) {
conn.Open ();
using (var cmd = new NpgsqlCommand ("\"GetActiveUserPackagesForOpenBillingPeriod\"", conn)) {
Console.WriteLine ("გავუშვი command");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue ("somedate", DateTime.Today);
var reader = cmd.ExecuteReader ();
string x = DBNull.Value.Equals (reader) ? " " : reader.ToString ();
if (x != null) {
while (reader.Read ()) {
result.Add (
new GetActiveUserPackagesForOpenBillingPeriodResult {
Amount = (decimal) reader["Amount"],
PackageID = (int) reader["PackageID"],
UserID = (int) reader["UserID"],
AccountID = (int) reader["AccountID"],
UserPackageStartDate = (DateTime) reader["UserPackageStartDate"],
}
);
}
}
conn.Close ();
}
var groupByResults = result.GroupBy (c => c.AccountID).Select (a => new {
accountId = a.Key,
lines = a.ToList ()
});
foreach (var group in groupByResults) {
var transactionHeader = new TransactionHeader () {
TransactionHeaderTypeID = (int) TransactionHeaderTypeEnum.Charge,
Date = date,
CorrectionDescription = null,
AccountID = group.accountId
};
foreach (var lineItem in group.lines) {
transactionHeader.TransactionLines.Add (new TransactionLine () {
UserID = lineItem.UserID,
PackageID = lineItem.PackageID,
Amount = this.CalculateUserChargeMethod (date, lineItem.Amount, lineItem.UserPackageStartDate)
});
}
transactionHeader.TotalAmount = transactionHeader.TransactionLines.Sum (c => c.Amount);
this.context.TransactionHeaders.Add (transactionHeader);
context.Add (transactionHeader);
this.Save ();
}
jobLog.EndDate = DateTime.Now;
jobLog.JobStatusID = (int) JobStatusEnum.Inactive;
context.Update (jobLog);
this.Save ();
ClosePeriodOnEndOfMonth (date, conn);
}
} catch (Exception ex) {
jobLog.EndDate = DateTime.Now;
jobLog.JobStatusID = (int) JobStatusEnum.Canceled;
jobLog.Comment = ex.ToString ();
context.Update (jobLog);
this.Save ();
//throw ex;
}
}
..........
public void CheckUserPayment () {
var Cert = new X509Certificate2 ("cert.p12", _tbcPaymentOptions.TBCPayCertificatePassword, X509KeyStorageFlags.MachineKeySet);
var time = DateTime.Now.AddMinutes (-5);
var latestJob = context.JobLogs.Include (c => c.JobStatus).OrderByDescending (c => c.StartDate).Where (c => c.JobId == (int) JobEnum.CheckPayment).FirstOrDefault ();
var jobLog = new JobLog ();
jobLog.JobId = (int) JobEnum.CheckPayment;
jobLog.JobStatusID = (int) JobStatusEnum.Active;
jobLog.StartDate = DateTime.Now;
context.Add (jobLog);
this.Save ();
if (latestJob != null && latestJob.JobStatusID == (int) JobStatusEnum.Active) {
jobLog.JobStatusID = (int) JobStatusEnum.Canceled;
jobLog.EndDate = DateTime.Now;
context.Update (jobLog);
this.Save ();
} else {
try {
var processingPayments = context.Payments.Where (c => c.AcceptanceAct.AcceptanceActStatusID == (int) AcceptanceActStatusEnum.Processing && c.CreatedDate < time).ToList ();
System.Console.WriteLine ("IN METHOD");
foreach (var item in processingPayments) {
System.Console.WriteLine ("Job Started");
this.CheckUserPaymentMethod (item.BankPaymentCode, item, _tbcPaymentOptions.AppPlatformIP, _tbcPaymentOptions.MerchantURL, Cert);
}
Console.WriteLine ("Job Done");
jobLog.JobStatusID = (int) JobStatusEnum.Inactive;
jobLog.EndDate = DateTime.Now;
context.Update (jobLog);
this.Save ();
//this.Save ();
} catch (Exception ex) {
jobLog.JobStatusID = (int) JobStatusEnum.Canceled;
jobLog.EndDate = DateTime.Now;
jobLog.Comment = ex.ToString ();
context.Update (jobLog);
this.Save ();
}
}
}
Startup.cs (whole code is not present):
services.AddEntityFrameworkNpgsql ()
.AddDbContext<EntityContext> (
options => options.UseNpgsql (connectionString)
);
The problem is that abill
is effectively being used as if it were a singleton object. This is going to cause you problems, particularly with DbContext
operations.
You need to change that and allow Hangfire to leverage DI properly. First give your AccountBilling
class an interface to use for injection:
public class AccountBilling : IAccountBilling
{
// snip
}
Now add it to your container:
services.AddScoped<IAccountBilling, AccountBilling>();
Now in your startup code, instead of using the abill
object, inject it properly:
RecurringJob.AddOrUpdate<IAccountBilling>(a => a.CheckUserPayment(), Cron.Minutely);