user1077685
user1077685

Reputation:

LINQ Pivot to Excel File

I have the following class

public class DaysCoverReport
{
    public string SupplierCode { get; set; }
    public string CustomerCode { get; set; }
    public DateTime StartDate { get; set; }
    public int Dur { get; set;}
}

I've created a list by parsing a .csv file:

IEnumerable<DaysCoverReport> daysCoverList = 
           DaysCoverReport.ParseDaysCoverReport(file).ToList();

I want to pivot the list by StartDate and sum Dur for each date, and output the following to an Excel workbook via the EPPlus library:

                              Start Date
Customer Code    Item Desc.    7/16/2014    7/17/2014    7/18/2014
1234-6789        Test Item A          10            2            3
45-9003          Test Item B           5            1            8

I assume the best method would be to pivot the data using LINQ and pass the pivoted data to EPPlus via the following:

worksheet.Cells["A1"].LoadFromCollection(pivotedDataList, true)

Here is what I have so far:

var query = (from d in daysCoverList
                group d by new {d.ItemDescription, d.CustomerCode, d.StartDate}
                into grp
                select new
                {
                    grp.Key.CustomerCode,
                    grp.Key.ItemDescription,
                    grp.Key.StartDate,
                    Dur = grp.Sum(d => d.Dur)
                }).ToList();

But it's not exactly what I want, as the list contains 2600 rows, whereas if I perform this operation in Excel, it consolidates down to ~10 rows. I think I might need to perform one more operation on the query object.

EDIT: Using @a-h's answer below, below is the full solution. Note that this solution includes a grand total row and column and is probably not optimized as I'm fairly new to LINQ.

IEnumerable<DaysCoverReport> daysCoverList = DaysCoverReport.ParseDaysCoverReport(file).ToList();

//Group by Customer Code and Item Description
var grouped = daysCoverList.GroupBy(d => new
{
    d.CustomerCode,
    d.ItemDescription
})
.Select(grp => new
{
    grp.Key.CustomerCode,
    grp.Key.ItemDescription,
    GroupedByDate = grp.ToLookup(g => g.StartDate.Date, g => g.Dur),
    DurSum = grp.Sum(g => g.Dur)
})
.OrderBy(grp => grp.CustomerCode)
.ToList();

var totalsByDate = daysCoverList.GroupBy(d => new
{
    d.StartDate.Date,
})
.Select(d => new
{
   d.Key.Date,
   GroupedByDate = d.ToLookup(g => g.StartDate.Date, g => g.Dur),
})
.ToList();

//Get distinct list of dates for pivot columns
var columns = grouped
   .SelectMany(grp => grp.GroupedByDate.Select(g => g.Key))
    .Distinct()
    .OrderBy(d => d).ToList();

using (ExcelPackage package = new ExcelPackage(new FileInfo(saveFileName)))
{
    ExcelWorksheet pivot = package.Workbook.Worksheets.Add("Traffic Light Report");

    pivot.Cells["A1"].Value = "Sum of Dur";
    pivot.Cells["A1"].Style.Font.Bold = true;
    pivot.Cells["C1"].Value = "Start Date";
    pivot.Cells["C1"].Style.Font.Bold = true;

    for(int i = 1; i <= columns.Count + 3; i++)
    {
        var headerCell = pivot.Cells[2, i];

        if (i == 1)
            headerCell.Value = "Customer Code";
        else if (i == 2)
           headerCell.Value = "Item Description";
        else if (i == columns.Count + 3)
            headerCell.Value = "Grand Total";
        else
            headerCell.Value = columns[i - 3].ToString("MM/dd/yyyy");

        headerCell.Style.Font.Bold = true;
    }

    int j = 3;
    foreach (var line in grouped)
    {
        for (int i = 1; i <= columns.Count + 3; i++)
        {
            var cell = pivot.Cells[j, i];

            if (i == 1)
                cell.Value = line.CustomerCode;
            else if (i == 2)
                cell.Value = line.ItemDescription;
            else if (i == columns.Count + 3)
                cell.Value = line.DurSum;
            else
                cell.Value = line.GroupedByDate.Contains(columns[i - 3]) ? line.GroupedByDate[columns[i - 3]].Sum() : 0;
         }

        j++;
    }

    //Write total row
    var totalRowCell = pivot.Cells[j, 1];
    totalRowCell.Value = "Grand Total";
    totalRowCell.Style.Font.Bold = true;

    int k = 3;
    foreach (var date in totalsByDate)
    {
        totalRowCell = pivot.Cells[j, k];
        totalRowCell.Value = date.GroupedByDate.Contains(columns[k - 3]) ? date.GroupedByDate[columns[k - 3]].Sum() : 0;

         k++;
    }

    pivot.Cells[j, k].Value = daysCoverList.Sum(d => d.Dur);

   //Apply conditional formatting for Traffic Light
    ExcelAddress formatRangeAddress = new ExcelAddress(3, 3, grouped.Count + 2, columns.Count + 2);

    var red = pivot.ConditionalFormatting.AddLessThanOrEqual(formatRangeAddress);
    red.Style.Fill.BackgroundColor.Color = System.Drawing.Color.Red;
    red.Formula = "0";

    var amber = pivot.ConditionalFormatting.AddBetween(formatRangeAddress);
    amber.Style.Fill.BackgroundColor.Color = System.Drawing.Color.Orange;
    amber.Formula = "0";
    amber.Formula2 = "15";

    var green = pivot.ConditionalFormatting.AddBetween(formatRangeAddress);
    green.Style.Fill.BackgroundColor.Color = System.Drawing.Color.Green;
    green.Formula = "15";
    green.Formula2 = "45";

    var blue = pivot.ConditionalFormatting.AddGreaterThan(formatRangeAddress);
    blue.Style.Fill.BackgroundColor.Color = System.Drawing.Color.Blue;
    blue.Formula = "45";

    pivot.Cells[pivot.Dimension.Address].AutoFitColumns();

    package.Save();
}

Upvotes: 0

Views: 550

Answers (1)

a-h
a-h

Reputation: 4284

Here's my quick hack at a solution. I've used AutoFixture at the top to create test data.

The first grouping is by Customer Code and Item Description, then, this is converted into a lookup based on the Date, and taking only the "Dur". This allows the Sum to be retrieved by key later on in the code.

I've included a set of writing out to tab separated values which should be similar to writing to Excel problem.

void Main()
{
    var fixture = new Fixture();

    var daysCoverList = fixture.CreateMany<DaysCoverReport>(10);

    // Group by customer code and description.
    var grouped = daysCoverList.GroupBy(dcl => new 
    { 
        CustomerCode = dcl.CustomerCode, 
        ItemDescription = dcl.ItemDescription, 
    })
    .Select(grp => new 
    { 
        CustomerCode = grp.Key.CustomerCode, 
        ItemDescription = grp.Key.ItemDescription, 
        GroupedByDate = grp.ToLookup(g => g.StartDate.Date, g => g.Dur)
    });

    var columns = grouped
        .SelectMany(grp => grp.GroupedByDate.Select(g => g.Key))
        .Distinct()
        .OrderBy(d => d);

    // Write column headings.
    Console.Write("Customer Code\t");
    Console.Write("Item Desc.\t");
    foreach(var dateColumn in columns)
    {
        Console.Write(dateColumn.ToString() + "\t");
    }
    Console.WriteLine();

    // Write values.
    foreach(var line in grouped)
    {
        Console.Write(line.CustomerCode);
        Console.Write("\t");
        Console.Write(line.ItemDescription);
        Console.Write("\t");

        foreach(var dateColumn in columns)
        {
            if(line.GroupedByDate.Contains(dateColumn))
            {
                Console.Write(line.GroupedByDate[dateColumn].Sum());
            }
            else
            {
                Console.Write(0);
            }

            Console.Write("\t");
        }
        Console.WriteLine();
    }
}

public class DaysCoverReport
{
    public string SupplierCode { get; set; }
    public string CustomerCode { get; set; }
    public DateTime StartDate { get; set; }
    public int Dur { get; set;}
    public string ItemDescription { get;set;}
}

Upvotes: 1

Related Questions