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))) //返回特定的字段
- );
-