C#におけるExcelファイルの高速読み込みとセル操作技術

Excelファイルとの連携は、業務系アプリケーション開発で頻出するタスクです。以下では、実践的な要件に応じた複数のアプローチを紹介します。パフォーマンス、互換性、保守性の観点から、それぞれの手法の特性を明確に区別し、現代的なC#環境に即した実装例を提供します。

方法1:OLE DBによる高速データ抽出(推奨)

COMオブジェクトに依存せず、軽量かつ高速な読み込みが可能なアプローチです。.NET Frameworkおよび.NET Core/.NET 5+(Windows環境限定)で動作します。

public static DataTable LoadExcelSheet(string filePath)
{
    if (!File.Exists(filePath))
        throw new FileNotFoundException("指定されたExcelファイルが見つかりません。");

    string extension = Path.GetExtension(filePath).ToLowerInvariant();
    string connectionString = extension switch
    {
        ".xls" => $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={filePath};Extended Properties=\"Excel 8.0;HDR=YES;IMEX=1\";",
        ".xlsx" or ".xlsb" => $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={filePath};Extended Properties=\"Excel 12.0;HDR=YES;IMEX=1\";",
        _ => throw new NotSupportedException("サポートされていないファイル形式です。")
    };

    using var connection = new OleDbConnection(connectionString);
    connection.Open();

    var schema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    string sheetName = schema.Rows[0]["TABLE_NAME"].ToString().TrimEnd('$');

    string query = $"SELECT * FROM [{sheetName}]";
    using var adapter = new OleDbDataAdapter(query, connection);
    var dataTable = new DataTable();
    adapter.Fill(dataTable);

    return dataTable;
}

方法2:NPOIライブラリを用いた非COM方式(クロスプラットフォーム対応)

Windows固有のCOMやOLE DBに依存せず、.NET Core / .NET 6+でも動作する現代的かつ安全な選択肢です。NuGetからEPPlusまたはNPOIを導入可能です。

// NuGet: Install-Package EPPlus
public static List<MeasurementRecord> ParseExcelWithEPPlus(string filePath)
{
    ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 商用利用時はライセンス設定が必要

    var records = new List<MeasurementRecord>();
    using var package = new ExcelPackage(new FileInfo(filePath));
    var worksheet = package.Workbook.Worksheets[0];

    int lastRow = worksheet.Dimension?.Rows ?? 1;
    for (int row = 2; row <= lastRow; row++)
    {
        string serialNo = worksheet.Cells[row, 1].Text.Trim();
        string timestampStr = worksheet.Cells[row, 2].Text.Trim();
        if (string.IsNullOrEmpty(serialNo) || string.IsNullOrEmpty(timestampStr))
            continue;

        if (DateTime.TryParse(timestampStr, out DateTime timestamp))
        {
            records.Add(new MeasurementRecord
            {
                SerialNumber = serialNo,
                Timestamp = timestamp,
                Channel1Value = worksheet.Cells[row, 3].Text,
                Channel2Value = worksheet.Cells[row, 4].Text,
                Channel3Value = worksheet.Cells[row, 5].Text,
                Channel4Value = worksheet.Cells[row, 6].Text
            });
        }
    }

    return records;
}

public class MeasurementRecord
{
    public string SerialNumber { get; set; }
    public DateTime Timestamp { get; set; }
    public string Channel1Value { get; set; }
    public string Channel2Value { get; set; }
    public string Channel3Value { get; set; }
    public string Channel4Value { get; set; }
}

セル値の安全な取得(マージ対応)

マージされたセルからの値取得は、単純な.Textアクセスでは不十分です。左上セルの値を一貫して返すロジックをカプセル化します。

public static string GetMergedCellValue(IXLCell cell)
{
    if (cell == null) return string.Empty;

    // EPPlusの場合:MergeAreaがnullでない=マージ済み
    if (cell.Worksheet.MergedRanges.Any(r => r.Contains(cell.Address)))
    {
        var mergedRange = cell.Worksheet.MergedRanges.First(r => r.Contains(cell.Address));
        return mergedRange.FirstCell().Value?.ToString() ?? string.Empty;
    }

    return cell.Value?.ToString() ?? string.Empty;
}

テキスト検索と行位置解析(マージ領域含む)

特定のラベル(例:「測定開始」)が存在する行番号と、そのセルが占める行数を取得するユーティリティです。

public static (int RowIndex, int SpanCount) FindLabelRow(IXLWorksheet sheet, string label)
{
    foreach (var row in sheet.RowsUsed())
    {
        foreach (var cell in row.CellsUsed())
        {
            if (cell.Value?.ToString()?.Trim() == label)
            {
                var mergedRange = sheet.MergedRanges.FirstOrDefault(r => r.Contains(cell.Address));
                int span = mergedRange != null ? mergedRange.RowCount() : 1;
                return (cell.Address.RowNumber(), span);
            }
        }
    }
    return (-1, 0);
}

タグ: csharp Excel EPPlus NPOI ole-db

7月26日 17:24 投稿