it-roy-ru.com

Как перебирать строки в таблице Excel с помощью epplus?

Я новичок в 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 ни для одного объекта в библиотеке.

Как перебрать таблицу, читая Row for Row?

40
oɔɯǝɹ

В поисках помощи по той же проблеме я наткнулся на эту ссылку . Это, безусловно, работает для меня! Определенно лучше, чем использование Interop объектов. :)

Я немного адаптировал это, хотя:

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.
    }
}
80
Chris Paton

Вот способ получить полную строку как 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
    // ...
}
16
Rango

Вы можете получить доступ к свойству .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);
    }
}
10
AlexFoxGill

Я не уверен в 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
2
Zach Spencer

Я также пытался выяснить, как правильно перебирать объекты и получать данные, которые мне нужны, с помощью этого 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;
                }
            }
        }
    }
}

Теперь все может немного запутаться, по крайней мере, у меня не было таблиц, с которых можно было бы начать. В том же пакете с использованием оператора, если я должен был сначала выполнить итерации по ячейкам рабочего листа, а затем коснуться чего-либо со свойством 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, чтобы получить то же самое. Как уже упоминали другие, методы расширения и, возможно, даже классы-оболочки могут дать вам больше детализации, основанной на специфике ваших бизнес-потребностей, но это не было целью этого вопроса.

Что касается индексирования комментариев и ответов, я бы посоветовал придерживаться свойств 'Row' и 'Column', first, last, for, foreach и т.д. Вместо жесткого кодирования индекса против неиндексированных базовых атрибутов, у меня не было выпустить здесь, по крайней мере, с новой версией. 

1
UberBiza

У меня была та же проблема, и я решил ее, используя 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];
                ...
            }
        }
    }
}

Вы должны проверить заголовок таблицы или другие вещи, но это помогло мне.

0
Dave Savage