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