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
プロパティが見つかりません。
テーブルを反復処理し、行の行を読み取るにはどうすればよいですか?
同じ問題に関するヘルプを探しているときに、私はこれに遭遇しました link 。それは確かに私のために働いた! 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.
}
}
完全な行を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
// ...
}
テーブルの.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);
}
}
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
また、オブジェクトを適切に反復処理し、この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をたどるだけです。他の人が述べたように、拡張メソッドと、場合によってはラッパークラスも、ビジネスニーズの詳細に基づいてより細かくすることができますが、それはこの質問の目的ではありません。
インデックス作成のコメントと応答に関して、インデックスを作成するのではなく、インデックスを作成しないベース属性をハードコーディングするのではなく、最初、最後、for、foreachなどの「Row」および「Column」プロパティに固執することをお勧めしますここで少なくとも新しいバージョンを発行します。
同じ問題があり、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];
...
}
}
}
}
テーブルヘッダーなどを確認する必要がありますが、それでうまくいきました。