C# の Excel ファイル操作ライブラリー

C# で Excel ファイルの読み込み及び書き出しの操作が可能な OSS のライブラリーをテストしてみました。テストしたライブラリーは以下の4つです。

1. NPOI

Java の Apacche POI を .NET に移植したものです。OOXML の xlsx 及び 97-2003 の xls ファイルの双方に対応しており、読み込みも書き出しもできる便利なライブラリーです。

問題点としては、xlsx ファイルを作成した場合、Excelでファイルを開くと、「'○○.xlsx'には、読み取れない内容が含まれています。このブックの内容を回復しますか?」というファイルが破損しているエラーが発生します。修復ボタンを押すと問題なく使えますが、あまり気分のいいものではないです。ライセンスは、Apache License 2.0 です。

2. EPPlus

2007/2010形式の xlsx ファイルの読み込み及び書き込みに対応しています。チャートや VBA を操作できる API もあり、Excel に関して言えば NPOI よりも機能は豊富です。処理速度については、ホームページで 50,000セルを1秒で読み込めるとありますが、今回のテストではその程度の性能は問題なくでていました。ライセンスは、LGPL ライセンスです。

3. ClosedXML

Microsoft 純正の Open XML SDK をそのままで使うのはあまりにも面倒なので、それを使いやすくしたライブラリーです。ベースとしてOpen XML SDK を使うので、対応する Excel のファイル形式は、2007/2010 形式になります。Open XML SDK がベースなので機能は豊富ですが、処理は少し重いです。ライセンスは、MIT ライセンスです。

4. Excel Data Reader

読み込み専用のソフトで、高速に処理できるのが特徴で、海外では人気があるライブラリーです。日本では、97-2003 形式の場合には、日本語がよく文字化けし、2007/2010 の xlsx ファイルの場合は、ふりがなつきで返ってきます。処理が速いだけに残念なのですが、日本語の場合はかなり使いづらいです。ライセンスは、MIT ライセンスです。

テストの方法

Excel ファイルの読み込みと書き出しの簡単なプログラムを書いてテストをしました。テストに使ったプログラムは一番最後においておきます。配列のインデックスが NPOI では 0 からですが、EPPlus と ClosedXML では 1 から始まるのが面白いです。

データのファイルについては、統計データをダウンロードして利用しました。6380行×17列、6160行×17列の2個のファイルで、合計約21万セルで、ファイル容量は xls で合計 3,171KB、xlsx で 合計 1,744KB です。

テスト結果

読み込みだけであれば、Excel Data Reader が抜群に速いです。サーバーで利用する場合には軽いというのは重要な要素ですが、残念なことに日本語の処理に問題があります。現状では、97-2003 形式の日本語の xls ファイルを扱う場合の選択肢は、NPOI しかないようです。2007/2010 形式の xlsx ファイルを扱う場合は、それぞれの特徴を踏まえて利用するライブラリーを選択するようにしたらいいと思います。もう少し処理が速いほうがいいとは思うのですが、それでもサーバーの処理で十分実用になる処理速度だと思います。(単位: ミリ秒)

読込(xls) 読込(xlsx) 書込(xls) 書込(xlsx)
NPOI 925 1,887 1,289 4,000
EPPlus - 3,084 - 4,987
ClosedXML - 38,174 - 45,813
Excel Data Reader 398 737 - -

ソース

private void ExcelDataXlsReader(string inFile, string outFile)
{
    FileStream stream = File.OpenRead(inFile);
    Excel.IExcelDataReader excelReader = Excel.ExcelReaderFactory.CreateBinaryReader(stream);
    DataSet result = excelReader.AsDataSet();
    stream.Close();
    var sb = new StringBuilder();
    foreach (DataRow datarow in result.Tables[0].Rows)
    {
        for (int c = 0; c < datarow.ItemArray.Length; c++)
            sb.Append(datarow[c].ToString().Replace("\n","") + "\t");
        sb.Append("\r\n");
    }
    File.WriteAllText(outFile, sb.ToString());
}
private void ExcelDataReader(string inFile, string outFile)
{
    FileStream stream = File.OpenRead(inFile);
    Excel.IExcelDataReader excelReader = Excel.ExcelReaderFactory.CreateOpenXmlReader(stream);
    DataSet result = excelReader.AsDataSet();
    stream.Close();
    var sb = new StringBuilder();
    foreach (DataRow datarow in result.Tables[0].Rows)
    {
        for(int c = 0; c < datarow.ItemArray.Length; c++)
            sb.Append(datarow[c].ToString().Replace("\n", "") + "\t");
        sb.Append("\r\n");
    }
    File.WriteAllText(outFile, sb.ToString());
}
private void NpoiXlsReader(string inFile, string outFile)
{
    FileStream stream = File.OpenRead(inFile);
    var book = new NPOI.HSSF.UserModel.HSSFWorkbook(stream);
    stream.Close();
    NPOI.SS.UserModel.ISheet sheet = book.GetSheetAt(0);
    int lastRowNum = sheet.LastRowNum;
    var sb = new StringBuilder();
    for (int r = 0; r < sheet.LastRowNum; r++)
    {
        var datarow = sheet.GetRow(r);
        {
            for (int c = 0; c < datarow.Cells.Count; c++)
                sb.Append(datarow.Cells[c].StringCellValue.Replace("\n", "") + "\t");
            sb.Append("\r\n");
        }
    }
    File.WriteAllText(outFile, sb.ToString());
}
private void NpoiReader(string inFile, string outFile)
{
    FileStream stream = File.OpenRead(inFile);
    var book = new NPOI.XSSF.UserModel.XSSFWorkbook(stream);
    stream.Close();
    NPOI.SS.UserModel.ISheet sheet = book.GetSheetAt(0);
    int lastRowNum = sheet.LastRowNum;
            
    var sb = new StringBuilder();
    for (int r = 0; r < sheet.LastRowNum; r++)
    {
        var datarow = sheet.GetRow(r);
        {
            foreach (NPOI.SS.UserModel.ICell t in datarow.Cells)
                sb.Append(t.StringCellValue.Replace("\n", "") + "\t");
            sb.Append("\r\n");
        }
    }
    File.WriteAllText(outFile, sb.ToString());
}
private void EpPlusReader(string infile, string outFile)
{
    FileInfo newFile = new FileInfo(infile);
    OfficeOpenXml.ExcelPackage pck = new OfficeOpenXml.ExcelPackage(newFile);
    OfficeOpenXml.ExcelWorksheet sheet = pck.Workbook.Worksheets[1];
                    
    var sb = new StringBuilder();
    int rows = sheet.Dimension.Rows;
    for (int r = 1; r <= rows; r++)
    {
        {
            for (int c = 1; c <= sheet.Dimension.Columns; c++)
                sb.Append(sheet.Cells[r, c].Text.Replace("\n", "") + "\t");
            sb.Append("\r\n");
        }
    }
    File.WriteAllText(outFile, sb.ToString());
}
private void ClosedXmlReader(string inFile, string outFile)
{
    var workbook = new ClosedXML.Excel.XLWorkbook(inFile);
    ClosedXML.Excel.IXLWorksheet sheet = workbook.Worksheets.Worksheet(1);
    var sb = new StringBuilder();
    var rows = sheet.LastRowUsed().RangeAddress.FirstAddress.RowNumber;
    for (int r = 1; r <= rows; r++)
    {
        {
            for (int c = 1; c <= sheet.LastColumnUsed().RangeAddress.FirstAddress.ColumnNumber; c++)
                sb.Append(sheet.Cell(r, c).GetString().Replace("\n", "") + "\t");
            sb.Append("\r\n");
        }
    }
    File.WriteAllText(outFile, sb.ToString());
}
private void NpoiXlsWriter(string inFile, string outFile)
{
    FileStream stream = File.OpenRead(inFile);
    var book = new NPOI.HSSF.UserModel.HSSFWorkbook(stream);
    //var book = new XSSFWorkbook(stream);
    stream.Close();
    NPOI.SS.UserModel.ISheet sheet = book.GetSheetAt(0);
    for (int r = 0; r < sheet.LastRowNum; r++)
    {
        var datarow = sheet.GetRow(r);
        {
            for (int c = 0; c < datarow.Cells.Count; c++)
                datarow.Cells[c].SetCellValue(r + c);
        }
    }
    FileStream streamw = File.OpenWrite(outFile);
    book.Write(streamw);
}
private void NpoiWriter(string inFile, string outFile)
{
    FileStream stream = File.OpenRead(inFile);
    var book = new NPOI.XSSF.UserModel.XSSFWorkbook(stream);
    stream.Close();
    NPOI.SS.UserModel.ISheet sheet = book.GetSheetAt(0);
    for (int r = 0; r < sheet.LastRowNum; r++)
    {
        var datarow = sheet.GetRow(r);
        {
            for (int c = 0; c < datarow.Cells.Count; c++)
                datarow.Cells[c].SetCellValue(r + c);
        }
    }
    FileStream streamw = File.OpenWrite(outFile);
    book.Write(streamw);
}
private void EpPlusWriter(string inFile, string outFile)
{
    FileInfo inFileInfo = new FileInfo(inFile);
    OfficeOpenXml.ExcelPackage pck = new OfficeOpenXml.ExcelPackage(inFileInfo);
    OfficeOpenXml.ExcelWorksheet sheet = pck.Workbook.Worksheets[1];
    for (int r = 1; r <= sheet.Dimension.Rows; r++)
    {
        {
            for (int c = 1; c <= sheet.Dimension.Columns; c++)
                sheet.Cells[r, c].Value = r + c;
        }
    }
    FileInfo outFileInfo = new FileInfo(outFile);
    pck.SaveAs(outFileInfo);
}
private void ClosedXmlWriter(string inFile, string outFile)
{
    var workbook = new ClosedXML.Excel.XLWorkbook(inFile);
    ClosedXML.Excel.IXLWorksheet sheet = workbook.Worksheets.Worksheet(1);
    for (int r = 1; r <= sheet.LastRowUsed().RangeAddress.FirstAddress.RowNumber; r++)
    {
        {
            for (int c = 1; c <= sheet.LastColumnUsed().RangeAddress.FirstAddress.ColumnNumber; c++)
                sheet.Cell(r, c).SetValue(c + r);;
        }
    }
    workbook.SaveAs(outFile);
}