C# 讀取 Excel (.xls/.xlsx) 有多種成熟方案,各有優缺點。本文系統比較 OLEDB (Microsoft.ACE)、EPPlus、ClosedXML、NPOI 四大主流庫,提供完整程式碼範例、效能測試、部署限制與選型建議,幫助專案依需求選擇最適合的方案。
| 特性 | OLEDB (ACE) | EPPlus | ClosedXML | NPOI |
|---|---|---|---|---|
| 支援格式 | .xls, .xlsx | .xlsx (5.0+ 支援 .xls) | .xlsx | .xls, .xlsx |
| 相依性 | 需安裝 Access Database Engine | 純 managed,無額外安裝 | 純 managed,無額外安裝 | 純 managed,無額外安裝 |
| 跨平台 | ❌ 僅 Windows | ✅ Windows/Linux/macOS | ✅ Windows/Linux/macOS | ✅ Windows/Linux/macOS |
| 授權 | Windows 授權涵蓋 | Polyform Noncommercial 1.0 (商業需付費) | MIT | Apache 2.0 |
| 效能 (大量資料) | 極快 (原生驅動) | 快 | 中等 | 中等 |
| 記憶體模式 | 串流 (DataReader) | 全載入 DOM | 全載入 DOM | 全載入 DOM / SAX 模式 |
| 功能豐富度 | 唯讀查詢 | 完整讀寫、樣式、公式、圖表 | 完整讀寫、樣式、公式 | 完整讀寫、樣式、公式、巨集 |
| 公式計算 | ❌ | ✅ (EPPlus.CalculationEngine) | ✅ 基礎支援 | ✅ 公式評估器 |
.xlsx: Provider=Microsoft.ACE.OLEDB.12.0;Data Source=file.xlsx;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1"
.xls: Provider=Microsoft.Jet.OLEDB.4.0;Data Source=file.xls;Extended Properties="Excel 8.0;HDR=YES;IMEX=1"
HDR=YES:第一列為標題;IMEX=1:混合資料型別視為文字,避免型別推斷錯誤。
using System;
using System.Data;
using System.Data.OleDb;
public class ExcelOleDbReader
{
public DataTable ReadSheet(string filePath, string sheetName = "Sheet1")
{
string ext = Path.GetExtension(filePath).ToLower();
string connStr = ext == ".xlsx"
? $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={filePath};Extended Properties=\"Excel 12.0 Xml;HDR=YES;IMEX=1\""
: $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={filePath};Extended Properties=\"Excel 8.0;HDR=YES;IMEX=1\"";
using var conn = new OleDbConnection(connStr);
conn.Open();
// 取得實際工作表名稱 (避免大小寫/$ 符號問題)
var schema = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
string actualSheet = schema.Rows.Cast()
.Select(r => r["TABLE_NAME"].ToString())
.FirstOrDefault(s => s.Equals($"[{sheetName}$]", StringComparison.OrdinalIgnoreCase)
|| s.Equals($"'{sheetName}'$", StringComparison.OrdinalIgnoreCase));
if (actualSheet == null) actualSheet = $"[{sheetName}$]"; // fallback
using var cmd = new OleDbCommand($"SELECT * FROM {actualSheet}", conn);
using var adapter = new OleDbDataAdapter(cmd);
var dt = new DataTable();
adapter.Fill(dt);
return dt;
}
// 串流式讀取 (低記憶體)
public IEnumerable ReadStream(string filePath, string sheetName = "Sheet1")
{
// ... 同樣建立連線與命令
using var reader = cmd.ExecuteReader();
while (reader.Read())
yield return reader;
}
}
Install-Package EPPlus
⚠️ 版本 5.0+ 採用 Polyform Noncommercial 1.0 授權:非商業免費,商業用途需購買授權。版本 4.5.3.1 仍為 LGPL,但不再維護。
using OfficeOpenXml;
using System.Data;
public class ExcelEpPlusReader
{
static ExcelEpPlusReader()
{
// 非商業專案設定
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
}
public DataTable ReadSheet(string filePath, string sheetName = "Sheet1")
{
using var package = new ExcelPackage(new FileInfo(filePath));
var ws = package.Workbook.Worksheets[sheetName]
?? package.Workbook.Worksheets.FirstOrDefault();
if (ws == null) throw new ArgumentException($"找不到工作表: {sheetName}");
var dt = new DataTable();
// 假設第一列為標題
for (int col = 1; col <= ws.Dimension.End.Column; col++)
dt.Columns.Add(ws.Cells[1, col].Text);
for (int row = 2; row <= ws.Dimension.End.Row; row++)
{
var dr = dt.NewRow();
for (int col = 1; col <= ws.Dimension.End.Column; col++)
dr[col - 1] = ws.Cells[row, col].Text;
dt.Rows.Add(dr);
}
return dt;
}
// 串流式寫入大量資料 (低記憶體)
public void WriteLargeData(string filePath, IEnumerable
Install-Package ClosedXML
using ClosedXML.Excel;
using System.Data;
public class ExcelClosedXmlReader
{
public DataTable ReadSheet(string filePath, string sheetName = "Sheet1")
{
using var wb = new XLWorkbook(filePath);
var ws = wb.Worksheet(sheetName) ?? wb.Worksheets.First();
var dt = new DataTable();
bool firstRow = true;
foreach (var row in ws.RowsUsed())
{
if (firstRow)
{
foreach (var cell in row.Cells())
dt.Columns.Add(cell.GetString());
firstRow = false;
}
else
{
var dr = dt.NewRow();
for (int i = 0; i < dt.Columns.Count; i++)
dr[i] = row.Cell(i + 1).GetString();
dt.Rows.Add(dr);
}
}
return dt;
}
}
Install-Package NPOI
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel; // .xlsx
using NPOI.HSSF.UserModel; // .xls
using System.Data;
public class ExcelNpoiReader
{
public DataTable ReadSheet(string filePath, string sheetName = "Sheet1")
{
IWorkbook workbook;
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
workbook = WorkbookFactory.Create(fs); // 自動偵測 .xls/.xlsx
}
ISheet sheet = workbook.GetSheet(sheetName) ?? workbook.GetSheetAt(0);
IRow headerRow = sheet.GetRow(0);
if (headerRow == null) return new DataTable();
var dt = new DataTable();
foreach (ICell cell in headerRow.Cells)
dt.Columns.Add(cell.GetStringCellValue());
for (int i = 1; i <= sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
if (row == null) continue;
DataRow dr = dt.NewRow();
for (int j = 0; j < dt.Columns.Count; j++)
{
ICell cell = row.GetCell(j);
dr[j] = cell?.ToString() ?? "";
}
dt.Rows.Add(dr);
}
return dt;
}
}
| 方案 | 耗時 | 峰值記憶體 | CPU |
|---|---|---|---|
| OLEDB (DataReader 串流) | 1.2s | 45 MB | 低 |
| EPPlus 6 (全載入) | 3.8s | 420 MB | 中 |
| ClosedXML (全載入) | 5.1s | 480 MB | 中高 |
| NPOI (全載入) | 4.5s | 460 MB | 中 |
| NPOI (SAX 事件式) | 2.9s | 60 MB | 中 |
需要跨平台 (Linux Docker)?
├── 是 → 需要商業授權預算?
│ ├── 是 → 功能豐富度優先? → EPPlus (購買授權)
│ └── 否 → MIT/Apache 授權? → ClosedXML (簡單) / NPOI (功能全)
└── 否 (Windows 專用) → 需要極致效能/記憶體?
├── 是 → 已安裝 ACE Driver? → OLEDB (唯讀匯入最快)
└── 否 → 參考跨平台選項
IMEX=1 強制視為文字,或在 SQL 用 SELECT FORMAT(col, "00000") (ACE 不支援 FORMAT),建議改用 EPPlus/NPOI 讀取原始 Cell 值。ws.Cells["A1"].Value、ws.Cell(row, col).Value 隨機存取,不需全表載入。ws.Calculate();NPOI 用 FormulaEvaluator.Evaluate(cell);ClosedXML cell.CachedValue;OLEDB 無法取得計算結果。沒有銀彈,只有適合情境的工具:
現代雲原生專案建議優先採用 ClosedXML 或 NPOI,避免 Windows 專用相依性與授權風險。