【问题标题】:LINQ Pivot to Excel FileLINQ Pivot to Excel 文件
【发布时间】:2014-07-21 13:59:30
【问题描述】:

我有以下课程

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

我通过解析 .csv 文件创建了一个列表:

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

我想通过StartDateDur 为每个日期旋转列表,并通过 EPPlus 库将以下内容输出到 Excel 工作簿:

                              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

我认为最好的方法是使用 LINQ 旋转数据并通过以下方式将旋转后的数据传递给 EPPlus:

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

这是我目前所拥有的:

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();

但这并不是我想要的,因为该列表包含 2600 行,而如果我在 Excel 中执行此操作,它会合并到大约 10 行。我想我可能需要对 query 对象再执行一项操作。

编辑:使用下面@a-h 的答案,下面是完整的解决方案。请注意,此解决方案包括总计行和列,并且可能没有优化,因为我对 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();
}

【问题讨论】:

  • 当您按日期(例如 StartDate)分组时,您可能需要丢弃小时、分钟、秒等。最简单的方法是使用 StartDate.Date。我没有看到你的数据,但这可能解释了你的代码和 Excel 之间的差异。
  • 好点。我肯定会忽略这一点,但是日期列包含以下格式的日期d/mm/yyyy,所以它工作正常。真的,我现在只需要执行实际的支点,但我不知道该怎么做。
  • 你可以试试 ToString() 方法的日期时间:msdn.microsoft.com/en-us/library/8kb3ddd4.aspx

标签: c# linq epplus


【解决方案1】:

这是我对解决方案的快速破解。我在顶部使用了 AutoFixture 来创建测试数据。

第一个分组是按客户代码和项目描述,然后,这被转换为基于日期的查找,并且只取“Dur”。这允许稍后在代码中通过键检索 Sum。

我已经包含了一组写到制表符分隔值的写法,应该类似于写到 Excel 问题。

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;}
}

【讨论】:

  • 谢谢!这非常有效,我只需要更改输出以写入 Excel 工作簿,并且我必须获取每列和每行的总计以模拟真正的 Excel 数据透视表。完整测试后,我将发布完整代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多