前言:為什麼需要在 .NET 中操作 Excel?
在企業級應用開發中,Excel 作為數據交換、報表匯出、批量導入的通用格式,佔據著不可替代的地位。許多開發者初次接觸時,容易混淆「在 Excel 中嵌入 .NET 代碼」(VSTO/OBA)與「在 .NET 應用程式中操作 Excel 文件」的區別。本文針對後者場景,系統梳理 .NET 操作 Excel 的三大主流技術路線,並提供可直接落地的核心代碼範例。
方案一:Microsoft.Office.Interop.Excel(COM 互操作)
核心特點
- 優勢:功能最全面,可完整操作 Excel 物件模型(儲存格、圖表、樞紐分析表、VBA 宏等),所見即所得。
- 劣勢:強依賴安裝完整版 Office Excel,不可部署於無 Office 的伺服器環境;效能較差,大量資料讀寫時明顯緩慢;易產生未釋放的 COM 物件導致記憶體洩漏,需嚴格管理生命週期。
- 適用場景:桌面端應用、需複雜格式/圖表操作、用戶機已安裝 Office 的環境。
關鍵實作代碼
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
// 單例模式管理 Application 實例,避免重複啟動
private static Application _excelApp;
private static Application GetExcelApp()
{
if (_excelApp == null)
{
_excelApp = new ApplicationClass { Visible = false, DisplayAlerts = false };
}
return _excelApp;
}
public void ReadExcelViaCom(string filePath)
{
Workbook workbook = null;
Worksheet worksheet = null;
try
{
var app = GetExcelApp();
workbook = app.Workbooks.Open(filePath, ReadOnly: true);
worksheet = (Worksheet)workbook.Sheets[1];
// 範例:讀取 A 欄第 2 行起的非空單元格
Range cell = (Range)worksheet.Cells[2, 1];
var result = new System.Text.StringBuilder();
for (int row = 2; cell.Value2 != null; row++)
{
result.Append(cell.Value2.ToString()).Append(". ");
cell = (Range)worksheet.Cells[row, 1];
}
MessageBox.Show(result.ToString());
}
catch (Exception ex)
{
Console.WriteLine($"COM 讀取異常: {ex.Message}");
}
finally
{
// 嚴格釋放 COM 物件,順序不可亂
if (workbook != null) { workbook.Close(false); Marshal.ReleaseComObject(workbook); }
if (worksheet != null) Marshal.ReleaseComObject(worksheet);
// 注意:生產環境中 Application 通常保持單例,程式退出時再 Quit 並 Release
}
}
方案二:OleDb / ADO.NET(將 Excel 視為資料源)
核心特點
- 優勢:無需安裝 Office,僅需安裝 Access Database Engine(ACE OLEDB Provider);以 SQL 查詢方式讀寫,代碼簡潔,適合批量資料匯入匯出;效能優於 COM 互操作。
- 劣勢:僅能處理資料層面,無法操作格式、圖表、樞紐分析表等表現層元素;對合併儲存格、特殊資料類型支援有限;32/64 位元 Provider 需與應用程式位元數一致。
- 適用場景:伺服器端批量資料導入導出、純資料交互、高效能讀寫需求。
連接字串關鍵點
- Excel 2007+ (.xlsx):
Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 12.0 Xml;HDR=YES;IMEX=1'
- Excel 97-2003 (.xls):
Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties='Excel 8.0;HDR=YES;IMEX=1'
HDR=YES 表示第一行為標題;IMEX=1 強制混合類型欄位按文字讀取,避免資料截斷。
關鍵實作代碼
using System.Data;
using System.Data.OleDb;
public DataTable ReadExcelViaOleDb(string filePath, string sheetName = "Sheet1")
{
var connStr = $@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={filePath};Extended Properties='Excel 12.0 Xml;HDR=YES;IMEX=1'";
var dt = new DataTable();
try
{
using (var conn = new OleDbConnection(connStr))
{
conn.Open();
// 獲取工作表實際名稱(可選)
// var schema = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
// sheetName = schema.Rows[0]["TABLE_NAME"].ToString();
using (var cmd = new OleDbCommand($"SELECT * FROM [{sheetName}]", conn))
using (var adapter = new OleDbDataAdapter(cmd))
{
adapter.Fill(dt);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"OleDb 讀取異常: {ex.Message}");
}
return dt;
}
方案三:Open XML SDK(操作 Office Open XML 格式)
核心特點
- 優勢:完全無 Office 依賴,官方推薦的伺服器端方案;直接操作 ZIP 封裝內的 XML,效能極高,支援超大文件(配合 SAX/Streaming 讀寫);可精確控制每個 XML 元素,功能極其強大。
- 劣勢:學習曲線陡峭,需理解 Open XML 標準(ECMA-376)及文件結構;代碼量大,簡單操作也需編寫大量樣板代碼;不支援舊版 .xls 格式。
- 適用場景:高併發伺服器端報表生成、超大文件處理、需精細控制樣式/結構、跨平臺部署(.NET Core / .NET 5+)。
核心類別速查
| 類別 | 對應 XML 部分 | 主要用途 |
SpreadsheetDocument | 整個 .xlsx 套件 | 入口點,開啟/建立文件 |
WorkbookPart | xl/workbook.xml | 工作簿層級:工作表管理、共用字串、樣式 |
WorksheetPart | xl/worksheets/sheetN.xml | 單個工作表:儲存格資料、合併儲存格、資料驗證 |
SharedStringTablePart | xl/sharedStrings.xml | 共用字串表,優化重複字串儲存 |
WorkbookStylesPart | xl/styles.xml | 樣式定義:字型、填充、邊框、數字格式 |
關鍵實作代碼:讀取指定儲存格值
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
public string ReadCellValue(string filePath, string sheetName, string addressName)
{
using (var doc = SpreadsheetDocument.Open(filePath, false))
{
var workbookPart = doc.WorkbookPart;
var sheet = workbookPart.Workbook.Descendants<Sheet>()
.FirstOrDefault(s => s.Name == sheetName);
if (sheet == null) return null;
var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
var cell = worksheetPart.Worksheet.Descendants<Cell>()
.FirstOrDefault(c => c.CellReference == addressName);
if (cell == null) return null;
string value = cell.CellValue?.InnerText;
// 處理共用字串類型
if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
{
var stringTable = workbookPart.SharedStringTablePart.SharedStringTable;
value = stringTable.ElementAt(int.Parse(value)).InnerText;
}
return value;
}
}
關鍵實作代碼:寫入數值到儲存格
public void WriteCellValue(string filePath, string sheetName, string addressName, string value)
{
using (var doc = SpreadsheetDocument.Open(filePath, true))
{
var workbookPart = doc.WorkbookPart;
var sheet = workbookPart.Workbook.Descendants<Sheet>()
.FirstOrDefault(s => s.Name == sheetName);
if (sheet == null) return;
var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
var sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
// 解析地址 (如 "B3") -> 欄索引、行索引
var colName = new string(addressName.Where(char.IsLetter).ToArray());
var rowIndex = uint.Parse(new string(addressName.Where(char.IsDigit).ToArray()));
var colIndex = ColNameToIndex(colName);
var row = sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
if (row == null)
{
row = new Row { RowIndex = rowIndex };
sheetData.Append(row);
}
var cell = row.Elements<Cell>().FirstOrDefault(c => c.CellReference.Value == addressName);
if (cell == null)
{
cell = new Cell { CellReference = addressName };
// 插入正確位置以維持順序
var refCell = row.Elements<Cell>().FirstOrDefault(c => ColNameToIndex(GetColumnName(c.CellReference)) > colIndex);
if (refCell != null) row.InsertBefore(cell, refCell);
else row.Append(cell);
}
cell.CellValue = new CellValue(value);
cell.DataType = CellValues.String; // 或 Number/Boolean 等
worksheetPart.Worksheet.Save();
}
}
private int ColNameToIndex(string colName) { /* 實現列名轉索引邏輯 */ }
private string GetColumnName(string cellRef) { /* 提取列名 */ }
三大方案綜合對比與選型建議
| 維度 | COM Interop | OleDb / ADO.NET | Open XML SDK |
| Office 依賴 | 必須安裝完整版 | 需安裝 ACE Provider | 完全無依賴 |
| 部署環境 | 僅限桌面/有 Office 的機器 | 伺服器/桌面均可 | 任意環境,含 Linux/Docker |
| 功能覆蓋 | 100% Excel 功能 | 僅資料讀寫 | 幾乎所有檔案結構/樣式 |
| 大數據效能 | 差 (跨行程調用開銷大) | 良好 (流式讀寫) | 極佳 (DOM/SAX 雙模式) |
| 開發難度 | 低 (物件模型直觀) | 低 (SQL 風格) | 高 (需懂 XML 結構) |
| 舊版 .xls 支援 | 支援 | 支援 (Jet Provider) | 不支援 |
選型決策樹
- 需操作圖表、樞紐分析表、VBA、列印設定等高階功能 → COM Interop (限桌面端)。
- 純資料導入導出、批量處理、伺服器端部署、不需複雜格式 → OleDb (最快上手)。
- 高併發報表生成、超大文件、跨平臺、精細樣式控制、無 Office 環境 → Open XML SDK (長遠最佳投資)。
進階補充:現代化替代方案
- ClosedXML:Open XML SDK 的優雅封裝,API 近似 VBA,大幅降低學習成本,推薦作為 Open XML 入門首選。
- EPPlus:功能豐富的商業庫 (5.0+ 版本為商業授權),支援圖表、樞紐分析表、條件格式等高階功能。
- NPOI:Apache POI 的 .NET 移植,同時支援 .xls 和 .xlsx,成熟穩定,社區活躍。
最佳實踐總結
- 資源釋放:COM 物件必須
Marshal.ReleaseComObject;OleDb/Open XML 善用 using 區塊。
- 大數據分批:避免一次性載入百萬行到記憶體,採用分頁讀寫或 Streaming 模式 (Open XML
OpenXmlReader/OpenXmlWriter)。
- 類型安全:OleDb 讀取時指定
IMEX=1;Open XML 寫入時正確設定 DataType。
- 錯誤處理:捕獲具體異常 (
COMException, OleDbException, OpenXmlPackageException) 並記錄日誌。
- 測試驗證:針對合併儲存格、隱藏行/列、資料驗證、條件格式等邊界情況編寫自動化測試。