Ejemplo n.º 1
0
        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());
            }
        }