Elasticsearch.Net、NEST 交流群:523061899

   demo源码 https://github.com/huhangfei/NestDemos

Elasticsearch.Net与NEST是Elasticsearch为C#提供的一套客户端驱动,方便C#调用Elasticsearch服务接口。Elasticsearch.Net是对Elasticsearch服务接口较基层的的实现,NEST是在前者基础之上进行的封装,方法更简洁使用更方便。本文是针对NEST 2.X的使用的总结。

引用

引用dll 

NEST.dll
Elasticsearch.Net.dll  
Newtonsoft.Json.dll

 

注:仅支持.net framework>=4.5

概念

 

存储结构:

在Elasticsearch中,文档(Document)归属于一种类型(type),而这些类型存在于索引(index)中. 

类比传统关系型数据库:

Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices   -> Types  -> Documents -> Fields

 

客户端语法

 

链式lambda 表达式( powerful query DSL)语法

s => s.Query(q => q
    .Term(p => p.Name, "elasticsearch")
)

 

对象初始化语法

var searchRequest = new SearchRequest<VendorPriceInfo>
{
  Query = new TermQuery
  {
      Field = "name",
      Value = "elasticsearch"
   }
};

 

Connection链接

//单node
Var node = new Uri(“……”);
var settings = new ConnectionSettings(node);
 
//多uris
Var uris = new Uri [] {
    new Uri(“……”),
    new Uri(“……”)
};
var pool = new StaticConnectionPool(uris);
 
//多node
Var nodes = new Node [] {
    new Node (new Uri(“……”)),
    new Node (new Uri(“……”))
};
var pool = new StaticConnectionPool(nodes);
 
var settings = new ConnectionSettings(pool);
 
var client = new ElasticClient(settings);

 

注:nest默认字段名首字母小写,如果要设置为与Model中一致按如下设置。(强烈建议使用该设置)

var settings = new ConnectionSettings(node).DefaultFieldNameInferrer((name) => name);

 

连接池类型

//单节点
IConnectionPool pool = new SingleNodeConnectionPool(urls.FirstOrDefault());
 
//请求时随机发送请求到各个正常的节点,不请求异常节点,异常节点恢复后会重新被请求
IConnectionPool pool = new StaticConnectionPool(urls); 
 
IConnectionPool pool = new SniffingConnectionPool(urls);
//false.创建客户端时,随机选择一个节点作为客户端的请求对象,该节点异常后不会切换其它节点
//true,请求时随机请求各个正常节点,不请求异常节点,但异常节点恢复后不会重新被请求
pool.SniffedOnStartup = true;
 
//创建客户端时,选择第一个节点作为请求主节点,该节点异常后会切换其它节点,待主节点恢复后会自动切换回来
IConnectionPool pool = new StickyConnectionPool(urls);

 

索引选择

 

方式1

//链接设置时设置默认使用索引
var settings = new ConnectionSettings().DefaultIndex("defaultindex");

 

方式2

//链接设置时设置索引与类型对应关系,操作时根据类型使用对应的索引
var settings = new ConnectionSettings().MapDefaultTypeIndices(m => m.Add(typeof(Project), "projects")  );

 

方式3

//执行操作时指定索引
client.Search<VendorPriceInfo>(s => s.Index("test-index"));
client.Index(data,o=>o.Index("test-index"));
 

优先级:方式3 > 方式2 > 方式1

索引(Index)

 

数据唯一Id

1) 默认以“Id”字段值作为索引唯一Id值,无“Id”属性,Es自动生成唯一Id值,添加数据时统一类型数据唯一ID已存在相等值,将只做更新处理。 注:自动生成的ID有22个字符长,URL-safe, Base64-encoded string universally unique identifiers, 或者叫UUIDs。 
2) 标记唯一Id值

[ElasticsearchType(IdProperty = "priceID")]
    public class VendorPriceInfo
    {
        public Int64 priceID { get; set; }
        public int oldID { get; set; }
        public int source { get; set; }
}

 

3) 索引时指定

client.Index(data, o => o.Id(data.vendorName));

 

优先级: 3) > 2) > 1)

类型(Type)

1) 默认类型为索引数据的类名(自动转换为全小写) 2) 标记类型

 [ElasticsearchType(Name = "datatype")]
    public class VendorPriceInfo
    {
        public Int64 priceID { get; set; }
        public int oldID { get; set; }
        public int source { get; set; }
    }

 

3) 索引时指定

client.Index(data, o => o.Type(new TypeName() { Name = "datatype", Type = typeof(VendorPriceInfo) }));

 

client.Index(data, o => o.Type<MyClass>());//使用 2)标记的类型

 

优先级:3)> 2) > 1)

创建

client.CreateIndex("test2");
//基本配置
IIndexState indexState=new IndexState()
{
       Settings = new IndexSettings()
       {
            NumberOfReplicas = 1,//副本数
            NumberOfShards = 5//分片数
       }
};
client.CreateIndex("test2", p => p.InitializeUsing(indexState));
 
//创建并Mapping
client.CreateIndex("test-index3", p => p.InitializeUsing(indexState).Mappings(m => m.Map<VendorPriceInfo>(mp => mp.AutoMap())));

 

注:索引名称必须小写

判断

client.IndexExists("test2");

 

删除

client.DeleteIndex("test2");

 

映射

 

概念

每个类型拥有自己的映射(mapping)或者模式定义(schema definition)。一个映射定义了字段类型、每个字段的数据类型、以及字段被Elasticsearch处理的方式。

获取映射

 var resule = client.GetMapping<VendorPriceInfo>();

 

特性

 /// <summary>
    /// VendorPrice 实体
    /// </summary>
    [ElasticsearchType(IdProperty = "priceID", Name = "VendorPriceInfo")]
    public class VendorPriceInfo
    {
        [Number(NumberType.Long)]
        public Int64 priceID { get; set; }
        [Date(Format = "mmddyyyy")]
        public DateTime modifyTime { get; set; }
        /// <summary>
        /// 如果string 类型的字段不需要被分析器拆分,要作为一个正体进行查询,需标记此声明,否则索引的值将被分析器拆分
        /// </summary>
        [String(Index = FieldIndexOption.NotAnalyzed)]
        public string pvc_Name { get; set; }
        /// <summary>
        /// 设置索引时字段的名称
        /// </summary>
        [String(Name = "PvcDesc")]
        public string pvc_Desc { get; set; }
        /// <summary>
        /// 如需使用坐标点类型需添加坐标点特性,在maping时会自动映射类型
        /// </summary>
        [GeoPoint(Name = "ZuoBiao",LatLon = true)]
        public GeoLocation Location { get; set; }
    }

 

映射

//根据对象类型自动映射
            var result= client.Map<VendorPriceInfo>(m => m.AutoMap());
            //手动指定
            var result1 = client.Map<VendorPriceInfo>(m => m.Properties(p => p
            .GeoPoint(gp => gp.Name(n => n.Location)// 坐标点类型
                .Fielddata(fd => fd
                    .Format(GeoPointFielddataFormat.Compressed)//格式 array doc_values compressed disabled
                    .Precision(new Distance(2, DistanceUnit.Meters)) //精确度
                    ))
            .String(s => s.Name(n => n.b_id))//string 类型
            ));
            //在原有字段下新增字段(用于存储不同格式的数据,查询使用该字段方法查看【基本搜索】)
            //eg:在 vendorName 下添加无需分析器分析的值  temp
            var result2 = client.Map<VendorPriceInfo>(
                m => m
                    .Properties(p => p.String(s => s.Name(n => n.vendorName).Fields(fd => fd.String(ss => ss.Name("temp").Index(FieldIndexOption.NotAnalyzed))))));

 

注:映射时已存在的字段将无法重新映射,只有新加的字段能映射成功。所以最好在首次创建索引后先进性映射再索引数据。 
注:映射时同一索引中,多个类型中如果有相同字段名,那么在索引时可能会出现问题(会使用第一个映射类型)。

数据

 

数据操作

 

说明

  • 添加数据时,如果文档的唯一id在索引里已存在,那么会替换掉原数据;
  • 添加数据时,如果索引不存在,服务会自动创建索引;
  • 如果服务自动创建索引,并索引了数据,那么索引的映射关系就是服务器自动设置的;
  • 通常正确的使用方法是在紧接着创建索引操作之后进行映射关系的操作,以保证索引数据的映射是正确的。然后才是索引数据;
  • 文档在Elasticsearch中是不可变的,执行Update事实上Elasticsearch的处理过程如下:
    • 从旧文档中检索JSON
    • 修改JSON值
    • 删除旧文档
    • 索引新文档

所以我们也可以使用Index来更新已存在文档,只需对应文档的唯一id。

添加索引数据

 

添加单条数据

var data = new VendorPriceInfo() { vendorName = "测试"};
client.Index(data);

 

添加多条数据

var datas = new List<VendorPriceInfo> {
                new VendorPriceInfo(){priceID = 1,vendorName = "test1"},
                new VendorPriceInfo(){priceID = 2,vendorName = "test2"}};
client.IndexMany(datas);

 

删除数据

 

单条数据

DocumentPath<VendorPriceInfo> deletePath=new DocumentPath<VendorPriceInfo>(7);
client.Delete(deletePath);

 

IDeleteRequest request = new DeleteRequest("test3", "vendorpriceinfo", 0);
client.Delete(request);

 

注:删除时根据唯一id删除

集合数据

  Indices indices = "test-1";
            Types types = "vendorpriceinfo";
            //批量删除 需要es安装 delete-by-query插件
            var result = client.DeleteByQuery<VendorPriceInfo>(indices, types,
                dq =>
                    dq.Query(
                        q =>
                            q.TermRange(tr => tr.Field(fd => fd.priceID).GreaterThanOrEquals("5").LessThanOrEquals("10")))
                            );
 

更新数据

 

更新所有字段

DocumentPath<VendorPriceInfo> deletePath=new DocumentPath<VendorPriceInfo>(2);
Var response=client.Update(deletePath,(p)=>p.Doc(new VendorPriceInfo(){vendorName = "test2update..."}));
//
IUpdateRequest<VendorPriceInfo, VendorPriceInfo> request = new UpdateRequest<VendorPriceInfo, VendorPriceInfo>(deletePath)
            {
                Doc = new VendorPriceInfo()
                {
                    priceID = 888,
                    vendorName = "test4update........"
                }
            };
            var response = client.Update<VendorPriceInfo, VendorPriceInfo>(request);
 

更新部分字段

IUpdateRequest<VendorPriceInfo, VendorPriceInfoP> request = new UpdateRequest<VendorPriceInfo, VendorPriceInfoP>(deletePath)
            {
                Doc = new VendorPriceInfoP()
                {
                    priceID = 888,
                    vendorName = "test4update........"
                }
 
            };
            var response = client.Update(request); 
 

更新部分字段

IUpdateRequest<VendorPriceInfo, object> request = new UpdateRequest<VendorPriceInfo, object>(deletePath)
            {
                Doc = new
                {
                    priceID = 888,
                    vendorName = " test4update........"
                }
            };
            var response = client.Update(request);
//
client.Update<VendorPriceInfo, object>(deletePath, upt => upt.Doc(new { vendorName = "ptptptptp" }));
 

注:更新时根据唯一id更新

更新时使用本版号加锁机制

 //查询到版本号
            var result = client.Search<MessageInfo>(
                s =>
                    s.Index("ep_epdb8buddy_imchatinfo_messageinfo_nopaging_msguid_index").Type("MessageInfo")
                        .Query(q => q.Term(tm => tm.Field("MsgUID").Value("5DCC-A8C4-44NV-VKGU"))).Size(1).Version());
            foreach (var s in result.Hits)
            {
                Console.WriteLine(s.Id+"  -  "+s.Version);
            }
 
            var path = new DocumentPath<MessageInfo>("5DCC-A8C4-44NV-VKGU");
            //更新时带上版本号 如果服务端版本号与传入的版本好相同才能更新成功
            var response = client.Update(path, (p) => p.Index("ep_epdb8buddy_imchatinfo_messageinfo_nopaging_msguid_index-2017.03").Type("MessageInfo")
                .Version(2).Doc(new MessageInfo() { MsgUID = "5DCC-A8C4-44NV-VKGU", FromUserName = "测测测" + DateTime.Now }));

 

获取数据

var response = client.Get(new DocumentPath<VendorPriceInfo>(0));
//
var response =
 client.Get(new DocumentPath<VendorPriceInfo>(0),pd=>pd.Index("test4").Type("v2")); 
//多个
var response = client.MultiGet(m => m.GetMany<VendorPriceInfo>(new List<long> { 1, 2, 3, 4 })); 

 

注:获取时根据唯一id获取

批处理操作

var listOps = new List<IBulkOperation>
            {
                //更新
                new BulkUpdateOperation<VendorOfferNewsByCsId, object>(new VendorOfferNewsByCsId() {},
                    new {Name = "new-project2"}) {Id = "3"},
                //删除
                new BulkDeleteOperation<VendorOfferNewsByCsId>(new Id(1))
                //...
            };
            IBulkRequest bulkRequest=new BulkRequest("indexname","typename");
            var request = new BulkRequest()
            {
 
                Operations = listOps
            };
            client.Bulk(request);

 

搜索

 

说明

  • 搜索分页时随着分页深入,资源花费是成倍增长的。限制from+size⇐10000分页说明资料 资料
  • 需要扫描所有数据时,使用滚屏扫描。 扫描与滚屏资料
  • 搜索中提供了查询(Query)和过滤(Filter):
  • query是要计算相关性评分的,filter不要;
  • query结果不缓存,filter缓存。
  • 全文搜索、评分排序,使用query;
  • 是非过滤,精确匹配,使用filter。

基本搜索

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))) //返回特定的字段
               );
            //TResult
            var result1 = client.Search<VendorPriceInfo, VendorPriceInfoP>(s => s.Query(
                q => q.MatchAll()
                )
                .Size(15)
                );

 

相关文章: