简体   繁体   中英

How to write this LINQ for applying business logic?

I am trying to apply some business logic.
My question about usage of LINQ on the following object models to apply the business logic. I have the following objects populated accordingly:

public class Waiver
{
   public string Id { get; set; }
   public int Type { get; set; }
   public decimal Amount { get; set; }
}

Business Logic to be applied:

1.) Apply Line Item waiver
If LineItem Waiver [Type] is 1111 deduct the LineItem Waiver amount off Unit Price
If LineItem Waiver [Type] is 2222 deduct LineItem WaiverAmount as a Percentage Off Unit Price
If LineItem Waiver [Type] is 3333 deduct the LineItem Waiver amount off (Line Price = Qty * Unit Price)
If LineItem Waiver [Type] is 4444 deduct LineItem Waiver Amount as a Percentage Off Line Price

2.) Apply Order waiver
If Order Waiver [Type] is 4444 deduct Order Waiver amount Off Total Order Price after applying LineItem Waivers
If Order Waiver [Type] is 8888 deduct Order Waiver amount as Percentage Off Order Price after applying LineItem Waivers

What is the best way to achieve this?

GetWaivedPrice(decimal unitPrice, int qty, IEnumerable<Waiver> waiver)

Can GetWaivedPrice be written as a single LINQ method with appropriate mapping for all the discount types?

This is what I am trying to achieve, preferably as a well written LINQ method:

private decimal GetWaivedPrice(decimal unitPrice, int qty, 
                                           IEnumerable<Waiver> waiver)
        {
            //Pseudo code shown for clarifying intent
            decimal waivedLineItemAmount = 0m;

            if waiver.Select(d => d.Type == 1111)
            //then apply the business logic on the unit price accordingly        
            if waiver.Select(d => d.Type == 2222)
            //then apply the business logic on the unit price accordingly  
            if waiver.Select(d => d.Type == 3333)
            //then apply the business logic on the unit price accordingly  

            return waivedLineItemAmount;

        }

I don't see the case for LINQ here. Just apply each Waver in turn.

    private decimal GetWaivedPrice(decimal unitPrice, int qty, 
                                       IEnumerable<Waiver> waiver)
    {
        //Pseudo code shown for clarifying intent
        decimal waivedLineItemAmount = 0m;

        //apply all waivers
        foreach (var w in waiver) {
         switch (w.Type) { 
          case 1111:
           waivedLineItemAmout += someComputation();
           break;
          case 2222:
           waivedLineItemAmout += someComputation();
           break;
          case 3333:
           waivedLineItemAmout += someComputation();
           break;
         }
        }

        return waivedLineItemAmount;
    }

You can formulate this with Enumerable.Aggregate if you insist on LINQ and a purely functional style but a simple loop seems just fine here.

I am not seeing the need for linq per-se, but maybe Object Oriented design.

I would instead have the waiver handle the business logic in a method call such as what you have now.

public class Waiver
{
   public string Id { get; set; }
   public int Type { get; set; }
   public decimal Amount { get; set; }

   public decimal GetWaivedPrice(decimal unitPrice, int qty) { ... }

}

By doing that it would facilitate any future linq projections or operations such as this grouping and of course centralize the business logic for future maintenance.

var groupedResult = myWaivers.Select(wv => new
    {
       Type = wv.Type,
       WaivedPrice = wv.GetWaivedPrice( unitPrice, qty)
    } )
                             .GroupBy(wv => wv.Type);

How about that:

private static Dictionary<int, Func<decimal, int, Waiver, decimal>> _logic
    = new Dictionary<int, Func<decimal, int, Waiver, decimal>>() {
        { 2222, (a, q, w) => a + w.Amount },
        { 3333, (a, q, w) => a + w.Amount },
        { 3333, (a, q, w) => a + w.Amount }
    };

private static decimal GetWaivedPrice(decimal unitPrice, int qty, 
                                   IEnumerable<Waiver> waiver)
{
    return waiver.Aggregate(0m, (a, s) => _logic[s.Type](a, qty, s), a => a);
}

Of course you have to update _logic dictionary with your discount logic to make it work.

Business rules are isolated into separate concerns (deductions and strategy) and it's now pretty LINQ-ish!

// Create a data structure to model the deductions themselves
public class LineItemDeduction
{
    public decimal UnitPriceAmount { get; set; }
    public decimal UnitPricePercentage { get; set; }
    public decimal LinePriceAmount { get; set; }
    public decimal LinePricePercentage { get; set; }

    // Assumed that waivers are distinct and are not composed together, only applied on the listed price.
    public decimal CalculateWaivedPrice(decimal unitPrice, int qty)
    {
        return ((unitPrice - UnitPriceAmount - (unitPrice * UnitPricePercentage)) * qty) - LinePriceAmount - (unitPrice * qty * LinePricePercentage);
    }
}

// Calculate the deductions
private LineItemDeduction CalculateLineItemDeductionStrategy(LineItemDeduction deduction, Waiver waiver)
{
    switch (waiver.Type) { 
      case 1111:
       deduction.UnitPriceAmount += waiver.Amount;
       break;
      case 2222:
       deduction.UnitPricePercentage += waiver.Amount;
       break;
      case 3333:
       deduction.LinePriceAmount += waiver.Amount;
       break;
      case 4444:
       deduction.LinePricePercentage += waiver.Amount;
       break;
     }

     return deduction;
}

// Extension method only for LineItem but it's the same principle for order waivers
public static decimal GetWaivedPrice(this IEnumerable<Waiver> waivers, decimal unitPrice, int qty, Func<LineItemDeduction, Waiver, LineItemDeduction> deductionStrategy)
{
    return waivers.Aggregate(
        new LineItemDeduction(),
        deductionStrategy,
        d => d.CalculateWaivedPrice(unitPrice, qty)
    );
}

// Now to get the waived price
var waivedPrice = waivers.GetWaivedPrice(unitPrice, qty, CalculateLineItemDeductionStrategy);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM