ElasticSearch NEST笔记

1. 什么是ElasticSearch?

ElasticSearch is a powerful open source search and analytics engine that makes data easy to explore.

可以简单理解成索引加检索的工具,当然它功能多于此。
ElasticSearch分为服务端与客户端,服务端提供REST API,客户端使用REST API。

2.怎么安装Elastic?

  1. 安装JDK(下载地址
  2. 安装ElasticSearch(下载地址
    1. 解压,运行\bin\elasticsearch.bat.
    2. 浏览器输入http://localhost:9200/,可以看到如图:

    3. 安装成功。
  3. 安装ElasticSearch – header plugin https://github.com/mobz/elasticsearch-head

    文档中有详细说明

    完成后,在如下图的地方找到一个html

  4. 安装完成~

3. 如何使用NEST客户端(文档:http://nest.azurewebsites.net/nest/quick-start.html

  1. 连接

    1. var node = new Uri("http://localhost:9200/");
    2.  
    3. var settings = new ConnectionSettings(
    4.     node,
    5.     defaultIndex: " geopoint-tests "
    6. );
    7.  
    8. var client = new ElasticClient(settings);
  2. 添加索引
    1. client.CreateIndex("geopoint-tests");

    上面这句代码是可以不用写的,因为在调用下面的index方法的时候,如果没有指定使用哪个index,ElasticSearch会直接使用我们在setting中的defaultIndex,如果没有,则会自动创建。

    1. client.Index(obj);

    但是如何你需要使用Mapping来调整索引结构,就会需要CreateIndex这个方法。具体的会在下面的Mapping中提到

  3. 添加数据
    1. client.Index(obj)
  4. 搜索

    正常来说,搜索的需求一般是我们传入一个keyword(和需要搜索的field name),返回符合条件的列表,那么搜索就分为全文搜索和单属性搜索。顾名思义,全文搜索就是用keyword去匹配所有的属性,单属性搜索就是只匹配指定的属性。

  • 全文搜索:
      1.  keyword = String.Format("*{0}*", keyword);

        1. //默认的Operator是Or,当keyword是类似于"One Two"之类的中间有空格的时候,会被当成两个关键词搜索,然后搜索结果进行or运算
        2. //所以我们需要根据需求来调整Operator
      2. var searchResults = client.Search<T>(s => s
      3.     .Index(index)
      4.     .Query(q => q.QueryString(qs => qs.Query(keyword).DefaultOperator(Operator.And)))
      5. );
      6.  
      7. return searchResults.Documents;

另外由于ES是分词搜索,所以当我们要用"One"来搜索完整的单词"JustOne"的时候,就必须在"One"外面添加**,类似于SQL里面的%keyword%,但是这样的做法会导致在用完整的单词来搜索的时候搜索不到结果,所以我们需要使用下面的方式(如果有更好的方法请不吝赐教):

      1. wholeKeyword = keyword;
      2. keyword = String.Format("*{0}*", keyword);
      3. QueryContainer query = new QueryStringQuery() { Query = keyword, DefaultOperator = Operator.And };
      4. if(!String.IsNullOrEmpty(wholeKeyword)){
      5.    QueryContainer wholeWordQuery = new QueryStringQuery() { Query = wholeKeyword };
      6.    query = query || wholeWordQuery;
      7. }
      8. var searchResults = client.Search<Person>(s => s
      9.     .Index("zhixiao-application")
      10.     .Query(query)
      11. );
  • 指定属性搜索

    指定属性的搜索有两种:

  1. 使用term Query

     QueryContainer query2 = new TermQuery { Field = item.Key, Value = item.Value.ToLower() };

Term是一个被索引的精确值,也就是说Foo, foo, FOO是不相等的,因此

在使用term query的时候要注意,term query在搜索的Field已经被索引的时候,是不支持大写的。下面为elasticSearch - header测试

所有数据:

大写搜索:

小写搜索:

NEST的使用:

      1. var searchResults = client.Search<Person>(s => s
      2.     .Index("zhixiao-application")
      3.     .Query(q => q.Term(t => t.OnField(f => f.Lastname == "keyword")))
      4. );

        或者(效果一样):

      5. QueryContainer termQuery = new TermQuery { Field = "lastname", Value = "keyword" };
      6. var searchResults = client.Search<Person>(s => s
      7.     .Index("zhixiao-application")
      8.     .Query(termQuery)
      9. );

PS:term query的Field是必须的,如果Field为空,会产生下面的错误           

2.使用 Query String query

QueryString query一般用于全文搜索,但是也可以用于单个属性的搜索(设置DefaultField属性),queryString query可以不区分大小写。QueryString还有一个好处就是我们可以搜索一个term中的一部分,例如lastname为"t Boterhuis 1",那么我们可以用"terhuis"搜索到这个数据(虽然需要在外面包上**),在term query里面就做不到,因为ES把每一个属性的值都分析成一个个单独的term,提高了搜索的效率。

下面为elasticSearch - header测试:

完整term搜索(大写):

完整term搜索(小写):

部分搜索(大写,不带**):

部分搜索(大写,带**):

部分搜索(小写,带**):

多词语搜索:当我们想搜索类似于:"t Boterhuis 2"这样的多个单词构成的keyword(用空格分开),term query是无法查询的,term query顾名思义就是单词查询。不能支持多单词查询

QueryString query:

大家可以看到,第三条也被搜索进来了,这是因为ES把"t Boterhuis 2"解析成了三个词汇"t"" Boterhuis""2"。然后分开搜索,把结果集合并,所以ID为4的记录也被搜索出来了。那么我们如何让合起来搜索呢?

      1. string keyword = "t Boterhuis 2";
      2. QueryContainer wholeWordQuery = new QueryStringQuery() { Query = keyword, DefaultOperator = Operator.And };
      3. var searchResults = client.Search<Person>(s => s
      4.     .Index("zhixiao-application")
      5.     .Query(wholeWordQuery)
      6. );

QueryString query有一个DefaultOperator的属性,我们可以将其设置为And,这样搜索的时候,ES会将几个term的search结果做and运算。

但是有一个比较大的问题是如果我的keyword是"Aberdeen Boterhuis",

这样也可以搜索出结果来:

我们搜索出了ID为7的数据,因为这条数据firstname里面有Aberdeen ,last name里面有Boterhuis。

目前还没有找到比较好的方法来解决这个问题,如果各位有好的方法请不吝赐教。

5.排序

搜索需要我们重新构建索引,这样才能发现错误并且解决他们。

首先我们把原先的索引先删除了

    1. var response = client.DeleteIndex(new DeleteIndexRequest(new IndexNameMarker() { Name = "zhixiao-application", Type = typeof(Person) };

然后重新创建索引

    1. var indexResult = client.CreateIndex("zhixiao- application");
    2. var response1 = client.Map<Person>(m => m.MapFromAttributes());
    3. IEnumerable<Person> persons = new List<Person>
    4. {
    5.     new Person()
    6.     {
    7.         Id = "4",
    8.         Firstname = "Boterhuis-040",
    9.         Lastname = "Gusto-040",
    10.         Chains = new string[]{ "a","b","c" },
    11.     },
    12.     new Person()
    13.     {
    14.         Id = "5",
    15.         Firstname = "[email protected]",
    16.         Lastname = "t Boterhuis 1",
    17.         Chains = new string[]{ "a","b","c" },
    18.     },
    19.     new Person()
    20.     {
    21.         Id = "6",
    22.         Firstname = "Aberdeen #110",
    23.         Lastname = "[email protected]",
    24.         Chains = new string[]{ "a","b","c" },
    25.     },
    26.     new Person()
    27.     {
    28.         Id = "7",
    29.         Firstname = "Aberdeen #110",
    30.         Lastname = "t Boterhuis 2",
    31.         Chains = new string[]{ "a","b","c" },
    32.     },
    33. };
    34. foreach (var person in persons)
    35. {
    36.     client.Index(person);
    37. }

Person的类:

    1. public class Person
    2.     {
    3.         public string Id { get; set; }
    4.         public string Firstname { get; set; }
    5.         public string Lastname { get; set; }
    6.         public string[] Chains { get; set; }
    7.     }

好了,让我们来测试下排序~

  1).ID排序

  正常!

var searchResults = client.Search<Person>(s => s

.Index("zhixiao-application")

.Sort(sort => sort.OnField(f => f.Id).Order(SortOrder.Ascending))

);

2.字符串排序

咦,奇怪,数据哪里去了呢?刚才ID的排序正常,为啥这里就不正常了呢?

我们先断点看下:

可以看到,ConnnectionStatus的status code是200,说明连接成功,但是我们又没有查询出数据来,接下来就需要我们的header工具了

在最后一个tab中,header工具允许我们将ConnectionStatus.Request中的json用于查询,以此来验证对错。我们可以看到,查询的时候报了一个错误

Can‘t sort on string types with more than one value per doc, or more than one token per field

因为我们的数据是

这样的,在解析的时候,firstname会被analyse成多个term,所以在排序的时候,就会出错。

那么我们要怎么办呢?

http://blog.wiercinski.net/2011/uncategorized/elasticsearch-sorting-on-string-types-with-more-than-one-value-per-doc-or-more-than-one-token-per-field/

在这里我找到了一些答案,我们需要将一个我们需要排序的字段mapping成两个不同的字段,一个经过分析(e.g. firstname),一个没有经过分析(e.g. firtname.sort ).

在NEST中,有一个简便的方法,就是在你需要排序的属性上面加一个Attribute

[ElasticProperty(AddSortField = true)]

所以我们先将原先的索引删除,然后重新添加索引。

client.Map<Person>(m => m.MapFromAttributes());

这句代码很重要(在调用它之前需要先CreateIndex),它根据ElasticProperty对对象进行了mapping,使得firstname变成了一个multi fields,这点我们可以从      Request的Json中红色部分看出

{
    "person": {
        "properties": {
            "id": {
                "type": "string"
            },
            "firstname": {
                "type": "multi_field",
                "fields": {
                    "firstname": {
                        "type": "string"
                    },
                    "sort": {
                        "index": "not_analyzed",
                        "type": "string"
                    }
                }
            },
            "lastname": {
                "type": "string"
            },
            "chains": {
                "type": "string"
            }
        }
    }
}

另外在header中可以发现,属性中多了一个person.firstname.sort

所以我们现在可以使用这个属性来进行排序了

代码如下:

var searchResults = client.Search<Person>(s => s

.Index("zhixiao-application")

.Sort(sort => sort.OnField(f => f.Firstname.Suffix("sort")).Ascending())

);

PS:注意上面红色部分的代码

排序结果如下:

成功!

3.距离排序

Elastic Search 自带了距离的排序和距离的筛选。所以我们只需要将索引建好,就可以使用。

好了,下面我们就一步步来进行:

       (1)创建model

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

public class Location

{

    public string Name { get; set; }

 

    [ElasticProperty(Type = FieldType.GeoPoint)]

    public Coordinate Coordinate { get; set; }

}

 

public class Coordinate

{

    public double Lat { get; set; }

 

    public double Lon { get; set; }

}

PS:[ElasticProperty(Type = FieldType.GeoPoint)]这个attribute是为了下面mapping的时候,ES会将其识别为GeoPoint

  (2)创建索引并且mapping

?


1

2

3

4

5

6

7

8

client.CreateIndex("geopoint-tests", s => s

    .AddMapping<Location>(f => f

    .MapFromAttributes()

    .Properties(p => p

        .GeoPoint(g => g.Name(n => n.Coordinate).IndexLatLon())

     )

   )

);

下面是创建索引并且mapping的request json: 

{
    "settings": {
        "index": { }
    },
    "mappings": {
        "location": {
            "properties": {
                "name": {
                    "type": "string"
                },
                "coordinate": {
                    "type": "geo_point",
                    "lat_lon": true
                }
            }
        }
    }
}

      (3)创建数据:为了方便大家看出来正确的排序,我将latitude设置成一样的,这样可以一眼就看出来排序的对错

client.IndexMany(new[]
{

    createLocation("1", 52.310551, 5.07039),

    createLocation("2", 52.310551, 10.761176),

    createLocation("3", 52.310551, 8.07039),

    createLocation("4", 52.310551, 6.07039),

});

private static Location createLocation(string name, double latitude, double longitude)

{

    return new Location

    {

        Name = name,

        Coordinate = new Coordinate { Lat = latitude, Lon = longitude }

    };

}

  (4)排序的使用:

    1. var results = client.Search<Location>(s => s
    2.     .SortGeoDistance(sort => sort.OnField("coordinate").PinTo(52.310551, 4.404954).Ascending()));

  结果:

  

  成功!

   4.距离Filter(其实这个应该分类在query中,不过这个需要在上面排序的代码上面做修改,所以就放到这里了)

  代码如下:

    1. var results = client.Search<Location>(s => s
    2.    .Filter(f => f.GeoDistance("coordinate", fd => fd.Distance(100, GeoUnit.Kilometers).Location(52.310551, 4.404954)))
    3.    .SortGeoDistance(sort => sort.OnField("coordinate").PinTo(52.310551, 4.404954).Ascending()));

  结果如下:

  

6.更新数据

由于比较简单,我就不解释啥了,直接上代码

            Person newperson = new Person()
            {
                Id = "7",
                Firstname = "Aberdeen #110",
                Lastname = "Update",
                Chains = new string[] { "a", "b", "c" },
            };

            UpdateRequest<Person> updateRequest = new UpdateRequest<Person>(7)
            {
                Doc = newperson
            };

            IUpdateResponse updateResponse = updateClient.Update<Person, Person>(updateRequest);
            if (updateResponse.ConnectionStatus.HttpStatusCode != 200)
            {
                updateClient.Index(newperson);
            }

            client.ClearCache();

            var searchResults = updateClient.Search<Person>(s => s
                .Index("zhixiao-application")
                .Query(q => q.Term(t => t.OnField(f => f.Id).Value(7)))
            );

            Console.WriteLine(searchResults.Documents.Count());

            foreach (var person in searchResults.Documents)
            {
                Console.WriteLine(person.Id + "," + person.Lastname);
            }

PS:需要注意的是,如果你需要清除一个属性的值,传入null会导致ES认为你不需要更新这个属性,所以清除一个属性的值我们需要传入一个""。

7.删除数据

//delete one by id

updateClient.Delete<Person>(d => d.Id(7));

//delete one by object

updateClient.Delete<Person>(new Person() { });

//delete the Indices

updateClient.DeleteIndex(new DeleteIndexRequest(new IndexNameMarker() { Name = "zhixiao-application", Type = typeof(Person) }));

时间: 2024-08-06 07:54:50

ElasticSearch NEST笔记的相关文章

Elasticsearch NEST – Examples for mapping between Query and C#

Elasticsearch NEST – Examples for mapping between Query and C# During my training with Elasticsearch I would like to map a query with GET/POST method to C# syntax of NEST. It's very helpful for me to see how NEST composes its internal queries and sen

elasticsearch学习笔记——相关插件

logstash-input-jdbc学习 ES(elasticsearch缩写)的一大优点就是开源,插件众多.所以扩展起来非常的方便,这也造成了它的生态系统越来越强大.这种开源分享的思想真是与天朝格格不入啊.国内的开源社区做了也很长时间,可是也没出现什么拿的出手的东西,可能只还有阿里比较注重分享一些. ES的查询速度非常快,搜索非常快.但是呢,我们的数据还是主要存在传统的关系型数据库中的.有没有什么办法可以将数据库中的数据实时同步到ES中呢.logstash就是这么一个东西. Logstash

Elasticsearch学习笔记-03.1集群健康

本文系本人根据官方文档的翻译,能力有限.水平一般,如果对想学习Elasticsearch的朋友有帮助,将是本人的莫大荣幸. 原文出处:https://www.elastic.co/guide/en/elasticsearch/reference/current/_cluster_health.html 让我们以一个基础的健康检查开始,用这个检查我们可以得知我们的集群工作状态如何.咱们来使用curl做这个检查,不过你也可以使用任何能发起HTTP/REST请求的工具来做这个练习.假设我们仍旧在启动E

elasticsearch 学习笔记

1.安装 https://www.elastic.co/cn/ 官方下载 2.启动 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff } span.s1 { } bin/elasticsearch p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000

Elasticsearch学习笔记-03.3创建索引

来吧,咱们一起创建一个名为"customer"的索引,然后查看集群中的所有索引: PUT /customer?pretty GET /_cat/indices?v 第一个命令使用PUT创建了一个名为customer的索引.我们简单的在命令后追加了一个pretty参数,用于将JSON类型的返回值格式化后打印在控制台. 我们也可以使用POST MAN操作,我们会得到类似下图所示的返回结果: 第二个命令的结果告诉我们(为方便查看,我直接在浏览器中打开了链接:http://localhost:

Elasticsearch学习笔记-03.2查看索引列表

使用下面的命令可以查看所有的索引: GET /_cat/indices?v 或直接在浏览器中打开连接: http://localhost:9200/_cat/indices?v 返回结果: health status index uuid pri rep docs.count docs.deleted store.size pri.store.size 表示在咱们的集群中还没有创建任何索引 本文系本人根据官方文档的翻译,能力有限.水平一般,如果对想学习Elasticsearch的朋友有帮助,将是

Elasticsearch 学习笔记2 集群和数据

集群术语 - 节点: 一个elasticsearch实例(一个elasticsearch进程)就是一个节点 - 集群: 由一个或者多个elasticsearch节点组成 - 主节点: 临时管理集群级别变更:新建/删除索引,新建/移除节点,不参与文档级别变更或者搜索,当数据量增长时,不会成为集群瓶颈,集群只有一个主节点,通过各个节点选举产生 - 分片(shard):是最小级别工作单元,它只是保存了索引中所有数据的一部分 - 主分片:每个文档属于一个单独主分片,主分片数量可以在创建索引时指定,默认个

Elasticsearch安装笔记

下载安装包 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.2.zip 开始执行bin/./elasticsearch的时候会报错 java.lang.RuntimeException: can not run elasticsearch as root at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.j

Elasticsearch学习笔记-02安装

本文系本人根据官方文档的翻译,能力有限.水平一般,如果对想学习Elasticsearch的朋友有帮助,将是本人的莫大荣幸.原文出处:https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html Elasticsearch要求Java最低版本为8. 截止本文撰写的时间,推荐您使用Oracle JDK 1.8.0_73版本.JAVA的安装会因为系统环境的不同而有很大差异,所以我们在这里不会涉及太多