業務常需「在 Excel 中搜索關鍵字」,本文基於 Microsoft.Office.Interop.Excel COM 互操作,提供兩種搜索方案:**高效的 Range.Find 一行定位**與**全單元格遍歷兜底**,並解析 UsedRange 索引陷阱與資源釋放最佳實踐。
Application → Workbooks → Workbook → Worksheets → Worksheet → Range(UsedRange) → Find()
using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection; // Missing.Value
public bool SearchExcelWithFind(string filePath, string keyword)
{
Excel.Application app = null;
Excel.Workbook wb = null;
try
{
app = new Excel.ApplicationClass { Visible = false, DisplayAlerts = false };
wb = app.Workbooks.Open(filePath, Missing.Value, true /*ReadOnly*/,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value);
object missing = Missing.Value;
object searchText = keyword.Trim().ToUpper();
int sheetCount = wb.Worksheets.Count;
for (int i = 1; i <= sheetCount; i++) // 索引從 1 開始!
{
Excel.Worksheet ws = (Excel.Worksheet)wb.Worksheets[i];
Excel.Range used = ws.UsedRange;
if (used == null) continue;
// Find 參數:What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat
Excel.Range found = used.Find(searchText, missing,
Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart,
Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext,
false, missing, missing);
if (found != null && found.Cells.Count >= 1)
{
// 找到:found.Row, found.Column, found.Value2 可用
return true;
}
Marshal.ReleaseComObject(ws);
}
return false;
}
finally
{
if (wb != null) { wb.Close(false); Marshal.ReleaseComObject(wb); }
if (app != null) { app.Quit(); Marshal.ReleaseComObject(app); }
GC.Collect(); GC.WaitForPendingFinalizers(); // 強制釋放 COM
}
}
| 參數 | 推薦值 | 說明 |
|---|---|---|
LookIn | xlValues | 搜索值(含公式結果),xlFormulas 搜公式文本,xlComments 搜批註 |
LookAt | xlPart | 部分匹配(含關鍵字),xlWhole 整格匹配 |
SearchOrder | xlByRows | 按行優先,大數據下比 xlByColumns 快 |
SearchDirection | xlNext | 向前搜索,**必須顯式指定**,Missing.Value 會編譯報錯 |
MatchCase | false | 不區分大小寫 |
public bool SearchExcelByIteration(string filePath, string keyword)
{
Excel.Application app = new Excel.ApplicationClass { Visible = false };
Excel.Workbook wb = app.Workbooks.Open(filePath, Missing.Value, true, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);
try
{
string kw = keyword.Trim().ToUpper();
int sheetCount = wb.Worksheets.Count;
for (int i = 1; i <= sheetCount; i++)
{
Excel.Worksheet ws = (Excel.Worksheet)wb.Worksheets[i];
Excel.Range used = ws.UsedRange;
if (used == null) continue;
int startRow = used.Row; // 起始行(通常 1)
int startCol = used.Column; // 起始列(通常 1)
int rowCount = used.Rows.Count;
int colCount = used.Columns.Count;
// 陷阱:單儲格 UsedRange 時 Row/Column 可能為 1,但索引仍從 1 開始
// 多儲格時 Row/Column 仍為起始位置,索引從 1 到 Count
for (int r = 1; r <= rowCount; r++)
{
for (int c = 1; c <= colCount; c++)
{
Excel.Range cell = (Excel.Range)used.Cells[r, c];
string val = (cell.Value2 ?? "").ToString().ToUpper();
if (val.IndexOf(kw) >= 0) return true;
Marshal.ReleaseComObject(cell);
}
}
Marshal.ReleaseComObject(used);
Marshal.ReleaseComObject(ws);
}
return false;
}
finally { wb.Close(false); Marshal.ReleaseComObject(wb); app.Quit(); Marshal.ReleaseComObject(app); GC.Collect(); GC.WaitForPendingFinalizers(); }
}
UsedRange.Rows.Count = 1, Columns.Count = 1,Cells[1,1] 有效。Cells[1,1] ~ Cells[Rows.Count, Columns.Count] 全有效,**索引始終從 1 開始**,與 UsedRange.Row/Column 無關(它們只是左上角坐標)。SXSSFWorkbook / EPPlus 支持流式寫入,搜索需全讀,建議拆分為「索引構建(後台任務)+ 關鍵字查詢(倒排索引/資料庫)」架構。Workbooks.Open(..., password),失敗捕獲異常記錄日誌。Marshal.ReleaseComObject,循環內釋放 Range/Worksheet,最後 GC.Collect() 兩次。| 庫 | 優勢 | 搜索能力 | 適用場景 |
|---|---|---|---|
| EPPlus | 無依賴、性能強、支持 .xlsx | 需自行遍歷,可並行 | Web 後端、高併發 |
| NPOI | 支持 .xls/.xlsx、流式 SXSSF | 同 EPPlus | 大文件、舊格式 |
| ClosedXML | API 友好、LINQ 風格 | ws.CellsUsed().Where(c=>c.GetString().Contains(kw)) | 中小項目、快速開發 |
| Interop | 100% 兼容 Excel 邏輯/公式 | Range.Find 極快 | 桌面增強、複雜公式依賴 |
搜索 Excel 核心在於 **用對工具、釋放資源、避開 COM 陷阱**。新項目首選 EPPlus/NPOI,舊項目維護時做好 COM 釋放與異常兜底。