public override async Task <double> TryGetCreditAsync( DbUserAccount userAccount, double amountRequested, DateTime?timeOfRequestUtc = null, CancellationToken cancellationToken = default(CancellationToken)) { if (Double.IsNaN(amountRequested) || amountRequested <= 0) { // You can't request a negative amount and requesting nothing is free return(0); } DateTime timeOfRequestOrNowUtc = timeOfRequestUtc ?? DateTime.Now; double creditRetrieved = 0; using (var context = new DbUserAccountContext(DbOptions)) { using (var dbContextTransaction = await context.Database.BeginTransactionAsync(cancellationToken)) { bool rolledBackDueToConcurrencyException = false; do { try { DbUserAccountCreditBalance balance = await context.DbUserAccountCreditBalances.Where(b => b.DbUserAccountId == userAccount.DbUserAccountId) .FirstOrDefaultAsync(cancellationToken: cancellationToken); if (balance == null) { creditRetrieved = Math.Min(amountRequested, userAccount.CreditLimit); double amountRemaining = userAccount.CreditLimit - creditRetrieved; context.DbUserAccountCreditBalances.Add( new DbUserAccountCreditBalance() { DbUserAccountId = userAccount.DbUserAccountId, ConsumedCreditsLastUpdatedUtc = timeOfRequestOrNowUtc, ConsumedCreditsLastValue = amountRemaining }); } else { double amountAvailable = Math.Max(0, userAccount.CreditLimit - DecayingDouble.Decay(balance.ConsumedCreditsLastValue, userAccount.CreditHalfLife, balance.ConsumedCreditsLastUpdatedUtc, timeOfRequestOrNowUtc)); if (amountAvailable > 0) { creditRetrieved = Math.Min(amountRequested, amountAvailable); } double amountRemaining = amountAvailable - creditRetrieved; balance.ConsumedCreditsLastValue = amountRemaining; balance.ConsumedCreditsLastUpdatedUtc = timeOfRequestOrNowUtc; } await context.SaveChangesAsync(cancellationToken); dbContextTransaction.Commit(); } catch (DbUpdateConcurrencyException) { dbContextTransaction.Rollback(); rolledBackDueToConcurrencyException = true; } } while (rolledBackDueToConcurrencyException); } } return(creditRetrieved); }
public void DecayOverTwoHalfLives() { double shouldBeVeryCloseTo1 = DecayingDouble.Decay(4, OneDay, FirstDayOfCentury, TwoDaysLater); Assert.InRange(shouldBeVeryCloseTo1, .99999, 1.000001); }