因應專案需要,先前研究過 Lucene.Net。Lucene.Net 功能強大效能佳,又提供極高客製彈性,但缺點是得自己處理從 PDF、Word/Excel/PowerPoint 檔提取文字、管理索引排程,瑣碎工作不少。最後,我選擇到超市買牛奶而不自己養牛,決定借用 Windows Search 功能實作網站內容全文檢索,建個目錄把檔案放進去(txt、html、pdf、docx、xlsx、pptx 都成),將其納入索引範圍,在 .NET 程式建個 OleDbConnection,就可以下 SELECT ... FROM SYSTEMINDEX WHERE ... 指令完成全文檢索,很簡單吧?
網路上前人的教學文不少,我也樂得乘涼(感謝),但還是多少踩了一些坑,整理筆記如下。
【參考資源】
- Using Windows Search in your applications
蠻淺顯完整的介紹文,是不錯的入門 - Property Mappings (from WDS 2.x to 3.x) (Windows)
對索引資料表不支援 SELECT * 傳回所有欄位,必須逐一列舉,WHERE 比較時也要用到欄位名稱。要知道 SystemIndex 有哪些欄位可用,可以參考上面的 MSDN 文件。但它並沒有包含所有可用欄位,像是顯示結果時通常要附帶一段文件內容作為預覽,可由System.Search.AutoSummary取得,就沒列在其中,要從範例學習。 - 一些中文部落格文章:
Windows Desktop Search(WDS) 3.0 in .NET | 流星的隨筆記事~ - 點部落
[C#]使用WSS執行全文檢索 - RiCo技術農場 - 點部落
【基本範例】
privatestaticvoid TestMSSearch()
{
using (var cn = new OleDbConnection(
@"Provider=Search.CollatorDSO;Extended Properties=""Application=Windows"""))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = @"
SELECT
System.ItemName,
System.ItemPathDisplay,
System.ItemDate,
System.Search.AutoSummary
FROM SystemIndex
WHERE SCOPE ='file:C:/WWW/Files'
AND CONTAINS('關鍵字', 1028)";
var dr = cmd.ExecuteReader();
while (dr.Read())
{
Console.WriteLine($"{dr[0]}({dr[1]}) @{dr[2]:MM/dd HH:mm}");
Console.WriteLine($"{dr[3]}");
}
}
Console.Read();
}
【實作眉角】
- 當 SQL 語法出錯,會遇到 E_FAIL(0x80004005) 錯誤,它跟存取被拒的系統錯誤代碼很像,不要被混淆了。欄位名稱敲錯還有「資料行不存在。」之類的提示,更嚴重的語法錯誤,如括號引號不對稱、無效的符號指令等,只會出現「發生一或多個錯誤」這種模糊提示,請仔細挑出 SQL 語法的毛病,不要花時間往權限方向偵錯。
- Windows Search 雖然可用 SQL 語法查詢,但支援程度有限,不要天真以為各式 T-SQL 語法都能搬來用。建議參考以下文件:
Querying the Index with Windows Search SQL Syntax (Windows)可支援的 SQL 語法
SQL Features Unavailable in Microsoft Windows Search (Windows)不支援的 SQL 語法 - Windows 10 1703 版的 Search Service 有個 Bug,DataReader.Read() 讀完最後一筆會噴出 0x80004005(-2147467259) 錯誤,未修正前的 Workaround 是用 try … catch … 把 DataReader.Read() 包起來,攔截 0x80004005 錯誤碼視為已無資料。
- 比對文字有兩個選擇 CONTAINS() 跟 FREETEXT(),以查詢"黑暗"為例,CONTAINS() 要文件出現"黑暗"字彙才算吻合,FREETEXT() 比較寬鬆,有"黑"也有"暗"就算數。
- 有時內容明明有關鍵字卻查不到,可能與分詞有關,但 Windows Search Service 不比 Lucene.Net 能任意客製調整,得把它當成傻瓜相機,方便但有極限。
- 我在中文版 Windows 10 測試 COTAINS("中文") 成功,移到英文版 Windows 2008 R2 時,發現用 ASP.NET 查不到中文關鍵字,查英文才有結果,直接用 LINQPad 跑同樣程式碼卻正常。最後我的解決方法是在 CONTAINS() 加上 LCID參數,改成 CONTINS('中文', 1028) 避免語系設定造成影響。(註: 1028-Chinese, Taiwan, 1033-English, United States, 2052-Chinese, China)
- System.Search.AutoSummary 欄位是檔案一開始的一段純文字內容,並非出現關鍵字的片段,如果規格要求預覽包含關鍵字的文字內容,必須另外擷取成純文字再找出關鍵字,一切得 DIY。