[料理佳餚] C# NEST 操作 Elasticsearch 搜尋服務(搜尋、排序結果)

要在 Elasticsearch 下達搜尋指令找出一些東西來,可以透過像 kopf 這類的 Plugin 所提供的 GUI 來直接操作 Elasticsearch 的 RESTFUL Api,可是我們最終還是有需要自己寫程式來下搜尋指令的時候,除了用像 RestSharp 這類的套件,再自行轉換 JSON 結果之外,當然也可以透過 NEST 來輕鬆達成。

項目比對搜尋(Term)

Elasticsearch 有一個下條件的方式叫 Term(暫時翻譯叫項目),NEST 也提供 Term 的方法來對應,先看範例程式碼。

private IEnumerable<TextItem> QueryByTerm()
{
    // 搜尋條件為 Id 這個欄位的值等於 2d50d70d-03f9-4769-8592-20de15bc6d0a
    return
        this.myindexClient.Search<TextItem>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Query(q =>
                q.Term(p => p.Id, "2d50d70d-03f9-4769-8592-20de15bc6d0a")))
        .Hits
        .Select(h => h.Source);
}
這裡看不懂 Lambda 語法的我救不了你。

使用 NEST 向 Elasticsearch 發送搜尋指令的起手式就是 Search 這個方法,Search 是一個一般化的方法,必須強制一定要指定一個類別給它,而參數就給入一個 Func<SearchDescriptor<T>, SearchDescriptor<T>>,使用方式就參考上面的程式碼。

Term 這個方式給的條件主要是針對 not_analyzed 的欄位做精確的比對,在應用上類似像是性別、血型、膚色…等這種欄位的篩選,當然這裡只是舉例,其他還有很多種應用,前提是欄位一定要是 not_analyzed。

關鍵字搜尋(QueryString)

由於中文字在索引時,不使用中文分詞分析器的情況之下,預設就是一個字一個詞,Elasticsearch 的索引方式是使用倒排索引(Inverted index),因此我們下中文關鍵字條件的時候,通常會在關鍵字的前後加上雙引號來強制指定關鍵字每個詞的排列順序,請看下面範例程式碼。

private IEnumerable<TextItem> QueryByString()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬英九"
    return
        this.myindexClient.Search<TextItem>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Query(q =>
                q.QueryString(qs => qs.OnFields(p => p.Content).Query("\"馬英九\""))))
        .Hits
        .Select(h => h.Source);
}

QueryString 這個方式主要是針對 analyzed 的欄位進行關鍵字比對,上面這個範例程式會找出符合 Content 這個欄位的值裡面第一個位置的詞為馬、第二個位置的詞為英、第三個位置的詞為九這個條件的資料。

QueryString vs. Match vs. MatchPhrase

關鍵字搜尋除了 QueryString 之外還可以用 Match、MatchPhrase,接下來說明這三者的差異。

下面的這種搜尋條件就是去找符合 Content 這個欄位的值有 "馬" or "英" or "九" 這個條件的資料,而且 "馬英九" 這三個字不指定位置順序,所以我們有可能會找出 "在英國有一匹九歲的馬。" 之類的結果。

private IEnumerable<TextItem> QueryByMatch()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬" 或 "英" 或 "九"
    //return
    //    this.myindexClient.Search<TextItem>(s => s
    //        .From(0)    // 從第 0 筆
    //        .Size(10)   // 抓 10 筆
    //        .Query(q =>
    //            q.QueryString(qs => qs.OnFields(p => p.Content).Query("馬英九"))))
    //    .Hits
    //    .Select(h => h.Source);

    // 上述條件相當於 Content 這個欄位的值 Match 馬英九
    return
        this.myindexClient.Search<TextItem>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Query(q =>
                q.Match(qs => qs.OnField(p => p.Content).Query("馬英九"))))
        .Hits
        .Select(h => h.Source);
}

因此,如果我們要找出我們一般認知的 "馬英九",不能用 Match 方法,得用 MatchPhrase,而 QueryString 方法的關鍵字就得前後加上雙引號。

private IEnumerable<TextItem> QueryByMatchPhrase()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬英九"
    //return
    //    this.myindexClient.Search<TextItem>(s => s
    //        .From(0)    // 從第 0 筆
    //        .Size(10)   // 抓 10 筆
    //        .Query(q =>
    //            q.QueryString(qs => qs.OnFields(p => p.Content).Query("\"馬英九\""))))
    //    .Hits
    //    .Select(h => h.Source);

    // 上述條件相當於 Content 這個欄位的值 MatchPhrase 馬英九
    return
        this.myindexClient.Search<TextItem>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Query(q =>
                q.MatchPhrase(qs => qs.OnField(p => p.Content).Query("馬英九"))))
        .Hits
        .Select(h => h.Source);
}

多條件搜尋

有了 NEST,多條件搜尋就簡單了,請看。

private IEnumerable<TextItem> QueryByMultipleConditions()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬英九" 而且也包含 "吳敦義"
    return
        this.myindexClient.Search<TextItem>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Query(q =>
                q.QueryString(qs => qs.OnFields(p => p.Content).Query("\"馬英九\""))
                && q.QueryString(qs => qs.OnFields(p => p.Content).Query("\"吳敦義\""))))
        .Hits
        .Select(h => h.Source);
}

排序搜尋結果

要排序搜尋結果只要加上 SortAscending 或 SortDescending 方法,給入要排序的欄位就可以了。

private IEnumerable<TextItem> QueryWithSorting()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬英九",並且依 CreateTime 降冪排序搜尋結果。
    return
        this.myindexClient.Search<TextItem>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Query(q =>
                q.QueryString(qs => qs.OnFields(p => p.Content).Query("\"馬英九\"")))
            .SortDescending(p => p.CreatedTime))
        .Hits
        .Select(h => h.Source);
}

參考資料

 < Source Code >

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學