Dave
Dave

Reputation: 253

Grouping and sum

I have a list as follows which will contain the following poco class.

public class BoxReportView
{
    public DateTime ProductionPlanWeekStarting { get; set; }
    public DateTime ProductionPlanWeekEnding { get; set; }
    public string BatchNumber { get; set; }
    public string BoxRef { get; set; }
    public string BoxName { get; set; }
    public decimal Qty { get; set; }

    public FUEL_KitItem KitItem { get; set; }
    public decimal Multiplier { get; set; }
}

I am wanting to group the report and sum it by using the BoxName and also the Qty SO I tried the following

var results = from line in kitItemsToGroup
              group line by line.BoxName into g
              select new BoxReportView
              {
                  BoxRef = g.First().BoxRef,
                  BoxName = g.First().BoxName,                                          
                  Qty = g.Count()
               };

In My old report I was just doing this

var multiplier = finishedItem.SOPOrderReturnLine.LineQuantity - 
                 finishedItem.SOPOrderReturnLine.StockUnitDespatchReceiptQuantity;

foreach (KitItem kItem in kitItems.Cast<KitItem().Where(z => z.IsBox == true).ToList())
{
   kittItemsToGroup.Add(new BoxReportView() {
       BatchNumber = _batchNumber,
       ProductionPlanWeekEnding = _weekEndDate,
       ProductionPlanWeekStarting = _weekStartDate,
       BoxRef = kItem.StockCode,
       KitItem = kItem,
       Multiplier = multiplier,
       Qty = kItem.Qty });
   }
}

Then I was just returning

return kitItemsToGroup;

But as I am using it as a var I cannot what is best way to handle the grouping and the sum by box name and qty.

Upvotes: 1

Views: 101

Answers (1)

Harald Coppoolse
Harald Coppoolse

Reputation: 30512

Whether it is the best way depends upon your priorities. Is processing speed important, or is it more important that the code is easy to understand, easy to test, easy to change and easy to debug?

One of the advantages of LINQ is, that it tries to avoid enumeration of the source more than necessary.

Are you sure that the users of this code will always need the complete collection? Can it be, that now, or in near future, someone only wants the first element? Or decides to stop enumeration after he fetched the 20th element and saw that there was nothing of interest for him?

When using LINQ, try to return IEnumerable<...> as long as possible. Let only the end-user who will interpret your LINQed data decide whether he wants to take only the FirstOrDefault(), or Count() everything, or put it in a Dictionary, or whatever. It is a waste of processing power to create a List if it is not going to be used as a List.

your LINQ code and your foreach do some completely different things. Alas it is quite common here on StackOverflow for people to ask for LINQ statements without really specifying their requirements. So I'll have to guess something in between your LINQ statement and your foreach.

Requirement Group the input sequence of kitItems, which are expected to be Fuel_KitItems into groups of BoxReportViews with the same BoxName, and select several properties from every Fuel_KitItem in each group.

var kitItemGroups = kitItems
    .Cast<Fuel_KitItem>()       // only needed if kitItems is not IEnumerable<Fuel_KitItem>
    // make groups of Fuel_KitItems with same BoxName:
    .GroupBy(fuelKitItem => fuelKitItem.BoxName,

        // ResultSelector, take the BoxName and all fuelKitItems with this BoxName:
        (boxName, fuelKitItemsWithThisBoxName) => new
        {
            // Select only the properties you plan to use:
            BoxName = boxName,

            FuelKitItems = fuelKitItemsWithThisBoxName.Select(fuelKitItem => new
            {
                 // Only Select the properties that you plan to use
                 BatchNumber = fuelKitItem.BatchNumber,
                 Qty = fuelKitItem.Qty,
                 ...

                 // Not needed, they are all equal to boxName:
                 // BoxName = fuelKitItem.BoxName
             })
            // only do ToList if you are certain that the user of the result
            // will need the complete list of fuelKitItems in this group
            .ToList(),
         });

Usage:

var kitItemGroups = ...

// I only need the KitItemGroups with a BoxName starting with "A"
var result1 = kitItemGroups.Where(group => group.BoxName.StartsWith("A"))
    .ToList();

// Or I only want the first three after sorting by group size
var result2 = kitItemGroups.OrderBy(group => group.FuelKitItems.Count())
    .Take(3)
    .ToList();

Efficiency Improvements: As long as you don't know how your LINQ will be used, don't make it a List. If you know that chances are high that the Count of group.FuelKitItems is needed, to a ToList

Upvotes: 3

Related Questions