protected virtual void OnDebtDealReceived( object o_debtDealsRegister, DebtDealReceivedEventData evData ) { /* * *** Notes on parallel execution *** * * Similar to DebtDealsRegister Add method, * consider only these cases: * * two unique involved id-s: * * same giver, same taker * * D1.giver = D2.taker, D1.taker = D2.giver * * Need to consider parallel changes of the same debt * in CurrentDebts table. * * Result - serialize always when * two deals have only two unique involved id-s. */ DebtDealRow deal = evData.Deal; DebtRow initialGiverDebtToTaker = this.dbc.CurrentDebts.Where(cd => cd.CreditorId == deal.TakerId && cd.DebtorId == deal.GiverId).FirstOrDefault(); if (initialGiverDebtToTaker == null) { initialGiverDebtToTaker = new DebtRow { CreditorId = deal.TakerId, DebtorId = deal.GiverId } } ; DebtRow initialTakerDebtToGiver = this.dbc.CurrentDebts.Where(cd => cd.CreditorId == deal.GiverId && cd.DebtorId == deal.TakerId).FirstOrDefault(); if (initialTakerDebtToGiver == null) { initialTakerDebtToGiver = new DebtRow { CreditorId = deal.GiverId, DebtorId = deal.TakerId } } ; DebtRow debtToChange; if (evData.Analysis.IsPayback) { debtToChange = initialGiverDebtToTaker; } else { debtToChange = initialTakerDebtToGiver; } decimal initialDebtAmount = debtToChange?.DebtTotal ?? 0m; decimal resultingDebtAmount; if (evData.Analysis.IsPayback) { resultingDebtAmount = Math.Max(0m, initialDebtAmount - deal.Amount); } else { resultingDebtAmount = initialDebtAmount + deal.Amount; } debtToChange.DebtTotal = resultingDebtAmount; if (resultingDebtAmount > DebtConstants.MaxZeroEquivalent) { if (this.dbc.CurrentDebts.Count(cd => cd.CreditorId == debtToChange.CreditorId && cd.DebtorId == debtToChange.DebtorId ) == 0 ) { this.dbc.CurrentDebts.Add(debtToChange); } } else { this.dbc.CurrentDebts.Remove(debtToChange); } this.dbc.SaveChanges(); }
/// <summary> /// /// </summary> /// <param name="evData"></param> /// <returns>new taker totals</returns> private void UpdateTotalsPerPerson(DebtDealReceivedEventData evData) { /* * *** Notes on parallel execution *** * * Need to consider: * * parallel changes of the same * giverTotals and takerTotals red from * CurrentTotalsPerPerson table. * * * parallel attempts to add new totals * for the same giver or taker. * * Result - serialize for operations in CurrentTotalsPerPerson * table with the same giverId or takerId. */ DebtDealRow deal = evData.Deal; PersonTotalsRow giverTotals = this.dbc.CurrentTotalsPerPerson .Where(ct => ct.PersonId == deal.GiverId) .FirstOrDefault() ?? new PersonTotalsRow(); giverTotals.PersonId = deal.GiverId; PersonTotalsRow takerTotals = this.dbc.CurrentTotalsPerPerson .Where(ct => ct.PersonId == deal.TakerId) .FirstOrDefault() ?? new PersonTotalsRow(); takerTotals.PersonId = deal.TakerId; if (evData.Analysis.IsPayback) { giverTotals.DueDebtsTotal = Math.Max( 0, giverTotals.DueDebtsTotal - deal.Amount); } else { giverTotals.HistoricallyCreditedInTotal += deal.Amount; takerTotals.DueDebtsTotal += deal.Amount; takerTotals.HistoricallyOwedInTotal += deal.Amount; takerTotals.HistoricalCountOfCreditsTaken++; } if (this.dbc.CurrentTotalsPerPerson .Count(ct => ct.PersonId == deal.GiverId) == 0 ) { this.dbc.CurrentTotalsPerPerson.Add(giverTotals); } if (this.dbc.CurrentTotalsPerPerson .Count(ct => ct.PersonId == deal.TakerId) == 0 ) { this.dbc.CurrentTotalsPerPerson.Add(takerTotals); } this.dbc.SaveChanges(); }
public long Add(DebtDealRow deal) { /* * *** Notes on parallel execution *** * * If two debt deals have 4 unique involved people id-s, * these same deals may be processed in parallel in this register * (but may need serialization while other registers handle * DebtDealAdded event). * * Need to consider validity of initialGiverDebtToTaker. * * Other cases: * three unique involved id-s: * same giver, different takers: => parallel-ok * * different givers, same taker: => parallel-ok * * two unique involved id-s: * same giver, same taker => serialize (case a) * * D1.giver = D2.taker, D1.taker = D2.giver * => serialize (case b) * * * Here, parallel execution has to be serialized when register * has not finished processing two deals which match one of these: * a) if giver and taker are the same for two deals, * later deal must wait till earlier one is fully processed. * (consider small and complete pay-back immediately * followed by a big credit - if D1 is processed after D2 * was fully processed, big credit could be considered * as mostly a GIFT, confusing it with a payback) * * b) if D1.giver = D2.taker, D1.taker = D2.giver * (consider pay and repay of the same amount - * in case of parallel execution of both deals, * payback could be missed and DB would get corrupt) */ long addedDebtDealId; using (var transaction = this.dbc.Database.BeginTransaction()) { this.dbc.DebtDeals.Add(deal); this.dbc.SaveChanges(); // need deal.Id addedDebtDealId = deal.Id; decimal initialGiverDebtToTaker = GiverDebtToTaker(); decimal?repayGiftAmount = null; if (initialGiverDebtToTaker > 0m) { repayGiftAmount = Math.Max(0m, deal.Amount - initialGiverDebtToTaker); } var analysisRow = new DebtDealAnalysisRow() { DebtDealId = deal.Id, IsPayback = initialGiverDebtToTaker > 0m }; this.dbc.DebtDealsAnalysis.Add(analysisRow); this.dbc.SaveChanges(); DebtDealReceived?.Invoke(this, new DebtDealReceivedEventData() { Deal = deal, Analysis = analysisRow, RepayGiftAmount = repayGiftAmount }); transaction.Commit(); } DebtDealAdded?.Invoke(this, new DebtDealAddedEventData() { AddedDebtDealId = addedDebtDealId }); return(addedDebtDealId); decimal GiverDebtToTaker() { return(this.dbc.CurrentDebts.Where(cd => cd.CreditorId == deal.TakerId && cd.DebtorId == deal.GiverId ).Select(cd => cd.DebtTotal).FirstOrDefault()); } }