ElasticSearch NEST筆記

ElasticSearch NEST筆記 html

1. 什麼是ElasticSearch?java

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

 

能夠簡單理解成索引加檢索的工具,固然它功能多於此。
ElasticSearch分爲服務端與客戶端,服務端提供REST API,客戶端使用REST API。
git

 

2.怎麼安裝Elastic? github

  1. 安裝JDK(下載地址
  2. 安裝ElasticSearch(下載地址
    1. 解壓,運行\bin\elasticsearch.bat.
    2. 瀏覽器輸入http://localhost:9200/能夠看到如圖:

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

    文檔中有詳細說明 web

    完成後,在以下圖的地方找到一個html json

  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,若是沒有,則會自動建立。 oracle

    1. client.Index(obj);

    可是如何你須要使用Mapping來調整索引結構,就會須要CreateIndex這個方法。具體的會在下面的Mapping中提到 app

  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 = "sales@historichousehotels.com",
    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 = "sales@historichousehotels.com",
    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
 
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
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) }));

相關文章
相關標籤/搜索