.Net Core中使用NEST簡單操作Elasticsearch

以往清泉發表於2023-02-17

C#中訪問Elasticsearch主要透過兩個包NESTElasticsearch.NetNEST用高階語法糖封裝了Elasticsearch.Net可以透過類Linq的方式進行操作,而Elasticsearch.Net相比之下更為原始直接非常自由。

注意:ES8.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上指定賬號密碼,也可以使用ConnectionSettingsBasicAuthentication來配置賬號密碼:

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方法插入單條資料需要在ConnectionSettingsDefaultIndex方法設定預設索引。使用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))) //返回特定的欄位
               );
    

 

相關文章