Elastcisearch.Nest 7.x 系列`偽`官方翻譯:通過 NEST 來快捷試用 Elasticsearch
- 本系列已經全部完成,完整版可見 :https://blog.zhuliang.ltd/categories/Elasticsearch/
- 本系列博文是“偽”官方文件翻譯(更加本土化),並非完全將官方文件進行翻譯,而是在查閱、測試原始文件並轉換為自己真知灼見後的“準”翻譯。有不同見解 / 說明不周的地方,還請海涵、不吝拍磚 :)
- 官方文件見此:https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html
- 本系列對應的版本環境:[email protected],[email protected],IDE 和開發平臺預設為 VS2019,.NET CORE 2.1
Elasticsearch.Net 和 NEST 對比說明:
- Elasticsearch 官方為 .NET 提供了 2 個官方客戶端庫:Elasticsearch.Net 和 NEST。
- 可以簡單理解為 Elasticsearch.Net 是 NEST 的一個子集。
- NEST 內部使用了 ElasticSearch.Net ,並通過 NEST 可以對外暴露 ElasticSearch.Net 客戶端。
- 但 NEST 包含了 ElasticSearch.Net 所沒有的一些高階功能,如:
- 強型別查詢 DSL:可以將所有請求和響應的物件型別轉換 1:1 的.NET 型別。
- 自動轉換為 CLR 的資料型別。
基本上 .NET 專案到了要使用上 ElasticSearch 的地步,直接選擇 NEST 即可。
在使用 NEST 作為客戶端的時候,建議將 ElasticClient 物件作為單例來使用。
- ElasticClient 被設計為執行緒安全。
- ES 中的快取是根據 ConnectionSettings 來劃分的,即服務端快取針對的是每一個 ConnectionStrings
- 例外: 當你需要連線不同的 ES 叢集的時候,就不要用單例了,應為不同的 ElasticClient 使用不同的 ConnectionStrings。
快速使用
- 使用 Nest 的時候約定 Nest 版本需要跟 ElasticSearch 版本保持一致,即服務端 ES版本為 7.3.1,則 Nest 版本也要使用 7.3.1
- 以下示例通過 IoC 進行注入(單例),你也可以直接通過單例模式來實現。
配置、連線 ElasticSearch
配置類:
public class ElasticSearchSettings
{
public string ServerUri { get; set; }
public string DefaultIndex { get; set; } = "defaultindex";
}
ElasticSearch 客戶端:
- 連線到 ElasticSearch,並設定預設索引
public class ElasticSearchClient : IElasticSearchRepository
{
private readonly ElasticSearchSettings _esSettings;
private readonly ElasticClient _client;
public ElasticSearchClient(IOptions<ElasticSearchSettings> esSettings)
{
_esSettings = esSettings.Value;
var settings = new ConnectionSettings(new Uri(_esSettings.ServerUri)).DefaultIndex(_esSettings.DefaultIndex);
_client = new ElasticClient(settings);
}
public ElasticSearchClient(ElasticSearchSettings esSettings)
{
_esSettings = esSettings;
var settings = new ConnectionSettings(new Uri(_esSettings.ServerUri)).DefaultIndex(_esSettings.DefaultIndex);
_client = new ElasticClient(settings);
}
}
- 建議增加預設索引名,不然在請求時推斷索引名的時候可能會引發異常,更多關於索引名的推斷見此博文NEST 教程系列 9-1 轉換:Index name inference | 索引名推斷的3種方式
連線配置上使用密碼憑證
ElasticSearch 可以直接在 Uri 上指定密碼,如下:
var uri = new Uri("http://username:password@localhost:9200")
var settings = new ConnectionConfiguration(uri);
- 但不建議這麼做,尤其是在多個節點的連線池或者能夠 reseeding 的連線池上的時候。
- **建議使用 ConnectionSettings 來設定憑證。NEST 教程系列 2-4 連線:Working with certificates | 使用憑證連線
使用連線池
ConnectionSettings 不僅支援單地址的連線方式,同樣提供了不同型別的連線池來讓你配置客戶端,如使用 SniffingConnectionPool 來連線叢集中的 3 個 Elasticsearch 節點,客戶端將使用該型別的連線池來維護叢集中的可用節點列表,並會以迴圈的方式傳送呼叫請求。
var uris = new[]{
new Uri("http://localhost:9200"),
new Uri("http://localhost:9201"),
new Uri("http://localhost:9202"),};
var connectionPool = new SniffingConnectionPool(uris);var settings = new ConnectionSettings(connectionPool)
.DefaultIndex("people");
_client = new ElasticClient(settings);
NEST 教程系列 2-1 連線:Configuration options| 配置選項
索引(Indexing)
- 這裡的“索引”需理解為動詞,用 indexing 來理解會更好,表示將一份文件插入到 ES 中。
假設有如下類 User.cs
public class User
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
索引(Indexing)/新增 一份文件到 ES 中
//同步
var response = _client.IndexDocument<User>(new User
{
Id = new Guid("3a351ea1-bfc3-43df-ae12-9c89e22af144"),
FirstName = "f1",
LastName = "l1"
});
//非同步
var response = _client.IndexDocumentAsync<User>(new User
{
Id = new Guid("82f323e3-b5ec-486b-ac88-1bc5e47ec643"),
FirstName = "f2",
LastName = "l2"
});
- 目前提供的方法基本上都含有同步和非同步版本(非同步方法以 *Async 結尾)
- IndexDocument 方法會判斷新增的文件是否已經存在(根據 _id),若存在,則新增失敗。
- NEST 會自動將 Id 屬性作為 ES 中的 _id,更多關於 id 的推斷方式見此博文:NEST 教程系列 9-3 轉換:Id 推斷
- 預設情況下,會使用 camel 命名方式進行轉換,你可以使用 ConnectionSettings 物件的 .DefaultFieldNameInferrer(Func<string, string>) 方法來調整預設轉換行為,更多關於屬性的推斷的見此博文:NEST 教程系列 9-4 轉換:Field 屬性推斷
最終請求地址為:
PUT /users/_doc/3a351ea1-bfc3-43df-ae12-9c89e22af144
查詢
通過類 lambda 表示式進行查詢
通過 Search
var result = _client.Search<User>(s=>s.From(0)
.Size(10)
.Query(q=>q.Match(m=>m.Field(f=>f.LastName).Query("l1"))));
請求 URL 如下:
POST /users/_search?typed_keys=true
- 以上搜索是基於“users”索引來進行搜尋
如何在 ES 上的所有索引上進行搜尋?通過 AllIndices(),如下:
var result = _client.Search<User>(s=>s
.AllIndices() //指定在所有索引上進行查詢
.From(0)
.Size(10)
.Query(q=>q.Match(m=>m.Field(f=>f.LastName).Query("l1"))));
假設有如下文件:
//users 索引
"hits" : [
{
"_index" : "users",
"_type" : "_doc",
"_id" : "3a351ea1-bfc3-43df-ae12-9c89e22af144",
"_score" : 1.0,
"_source" : {
"id" : "3a351ea1-bfc3-43df-ae12-9c89e22af144",
"firstName" : "f1",
"lastName" : "l1"
}
},
{
"_index" : "users",
"_type" : "_doc",
"_id" : "05245504-053c-431a-984f-23e16d8fbbc9",
"_score" : 1.0,
"_source" : {
"id" : "05245504-053c-431a-984f-23e16d8fbbc9",
"firstName" : "f2",
"lastName" : "l2"
}
}
]
// thirdusers 索引
"hits" : [
{
"_index" : "thirdusers",
"_type" : "_doc",
"_id" : "619ad5f8-c918-46ef-82a8-82a724ca5443",
"_score" : 1.0,
"_source" : {
"firstName" : "f1",
"lastName" : "l1"
}
}
]
則最終可以獲取到 users 和 thirdusers 索引中分別獲取到 _id 為 3a351ea1-bfc3-43df-ae12-9c89e22af144 和 619ad5f8-c918-46ef-82a8-82a724ca5443 的文件資訊。
可以通過 .AllTypes() 和 .AllIndices() 從所有 型別(types) 和 所有 索引(index)中查詢資料,最終查詢會生成在 /_search 請求中。關於 Type 和 Index,可分別參考:NEST 教程系列 9-6 轉換:Document Paths 文件路徑和跳轉:NEST 教程系列 9-7 轉換:Indices Paths 索引路徑
通過查詢物件進行查詢
通過 SearchRequest 物件進行查詢。
例:在所有索引查詢 LastName="l1"的文件資訊
var request = new SearchRequest(Nest.Indices.All) //在所有索引上查詢
{
From = 0,
Size = 10,
Query = new MatchQuery
{
Field = Infer.Field<User>(f => f.LastName),
Query = "l1"
}
};
var response = _client.Search<User>(request);
生成的請求 URL 為:
POST /_all/_search?typed_keys=true
通過 .LowLever 屬性來使用 Elasticsearch.Net 來進行查詢
使用 Elasticsearch.Net 來進行查詢的契機:
- 當客戶端存在 bug,即使用上述 NEST 的 Search 和 SearchRequest 異常的時候,可以考慮用 Elasticsearch.Net 提供的查詢方式。
- 當你希望用一個匿名物件或者 JSON 字串來進行查詢的時候。
var response = _client.LowLevel.Search<SearchResponse<User>>("users", PostData.Serializable(new
{
from = 0,
size = 10,
query = new
{
match = new
{
lastName = "l1"
}
}
}));
聚合查詢
除了結構化和非結構化查詢之外, ES 同樣支援聚合(Aggregations)查詢:
var result = _client.Search<User>(s => s
.Size(0) //設定為 0 ,可以讓結果只包含聚合的部分:即 hits 屬性中沒有結果,聚合結果顯示在 ”aggregations“
.Query(q =>
q.Match(m =>
m.Field(f => f.FirstName)
.Query("f2")))
.Aggregations(a => //使用 terms 聚合,並指定到桶 last_name 中
a.Terms("last_name", ta =>
ta.Field(f => f.LastName)))
);
- 一般使用 term 聚合來獲取每個儲存桶的文件數量,其中每個桶將以 lastName 作為關鍵字。
更多關於聚合的操作可見此:NEST 教程系列 8 聚合:Writing aggregations | 使用聚