C#
中訪問Elasticsearch
主要透過兩個包NEST
和Elasticsearch.Net
,NEST
用高階語法糖封裝了Elasticsearch.Net
可以透過類Linq
的方式進行操作,而Elasticsearch.Net
相比之下更為原始直接非常自由。
注意:ES
的8.X
以上的版本有新的包Elastic.Clients.Elasticsearc
支援。
此處使用NEST
,我們透過Nuget
安裝,如下圖:
1、準備結構
準備以下實體
public class Company { public string Id { get; set; } public string Name { get; set; } public string Description { get; set; } public User User { get; set; } } public class User { public string Name { get; set; } public int Gender { get; set; } }
2、連線ES
如果是單機連線如下程式碼,可以直接在Uri
上指定賬號密碼,也可以使用ConnectionSettings
的BasicAuthentication
來配置賬號密碼:
var singleNode = new Uri("http://elastic:123456@localhost:9200"); var connSettings = new ConnectionSettings(singleNode);
//connSettings.BasicAuthentication("elastic", "123456"); var esClient = new ElasticClient(connSettings);
如果是多個節點叢集則如下程式碼:
var nodes = new Uri[] { new Uri("http://esNode1:9200"), new Uri("http://esNode2:9200"), new Uri("http://esNode3:9200") }; var pool = new StaticConnectionPool(nodes); var settings = new ConnectionSettings(pool); var client = new ElasticClient(settings);
3、建立索引
索引名稱必須符合規則否則建立會失敗,比如索引只能小寫,具體程式碼如下:
var indexName = "my_index1";//索引名稱 var res = await esClient.Indices.CreateAsync(indexName, o => o.Map(g => g.AutoMap<Company>()));//對映結構
也可以在向索引插入資料的時候自動判斷是否存在索引,不存在會自動建立。索引結構欄位對映一但建立就無法修改,可以透過新建索引然後轉移資料的方式修改索引結構,但是可以往裡面新增欄位對映,比如修改了實體結構新的欄位將會被對映。
4、插入資料
使用IndexDocumentAsync
方法插入單條資料需要在ConnectionSettings
的DefaultIndex
方法設定預設索引。使用IndexAsync
插入單條資料時需要選擇指定索引,如下:
var singleNode = new Uri("http://localhost:9200"); var connSettings = new ConnectionSettings(singleNode); connSettings.BasicAuthentication("elastic", "123456"); var esClient = new ElasticClient(connSettings.DefaultIndex("my_index1")); var indexName = "my_index1"; var company = new Company() { Name = "超級公司bulk", Description = "超級描述bulk", }; var res1 = await esClient.IndexDocumentAsync(company); var res2 = await esClient.IndexAsync(company, g => g.Index(indexName))
如果需要批次插入需要用BulkDescriptor
物件包裹,然後使用BulkAsync
方法插入,或者不要包裹直接用IndexManyAsync
方法插入,具體如下:
var company = new Company() { Name = "超級公司bulk", Description = "超級描述bulk" }); BulkDescriptor descriptor = new BulkDescriptor(); descriptor.Index<Company>(op => op.Document(company).Index(indexName)); var res = await esClient.BulkAsync(descriptor); //var list = new List<Company>(); //list.Add(company); //var res = await esClient.IndexManyAsync(list, indexName);
如果實體有Id
則會使用Id
的值做為_id
的索引文件唯一值,或者可以透過手動指定如await esClient.IndexAsync(company, g => g.Index(indexName).Id(company.Id))
,如果id相同執行插入操作則為更新不會重複插入。在新增後是會返回id等資訊可以加以利用。
5、刪除資料
刪除指定單條資料需要知道資料的id
,如下兩種方式:
DocumentPath<Company> deletePath = new DocumentPath<Company>(Guid.Empty); var delRes = await esClient.DeleteAsync(deletePath, g => g.Index(indexName)); //或者 IDeleteRequest request = new DeleteRequest(indexName, "1231"); var delRes = await esClient.DeleteAsync(request);
多條刪除使用DeleteByQueryAsync
方法進行匹配刪除,下面兩種方式等價,刪除Description
欄位模糊查詢有描述
的資料(最多10條):
var req = new DeleteByQueryRequest<Company>(indexName) { MaximumDocuments = 10,//一次最多刪幾條 Query = new MatchQuery() { Field = "description", Query = "描述" } }; var result = await esClient.DeleteByQueryAsync(req); //等價於 var result = await esClient.DeleteByQueryAsync<Company>(dq => dq.MaximumDocuments(10).Query( q => q.Match(tr => tr.Field(fd => fd.Description).Query("描述"))).Index(indexName) );
6、更新資料
除了上述插入資料時自動根據id
進行更新外還有以下的主動更新。
根據id
更新單條資料以下程式碼等價,可以更新部分欄位值,但是_id
是確定就不會更改的雖然對應的Id
欄位已被修改:
DocumentPath<Company> deletePath = new DocumentPath<Company>("1231"); var res = await esClient.UpdateAsync(deletePath ,(p) => p.Doc(company).Index(indexName)); //等價於 IUpdateRequest<Company, Company> request = new UpdateRequest<Company, Company>(indexName, "1231") { Doc = new Company() { Id = "888", Description = "11111", } }; var res = await esClient.UpdateAsync(request);
如果有多個id
更新多條資料可以用如下方法:
var res = esClient.Bulk(b => b.UpdateMany(new List<Company>() { new Company() { Id="1231", } }, (b, u) => b.Id(u.Id).Index(indexName).Doc(new Company { Name = "我無語了" })));
透過條件批次更新如下,
var req = new UpdateByQueryRequest<Company>(indexName) { MaximumDocuments = 10,//一次最多更新幾條 Query = new MatchQuery() { Field = "description", Query = "66", }, Script = new ScriptDescriptor() .Source($"ctx._source.description = params.description;") .Params(new Dictionary<string, object> { { "description","小時了123123123"} }), Refresh = true }; var result = await esClient.UpdateByQueryAsync(req);
7、資料查詢
上文中的更新等都用到了查詢過濾,此處就用網上的這個例子吧:
var result = client.Search<VendorPriceInfo>( s => s .Explain() //引數可以提供查詢的更多詳情。 .FielddataFields(fs => fs //對指定欄位進行分析 .Field(p => p.vendorFullName) .Field(p => p.cbName) ) .From(0) //跳過的資料個數 .Size(50) //返回資料個數 .Query(q => q.Term(p => p.vendorID, 100) // 主要用於精確匹配哪些值,比如數字,日期,布林值或 not_analyzed的字串(未經分析的文字資料型別): && q.Term(p => p.vendorName.Suffix("temp"), "姓名") //用於自定義屬性的查詢 (定義方法檢視MappingDemo) && q.Bool( //bool 查詢 b => b .Must(mt => mt //所有分句必須全部匹配,與 AND 相同 .TermRange(p => p.Field(f => f.priceID).GreaterThan("0").LessThan("1"))) //指定範圍查詢 .Should(sd => sd //至少有一個分句匹配,與 OR 相同 .Term(p => p.priceID, 32915), sd => sd.Terms(t => t.Field(fd => fd.priceID).Terms(new[] {10, 20, 30})),//多值 //|| //sd.Term(p => p.priceID, 1001) //|| //sd.Term(p => p.priceID, 1005) sd => sd.TermRange(tr => tr.GreaterThan("10").LessThan("12").Field(f => f.vendorPrice)) ) .MustNot(mn => mn//所有分句都必須不匹配,與 NOT 相同 .Term(p => p.priceID, 1001) , mn => mn.Bool( bb=>bb.Must(mt=>mt .Match(mc=>mc.Field(fd=>fd.carName).Query("至尊")) )) ) ) )//查詢條件 .Sort(st => st.Ascending(asc => asc.vendorPrice))//排序 .Source(sc => sc.Include(ic => ic .Fields( fd => fd.vendorName, fd => fd.vendorID, fd => fd.priceID, fd => fd.vendorPrice))) //返回特定的欄位 );