Comment puis-je itérer des lignes dans une table excel en utilisant epplus?

je suis nouveau à epplus, et je suis en train de lire certaines valeurs à partir d'un tableau excel.

c'est Ce que j'ai jusqu'à présent:

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)  // <-- !!
             { ... }
        }
    }
}

Cependant, maintenant, je suis perplexe, comme le ExcelTable a seulement un Columns propriété, mais pas un Rows propriété comme je l'avais prévu. Je ne peux pas trouver un Rows propriété sur n'importe quel objet dans la bibliothèque.

Comment puis-je itérer à travers une table, en lisant ligne par ligne?

36
demandé sur oɔɯǝɹ 2014-02-13 03:18:42

6 réponses

en cherchant de l'aide sur le même problème, je suis tombé sur ce lien. Il a certainement travaillé pour moi! Certainement mieux que D'utiliser des objets Interop. :)

j'ai adapté un peu:

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.
    }
}
74
répondu Chris Paton 2016-01-26 10:18:40

Ici un moyen d'obtenir la rangée complète ExcelRange qui peut alors être itéré ou utilisé pour 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
    // ...
}
12
répondu Tim Schmelter 2014-12-19 11:45:24

vous pouvez accéder au .Worksheet propriété d'une table et d'indexer ses cellules. J'ai écrit une méthode d'extension à cet effet, qui génère une série de dictionnaires de mise en correspondance du nom de la colonne à la valeur de la cellule:

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);
    }
}
8
répondu AlexFoxGill 2014-10-28 15:42:03

Je ne suis pas sûr d'epplus, mais j'ai pensé que je ferais une suggestion rapide d'utiliser 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();

vous pouvez obtenir à partir de NuGet

Install-Package LinqToExcel
2
répondu Zach Spencer 2014-02-12 23:54:25

j'ai eu le même problème et je l'ai résolu en utilisant le ExcelTable pour obtenir les limites du tableau et de l' ExcelWorksheet pour récupérer les données. Ainsi, votre code devrait ressembler à quelque chose comme ceci:

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

vous devez vérifier l'en-tête de table ou d'autres choses, mais cela a fait l'affaire pour moi.

0
répondu Dave Savage 2017-05-18 16:03:36

j'essayais aussi de comprendre comment bien itérer à travers les objets et obtenir les données dont j'ai besoin avec cette API.

j'ai recueilli des informations à partir de divers postes et la page de démarrage de l'auteur et mis tout cela ensemble pour m'aider moi-même et les autres.

La question principale est de votre point d'entrée pour l'itération. La plupart des solutions que j'ai vu vont après la feuille de travail, alors que cette question est spécifique sur la Table, j'étais curieux de les deux donc je présente mon les résultats sur les deux.

Feuille De Calcul Exemple:

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

maintenant les choses peuvent être un peu perturbées ici, pour moi au moins je n'avais pas de tables pour commencer. Sous le même paquet en utilisant l'énoncé, si je devais d'abord itérer au-dessus des cellules de feuille de travail et puis toucher n'importe quoi avec la propriété de Tables il a jeté une exception. Si je ré-instancie un paquet et que j'utilise le même code/similaire, il n'explose pas en voyant si nous avons des Tables ou non.

Table Exemple:

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

essentiellement, tout fonctionne et fonctionne de la même manière, vous avez juste à aller après enfant.Parent, alias table.Feuille de travail pour obtenir la même chose. Comme d'autres l'ont mentionné, les méthodes d'extension et peut-être même les classes d'emballage pourraient vous procurer une plus grande granularité basée sur les spécificités de vos besoins d'affaires, mais ce n'était pas le but de cette question.

En ce qui concerne l'indexation des commentaires et des réponses, je vous conseille de coller avec la "Ligne" et "Colonne" propriétés, premier, dernier, pour, foreach, etc. au lieu d'un indice de codage sur papier par rapport à des attributs de base non indexés, Je n'ai pas eu de problème ici au moins avec la nouvelle version.

0
répondu UberBiza 2018-09-11 15:48:22