C# Asp.net中使用Elasticsearch5.x 的NEST客戶端

本文中,數據(文檔)之間的關係類型,是嵌套文檔

事前準備:

1、安裝:

elasticsearch-5.6.2

2、引用:

Elasticsearch.Net(這裏使用的是6.0.0)

Nest(這裏使用的是6.0.0)

一、連接(核心):

public class ESProvider
{
    public ElasticClient ESClient;

    public ESProvider()
    {
        try
        {
            //單個節點
            //var node = new Uri(ElasticSearchSetting.ServerNode);
            //ConnectionSettings settings = new ConnectionSettings(node);
            //settings.RequestTimeout(TimeSpan.FromSeconds(ElasticSearchSetting.RequestTimeout));//超時時間
            //settings.DefaultIndex(ElasticSearchSetting.DefaultIndexName.ToLower());//默認索引(可選,索引名必須小寫)
            //ESClient = new ElasticClient(settings);

            //多節點(方便擴展)
            string server = ElasticSearchSetting.ServerNode;
            string[] serverArray = server.Split(',');
            Uri[] nodesArray = new Uri[serverArray.Length];
            for (int i = 0; i < serverArray.Length; i++)
            {
                nodesArray[i] = new Uri(serverArray[i]);
            }
            //var connectionPool = new SniffingConnectionPool(nodesArray);
            var connectionPool = new StaticConnectionPool(nodesArray);
            var settings = new ConnectionSettings(connectionPool);
            settings.RequestTimeout(TimeSpan.FromSeconds(ElasticSearchSetting.RequestTimeout));//超時時間
            settings.DefaultIndex(ElasticSearchSetting.DefaultIndexName.ToLower());//默認索引(可選,索引名必須小寫)
            ESClient = new ElasticClient(settings);
        }
        catch (Exception ex)
        {
        }
    }
}

配置文件:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <ElasticSearch>
    <!--節點(如果多個節點則用","號隔開)-->
    <add key="ServerNode" value="http://localhost:9200"></add>
    <!--超時時間(單位秒)-->
    <add key="RequestTimeout" value="120"></add>
  </ElasticSearch>
</configuration>

二、映射(簡單映射,sku嵌套在spu中):

//IdProperty指定主鍵
    [ElasticsearchType(Name = "ES_GoodsSpu", IdProperty = "GoodsSpuId")]
    public class ES_GoodsSpu
    {
        [Keyword(Index = true)]
        public string GoodsSpuId { get; set; }
        
        [Text(Index = true, Analyzer = "ik_max_word")]
        public string GoodsName { get; set; }

        [Keyword(Index = false)]
        public string GoodsMainImg { get; set; }

        [Text(Index = true, Analyzer = "ik_max_word")]
        public string GoodsContent { get; set; }

        [Text(Index = true, Analyzer = "ik_max_word")]
        public string GoodsLabel { get; set; }
        
        [Keyword(Index = true)]
        public string AddTime { get; set; }
        
        [Nested]
        public List<ES_GoodsSku> GoodsSkuList { get; set; }
    }

    public class ES_GoodsSku
    {
        [Keyword(Index = true)]
        public string GoodsSpuId { get; set; }
        [Keyword(Index = true)]
        public string GoodsSkuId { get; set; }
        [Keyword(Index = true)]
        public string SkuName { get; set; }
        [Number(NumberType.Double, Index = false)]
        public double SkuWidth { get; set; }
        [Number(NumberType.Double, Index = false)]
        public double SkuLength { get; set; }
        [Number(NumberType.Double, Index = false)]
        public double SkuHeight { get; set; }
        [Number(NumberType.Byte, Index = true)]
        public byte SkuColor { get; set; } 
        //Elasticsearch中沒有decimal等效的類型,最近的類型是double
        [Number(NumberType.Double, Index = true)]
        public decimal SkuPrice { get; set; }
        [Keyword(Index = false)]
        public DateTime AddTime { get; set; }
    }

三、創建索引:

public bool CreateIndex<ES_GoodsSku>(string indexName) 
{
    try
    {
        if (string.IsNullOrWhiteSpace(indexName))
            return false;

        indexName = indexName.ToLower();

        IndexState indexState = new IndexState
        {
            Settings = new IndexSettings
            {
                NumberOfReplicas = 1, //副本數
                NumberOfShards = 5 //分片數
            }
        };

        ICreateIndexResponse response = ESClient.CreateIndex(indexName, p => p
            //.InitializeUsing(indexState)//默認即可
            .Mappings(m => m.Map<ES_GoodsSku>(mp => mp.AutoMap()))//自動映射
        );

        return response.IsValid;
    }
    catch (Exception ex)
    {
        return false;
    }
}

四、添加:

public bool Add(ES_GoodsSku model,string indexName)
{
    try
    {
        if (model == null)
            return false;

        var response = ESClient.Index(model, x => x.Index(indexName));
        return response.IsValid;
    }
    catch (Exception ex)
    {
        return false;
    }
}

五、刪除:

/// <summary>
/// 單個刪除
/// </summary>
/// <param name="spuId"></param>
/// <returns></returns>
public bool DeleteBySpuId(string spuId)
{
    try
    {
        if (string.IsNullOrWhiteSpace(spuId))
            return false;

        var response = ESClient.DeleteByQuery<ES_GoodsSpu>(
            i => i
                .Index(indexName)
                .Query(q => q.Term(t => t.Field(f => f.GoodsSpuId).Value(spuId)))
            );

        return response.IsValid;
    }
    catch (Exception ex)
    {
        return false;
    }
}

/// <summary>
/// 批量刪除
/// </summary>
/// <param name="idArray"></param>
/// <returns></returns>
public bool DeleteBySpuIdArray(string[] spuIdArray)
{
    try
    {
        if (spuIdArray.Length <= 0)
            return false;

        var response = ESClient.DeleteByQuery<ES_GoodsSpu>(
            i => i
                .Index(indexName)
                .Query(q => q.Terms(t => t.Field(f => f.GoodsSpuId).Terms(spuIdArray)))
            );

        return response.IsValid;
    }
    catch (Exception ex)
    {
        return false;
    }
}

六、查詢:

1、簡單查詢(包括嵌套查詢)

/// <summary>
/// 查詢
/// </summary>
/// <param name="param">參數</param>
/// <param name="num">數量</param>
/// <param name="orderby">排序</param>
/// <returns></returns>
public List<ES_GoodsSpu> GetList(Dictionary<string, object> param, int num, string orderby = null)
{
    try
    {
        //排序
        Func<SortDescriptor<ES_GoodsSpu>, IPromise<IList<ISort>>> sortDesc = sd =>
        {
            //根據分值排序
            sd.Ascending(SortSpecialField.Score);
                    
            sd.Ascending(d => d.AddTime);
 
            return sd;
        };
 
        //must 條件
        var mustQuerys = new List<Func<QueryContainerDescriptor<ES_GoodsSpu>, QueryContainer>>();
        //must not 條件
        var mustNotQuerys = new List<Func<QueryContainerDescriptor<ES_GoodsSpu>, QueryContainer>>();
        //should 條件
        var shouldQuerys = new List<Func<QueryContainerDescriptor<ES_GoodsSpu>, QueryContainer>>();
 
        if (param != null && param.Count > 0)
        {
            foreach (KeyValuePair<String, object> kvp in param)
            {
                if (string.IsNullOrWhiteSpace(kvp.Value.ToString()))
                    continue;
 
                string key = kvp.Key.ToLower();
                string value = kvp.Value.ToString();
                switch (key)
                {
                    case "spuids": //多個id集合
                        if (!string.IsNullOrWhiteSpace(value) && value.Split(',').Count() > 0)
                        {
                            string[] spuIdArray = value.Split(',');
                            mustQuerys.Add(mq => mq.Terms(tm => tm.Field(f => f.GoodsSpuId).Terms(spuIdArray)));
                        }
                        break;
                    case "name":
                        mustQuerys.Add(mq => mq.Term(tm => tm.Field(f => f.GoodsName).Value(value)));
                        break;
                    case "color":
                        byte color = 0;
                        byte.TryParse(value, out color);
                        if (color > 0)
                        {
                            //嵌套查詢(Nested,Path)
                            mustQuerys.Add(mq => mq.Nested(n => n.Path(p => p.GoodsSkuList).Query(q => q.Term(tm => tm.Field(f => f.GoodsSkuList.FirstOrDefault().SkuColor).Value(color)))));
                        }
                        break;
                    case "minprice":
                        double minprice = -1;//默認值
                        double.TryParse(value, out minprice);
                        if (minprice > -1)
                        {
                            double maxprice0 = 9999999999;//需要和LessThanOrEquals一起才起作用,所以這裏給個最大值
                            //嵌套查詢(Nested,Path)
                            mustQuerys.Add(mq => mq.Nested(n => n.Path(p => p.GoodsSkuList).Query(q => q.Range(tm => tm.Field(f => f.GoodsSkuList.FirstOrDefault().SkuPrice).GreaterThanOrEquals(minprice).LessThanOrEquals(maxprice0)))));
                        }
                        break;
                    case "maxprice":
                        double maxprice = -1;//默認值
                        double.TryParse(value, out maxprice);
                        if (maxprice > -1)
                        {
                            //嵌套查詢(Nested,Path)
                            mustQuerys.Add(mq => mq.Nested(n => n.Path(p => p.GoodsSkuList).Query(q => q.Range(tm => tm.Field(f => f.GoodsSkuList.FirstOrDefault().SkuPrice).LessThanOrEquals(maxprice)))));
                        }
                        break;
                }
            }
        }
 
        //搜索
        var searchResults = base.ESClient.Search<ES_GoodsSpu>(s => s
            .Index(indexName)
            .Size(num)
            .Query(q => q.Bool(b => b.Must(mustQuerys).MustNot(mustNotQuerys).Should(shouldQuerys)))
            .Sort(sortDesc)
            );
 
        return searchResults.Documents.ToList();
    }
    catch (Exception ex)
    {
        LogObj.GetLogService().LogDetailError(ex);
        return null;
    }
}

2、分頁查詢

/// <summary>
/// 分頁查詢
/// </summary>
/// <param name="pageIndex">頁索引</param>
/// <param name="pageSize">頁大小</param>
/// <param name="param">參數</param>
/// <param name="totalCount">總數量</param>
/// <param name="totalPage">總頁數</param>
/// <param name="orderby">排序</param>
/// <returns></returns>
public List<ES_GoodsSpu> GetPageList(int pageIndex, int pageSize, Dictionary<string, object> param, ref long totalCount, ref long totalPage, string orderby = null)
{
    try
    {
        //排序
        Func<SortDescriptor<ES_GoodsSpu>, IPromise<IList<ISort>>> sortDesc = sd =>
        {
            //根據分值排序
            sd.Descending(SortSpecialField.Score);
            sd.Descending(d => d.AddTime); 
            return sd;
        };
 
        //must 條件
        var mustQuerys = new List<Func<QueryContainerDescriptor<ES_GoodsSpu>, QueryContainer>>();
        //should 條件
        var shouldQuerys = new List<Func<QueryContainerDescriptor<ES_GoodsSpu>, QueryContainer>>();
 
        if (param != null && param.Count > 0)
        {
            foreach (KeyValuePair<String, object> kvp in param)
            {
                if (string.IsNullOrWhiteSpace(kvp.Value.ToString()))
                    continue;
 
                string key = kvp.Key.ToLower();
                string value = kvp.Value.ToString();
                switch (key)
                {
                    case "name":
                        mustQuerys.Add(mq => mq.Term(tm => tm.Field(f => f.GoodsName).Value(value)));
                        break;
                    case "querykey":
                        //should條件
                        shouldQuerys.Add(mq => mq.Term(tm => tm.Field(f => f.GoodsName).Value(value)));
                        shouldQuerys.Add(mq => mq.Term(tm => tm.Field(f => f.GoodsLabel).Value(value)));
                        shouldQuerys.Add(mq => mq.Term(tm => tm.Field(f => f.GoodsContent).Value(value)));
                        break;
                }
            }
        }               
 
        //搜索
 
        var searchResults = base.ESClient.Search<ES_GoodsSpu>(s => s
            .Index(indexName)
            .From(pageSize * (pageIndex - 1))
            .Size(pageSize)
            .Query(q => q.Bool(b => b.Must(mustQuerys).Should(shouldQuerys)))
            .Sort(sortDesc)
            );//匹配全部
 
                
        //總數、總頁數
        totalCount = searchResults.Total;
        totalPage = (long)Math.Ceiling((double)totalCount / (double)pageSize);
 
        //返回
        return searchResults.Documents.ToList();
    }
    catch (Exception ex)
    {
        return null;
    }
}

3、分組查詢(根據品牌id,獲取每個品牌下商品數量)

/// <summary>
/// 根據品牌id分組,統計每個品牌下的商品數量
/// </summary>
/// <returns></returns>
public List<Tuple<string, long>> GetGroupByBrandId(List<string> brnadIdList)
{
    try
    {
        if (brnadIdList.Count <= 0)
            return null;

        string groupName = "BrandID_group";
        var result = base.ESClient.Search<ES_GoodsSpu>(s => s
            .Index(indexName)
            .Query(q => q.Terms(tm => tm.Field(f => f.BrandID).Terms(brnadIdList.ToArray())))
            .Aggregations(ag => ag
                    .Terms(groupName, t => t
                        .Field("brandID.keyword") //分組字段需要爲keyword類型,所以這裏加了".keyword"後綴,並且brandID名稱要和es相同(包括大小寫)
                        .Size(brnadIdList.Count) //分組數(這裏和品牌id集合數相同)
                        )
                )
            );

        if (result.IsValid)
        {
            List<Tuple<string, long>> tupleList = new List<Tuple<string, long>>();
            var vendorIdGroup = (BucketAggregate)result.Aggregations[groupName];
            foreach (var bucket in vendorIdGroup.Items)
            {
                var obj = (KeyedBucket<Object>)bucket;
                string brandId = obj.Key.ToString();//品牌id
                long num = (long)obj.DocCount;//該品牌下的商品數量
                Tuple<string, long> tuple = new Tuple<string, long>(brandId, num);
                tupleList.Add(tuple);
            }
            return tupleList;
        }
        return null;
    }
    catch (Exception ex)
    {
        return null;
    }
}

上面代碼中Field("brandID.keyword"),如果字段不是keyword類型,會報exception [type=search_phase_execution_exception, reason=all shards failed]錯誤。

 

最後:

這裏只是把我的項目中代碼簡單複製了過來,也是第一次使用ElasticSearch,各種查資料,終於不負有心人,功能最終都實現了,如果有錯誤或者有改進之處,望不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章