[料理佳餚] C# NEST 操作 Elasticsearch 搜尋服務(傳回部分欄位、刪除索引)

NEST 的 Search 方法直接將搜尋結果轉換成強型別來提供給我們使用,可說是非常方便,那我們能不能將搜尋結果轉成另外一個強型別呢?答案是肯定的,不過如果直接將 Search 這個一般化方法指定的類別給換掉,會踩到一些雷,且看接下來的說明。

搜尋結果轉換為其他型別

要將搜尋結果轉換成其他型別,只要將 Search 這個一般化的方法所指定的類別換掉即可,不過得改一些地方。

下面這個範例是我想要將搜尋結果轉換為 TextItemViewModel 這個類別,因為 Search 這個方法預設認定指定的類別名稱就是 document 的 type,所以直接替換掉是會出錯的,我們必須多加一行 Type("textitem") 指定 type 為 textitem。

接著 QueryString 裡面的 OnFields 方法,我們必須將指定要搜尋的欄位改以字串陣列的方式塞進去,因為要轉換的目的類別不一定會有我們指定的搜尋欄位,因此必須以這種方式給入參數。

private IEnumerable<TextItemViewModel> QueryByKeyword()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬英九"
    return
        this.myindexClient.Search<TextItemViewModel>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Type("textitem")
            .Query(q =>
                q.QueryString(qs => qs.OnFields(new string[] { "content" }).Query("\"馬英九\""))))
        .Hits
        .Select(h => h.Source);
}
使用這種直接轉換的方式有一個前提是目的類別的屬性名稱,不論大小寫與 document 的欄位名稱必須要一模一樣,如果目的類別的屬性名稱與 document 的欄位名稱不完全一樣,必須自行調整 Reflection 的邏輯。

搜尋結果轉換為其他型別(使用 Fields 方法)

剛剛介紹的這種直接轉換的方式會有一個 overhead,就是從 Elasticsearch 取得的資料還是完整的 document,是已經都取得了之後才由 NEST 幫我們做 Reflection,其他不要的欄位就造成了效能的浪費,如果不要這麼多欄位我們可以加上 Fields 來指定我們想要的欄位就可以了。

下面我使用 Fields 方法指定傳回 Id、AuthorId、AuthorName、Summary 這 4 個欄位,而我自行寫了一個 ReflectTo 的方法將回傳的結果 Reflect 成 TextItemViewModel 這個類別。

private IEnumerable<TextItemViewModel> QueryByKeywordUseFields()
{
    // 搜尋條件為 Content 這個欄位的值包含 "馬英九"
    return
        this.myindexClient.Search<TextItemViewModel>(s => s
            .From(0)    // 從第 0 筆
            .Size(10)   // 抓 10 筆
            .Type("textitem")
            .Query(q =>
                q.QueryString(qs => qs.OnFields(new string[] { "content" }).Query("\"馬英九\"")))
            .Fields(p => p.Id, p => p.AuthorId, p => p.AuthorName, p => p.Summary))
        .Hits
        .Where(h => h.Fields.FieldValuesDictionary != null && h.Fields.FieldValuesDictionary.Count > 0)
        .Select(h => ReflectTo<TextItemViewModel>(h.Fields.FieldValuesDictionary));
}
private static T ReflectTo<T>(IDictionary<string, object> dict)
{
    Type type = typeof(T);
    var obj = Activator.CreateInstance(type);

    foreach (var fv in dict)
    {
        var property = type.GetProperties().Where(p => p.Name.ToLower() == fv.Key.ToLower()).FirstOrDefault();

        if (property != null && fv.Value != null)
        {
            property.SetValue(obj, ((Newtonsoft.Json.Linq.JArray)fv.Value).First.ToObject<object>());
        }
    }

    return (T)obj;
}
如果我們的目的類別的屬性名稱與 document 的欄位名稱不完全一樣的時候,就自己在 Reflection 的邏輯動手腳,可以透過在 TextItemViewModel 的屬性上加註 Attribute 的方式來達到我們想要的映射結果。

刪除索引

我們想要刪除索引就簡單了,使用 DeleteByQuery 這個方法就可以了,下面的範例是我想要刪除 AuthorId 為 123456 的索引資料。

private void DeleteDocuments()
{
    var deletedResult =
        this.myindexClient.DeleteByQuery<TextItem>(d =>
            d.Query(q =>
                q.Term(p => p.AuthorId, "123456")
        ));
}

參考資料

 < Source Code >

相關資源

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