【问题标题】:How do I iterate through rows in an excel table using epplus?如何使用 epplus 遍历 Excel 表中的行?
【发布时间】:2014-02-12 23:18:42
【问题描述】:

我是 epplus 的新手,我正在尝试从 Excel 表中读取一些值。

这是我目前所拥有的:

var fileInfo = new FileInfo(filename);
using(var excelPackage = new OfficeOpenXml.ExcelPackage(fileInfo))
{
    foreach (var sheet in excelPackage.Workbook.Worksheets)
    {
        foreach (ExcelTable table in sheet.Tables)
        {
             foreach(var row in table.Rows)  // <-- !!
             { ... }
        }
    }
}

但是,现在我被难住了,因为 ExcelTable 只有一个 Columns 属性,但没有我预期的 Rows 属性。我在库中的任何对象上都找不到 Rows 属性。

如何遍历表格,逐行读取?

【问题讨论】:

  • 您找到解决方案了吗?
  • 我最终使用了Microsoft.Office.Interop.Excel 类。它们支持读取 Table 对象。您只需要知道在 API 中,Excel 工作表中的表格称为 ListObjects。
  • 我要离开 Interop。有几个问题,如果文件损坏,它就无法处理。我已经用我找到的链接为此提供了答案。

标签: c# excel epplus


【解决方案1】:

在寻找相同问题的帮助时,我偶然发现了这个link。它当然对我有用!绝对比使用互操作对象更好。 :)

我稍微调整了一下:

var package = new ExcelPackage(new FileInfo("sample.xlsx"));

ExcelWorksheet workSheet = package.Workbook.Worksheets[0];
var start = workSheet.Dimension.Start;
var end = workSheet.Dimension.End;
for (int row = start.Row; row <= end.Row; row++)
{ // Row by row...
    for (int col = start.Column; col <= end.Column; col++)
    { // ... Cell by cell...
        object cellValue = workSheet.Cells[row, col].Text; // This got me the actual value I needed.
    }
}

【讨论】:

  • 你已经把计数器放倒了,你应该这样做:workSheet.Cells[j, i],因为语法是[row, column],但在你的情况下你做了[column, row]。
  • 我发现工作表不是从零开始的。我必须使用package.Workbook.Worksheets[1] 来获取第一个工作表。
  • @SkipHarris 是的,工作表、单元格,所有内容都从 0 开始索引。例如,我想获得第二列,我必须使用 col == 2。
  • 工作表是 1 基于 .NET 4 及更高版本,0 基于 .Net Core 2.0 (netstandard2.0) github.com/JanKallman/EPPlus/blob/master/EPPlus/…
  • 这描述了如何读取工作表中的每个单元格,但没有回答如何循环遍历 Table 的每一行的问题。
【解决方案2】:

这是一种将完整行作为ExcelRange 的方法,然后可以对其进行迭代或用于 LINQ:

for (var rowNum = 1; rowNum <= sheet.Dimension.End.Row; rowNum++)
{
    var row = sheet.Cells[string.Format("{0}:{0}", rowNum)];
    // just an example, you want to know if all cells of this row are empty
    bool allEmpty = row.All(c => string.IsNullOrWhiteSpace(c.Text));
    if (allEmpty) continue; // skip this row
    // ...
}

【讨论】:

  • 请问您是否介意添加代码如何获取单元格的文本。
  • 这里是如何获取第 5 行单元格 1 上单元格的文本 var text = worksheet.Cells[5, 1].Value?.ToString() ?? "";
  • 这样做并最终得到 16384 列的范围,即使我的工作簿中有 5 个列。
【解决方案3】:

您可以访问表格的.Worksheet 属性并为其单元格编制索引。为此,我编写了一个扩展方法,它生成了一系列将列名映射到单元格值的字典:

public static IEnumerable<IDictionary<string, object>> GetRows(this ExcelTable table)
{
    var addr = table.Address;
    var cells = table.WorkSheet.Cells;

    var firstCol = addr.Start.Column;

    var firstRow = addr.Start.Row;
    if (table.ShowHeader)
        firstRow++;
    var lastRow = addr.End.Row;

    for (int r = firstRow; r <= lastRow; r++)
    {
        yield return Enumerable.Range(0, table.Columns.Count)
            .ToDictionary(x => table.Columns[x].Name, x => cells[r, firstCol + x].Value);
    }
}

【讨论】:

  • 如何获取 ExcelTable 对象?我看不到它在 EPPlush 的示例项目中使用
  • @BjarkiHeiðar 看到问题; excelPackage.Workbook.Worksheets.SelectMany(s =&gt; s.Tables)
【解决方案4】:

我遇到了同样的问题,我使用ExcelTable 来获取表格边界并使用ExcelWorksheet 来检索数据来解决它。所以你的代码看起来像这样:

var fileInfo = new FileInfo(filename);
using(var excelPackage = new OfficeOpenXml.ExcelPackage(fileInfo))
{
    foreach (var sheet in excelPackage.Workbook.Worksheets)
    {
        foreach (ExcelTable table in sheet.Tables)
        {
            ExcelCellAddress start = table.Address.Start;
            ExcelCellAddress end = table.Address.End;

            for (int row = start.Row; row <= end.Row; ++row)
            {
                ExcelRange range = sheet.Cells[row, start.Column, row, end.Column];
                ...
            }
        }
    }
}

您需要检查表头或其他内容,但这对我有用。

【讨论】:

    【解决方案5】:

    我还试图弄清楚如何正确地遍历对象并使用此 API 获取我需要的数据。

    我从各种帖子和作者的入门页面收集信息,并将它们放在一起以帮助自己和他人。

    主要问题是迭代的入口点。我见过的大多数解决方案都在工作表之后,而这个问题在表格中是特定的,我对这两个问题都很好奇,所以我在这两个问题上都展示了我的发现。

    工作表示例:

    using (var package = new ExcelPackage(new FileInfo(file)))
    {
        //what i've seen used the most, entry point is the worksheet not the table w/i the worksheet(s)
        using (var worksheet = package.Workbook.Worksheets.FirstOrDefault())
        {
            if (worksheet != null)
            {
                for (int rowIndex = worksheet.Dimension.Start.Row; rowIndex <= worksheet.Dimension.End.Row; rowIndex++)
                {
                    var row = worksheet.Row(rowIndex);
                    //from comments here... https://github.com/JanKallman/EPPlus/wiki/Addressing-a-worksheet
                    //#:# gets entire row, A:A gets entire column
                    var rowCells = worksheet.Cells[$"{rowIndex}:{rowIndex}"];
                    //returns System.Object[,]
                    //type is string so it likely detects many cells and doesn't know how you want the many formatted together...
                    var rowCellsText = rowCells.Text;
                    var rowCellsTextMany = string.Join(", ", rowCells.Select(x => x.Text));
                    var allEmptyColumnsInRow = rowCells.All(x => string.IsNullOrWhiteSpace(x.Text));
                    var firstCellInRowWithText = rowCells.Where(x => !string.IsNullOrWhiteSpace(x.Text)).FirstOrDefault();
                    var firstCellInRowWithTextText = firstCellInRowWithText?.Text;
                    var firstCellFromRow = rowCells[rowIndex, worksheet.Dimension.Start.Column];
                    var firstCellFromRowText = firstCellFromRow.Text;
                    //throws exception...
                    //var badRow = rowCells[worksheet.Dimension.Start.Row - 1, worksheet.Dimension.Start.Column - 1];
    
                    //for me this happened on row1 + row2 beign merged together for the column headers
                    //not sure why the row.merged property is false for both rows though
                    if (allEmptyColumnsInRow)
                        continue;
    
                    for (int columnIndex = worksheet.Dimension.Start.Column; columnIndex <= worksheet.Dimension.End.Column; columnIndex++)
                    {
                        var column = worksheet.Column(columnIndex);
                        var currentRowColumn = worksheet.Cells[rowIndex, columnIndex];
                        var currentRowColumnText = currentRowColumn.Text;
                        var currentRowColumnAddress = currentRowColumn.Address;
                        //likely won't need to do this, but i wanted to show you can tangent off at any level w/ that info via another call
                        //similar to row, doing A:A or B:B here, address is A# so just get first char from address
                        var columnCells = worksheet.Cells[$"{currentRowColumnAddress[0]}:{currentRowColumnAddress[0]}"];
                        var columnCellsTextMany = string.Join(", ", columnCells.Select(x => x.Text));
                        var allEmptyRowsInColumn = columnCells.All(x => string.IsNullOrWhiteSpace(x.Text));
                        var firstCellInColumnWithText = columnCells.Where(x => !string.IsNullOrWhiteSpace(x.Text)).FirstOrDefault();
                        var firstCellInColumnWithTextText = firstCellInColumnWithText?.Text;
                    }
                }
            }
        }
    }
    

    现在事情可能会有些混乱,至少对我来说,我没有桌子可以开始。在同一个包 using 语句下,如果我首先遍历工作表单元格,然后使用 Tables 属性触摸任何内容,则会引发异常。如果我重新实例化一个包并使用相同/相似的代码,则在查看我们是否有任何表格时它不会爆炸。

    表格示例:

    //for some reason, if i don't instantiating another package and i work with the 'Tables' property in any way, the API throws a...
    //Object reference not set to an instance of an object.
    //at OfficeOpenXml.ExcelWorksheet.get_Tables()
    //excetion... this is because i have data in my worksheet but not an actual 'table' (Excel => Insert => Table)
    //a parital load of worksheet cell data + invoke to get non-existing tables must have a bug as below code does not
    //throw an exception and detects null gracefully on firstordefault
    using (var package = new ExcelPackage(new FileInfo(file)))
    {
        //however, question was about a table, so lets also look at that... should be the same?
        //no IDisposable? :(
        //adding a table manually to my worksheet allows the 'same-ish' (child.Parent, aka table.WorkSheet) code to iterate
        var table = package.Workbook.Worksheets.SelectMany(x => x.Tables).FirstOrDefault();
    
        if (table != null)
        {
            for (int rowIndex = table.Address.Start.Row; rowIndex <= table.Address.End.Row; rowIndex++)
            {
                var row = table.WorkSheet.Row(rowIndex);
    
                var rowCells = table.WorkSheet.Cells[$"{rowIndex}:{rowIndex}"];
                var rowCellsManyText = string.Join(", ", rowCells.Select(x => x.Text));
    
                for (int columnIndex = table.Address.Start.Column; columnIndex <= table.Address.End.Column; columnIndex++)
                {
                    var currentRowColumn = table.WorkSheet.Cells[rowIndex, columnIndex];
                    var currentRowColumnText = currentRowColumn.Text;
                }
            }
        }
    }
    

    基本上一切工作和运作方式都相同,您只需要关注 child.Parent,AKA table.WorkSheet 即可获得相同的东西。正如其他人所提到的,扩展方法甚至可能的包装类可以根据您的业务需求的具体情况为您提供更多粒度,但这不是这个问题的目的。

    关于索引 cmets 和响应,我建议坚持使用“行”和“列”属性,例如 first、last、for、foreach 等,而不是硬编码索引与非索引基本属性,至少在新版本的情况下,我在这里没有问题。

    【讨论】:

    • 你是天使!你的工作表方法正是我所需要的
    【解决方案6】:

    我不确定 epplus,但我想我会快速建议使用 LinqToExcel

    var excel = new ExcelQueryFactory(excel);
    
    var info = excel.Worksheet("Sheet1")
                    .Select(z=> new
                         {
                          Name = row["Name"].Cast<string>(),
                          Age = row["Age"].Cast<int>(),
                         }).ToList();
    

    你可以从 NuGet 获取它

    Install-Package LinqToExcel
    

    【讨论】:

    • LinqToExcel也支持写入excel吗?
    • 抱歉,没有。
    • 由于您的回答,我从 OpenXml 转移到了 LinqToExcel。谢谢
    猜你喜欢
    • 1970-01-01
    • 2020-03-12
    • 2011-10-27
    • 2021-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 2019-10-21
    相关资源
    最近更新 更多