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

本文详述了如何在Elasticsearch中处理嵌套文档,包括安装配置、连接、映射、创建索引、增删改查及分页、分组查询等功能实现。

本文中,数据(文档)之间的关系类型,是嵌套文档

事前准备:

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 =>
        {
            //默认根据分值排序
            if (string.IsNullOrWhiteSpace(orderby))
                sd.Descending(SortSpecialField.Score);
            else
            {
                //排序
                orderby = orderby.Trim();
                switch (orderby)
                {
                    //嵌套排序--价格升序
                    case "PriceSortAsc"://价格升序
                        sd.Field(f => f.Field(ff => ff.GoodsSkuList.FirstOrDefault().SkuPrice)
                            .NestedPath(p => p.GoodsSkuList.FirstOrDefault())
                            .Order(SortOrder.Ascending)
                            .Mode(SortMode.Min) //sku最小值排序
                        );
                        break;

                    //嵌套排序--价格降序
                    case "PriceSortDesc"://价格降序
                        sd.Field(f => f.Field(ff => ff.GoodsSkuList.FirstOrDefault().SkuPrice)
                            .NestedPath(p => p.GoodsSkuList.FirstOrDefault())
                            .Order(SortOrder.Descending)
                            .Mode(SortMode.Min) //sku最小值排序
                        );
                        break;

                    case "TimeSort"://时间排序
                        sd.Descending(d => d.AddTime);
                        break;

                    default:
                        sd.Descending(SortSpecialField.Score);
                        break;
                }
            }

            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 =>
        {
            //默认根据分值排序
            if (string.IsNullOrWhiteSpace(orderby))
                sd.Descending(SortSpecialField.Score);
            else
            {
                //排序
                orderby = orderby.Trim();
                switch (orderby)
                {
                    //嵌套排序--价格升序
                    case "PriceSortAsc"://价格升序
                        sd.Field(f => f.Field(ff => ff.GoodsSkuList.FirstOrDefault().SkuPrice)
                            .NestedPath(p => p.GoodsSkuList.FirstOrDefault())
                            .Order(SortOrder.Ascending)
                            .Mode(SortMode.Min) //sku最小值排序
                        );
                        break;

                    //嵌套排序--价格降序
                    case "PriceSortDesc"://价格降序
                        sd.Field(f => f.Field(ff => ff.GoodsSkuList.FirstOrDefault().SkuPrice)
                            .NestedPath(p => p.GoodsSkuList.FirstOrDefault())
                            .Order(SortOrder.Descending)
                            .Mode(SortMode.Min) //sku最小值排序
                        );
                        break;

                    case "TimeSort"://时间排序
                        sd.Descending(d => d.AddTime);
                        break;

                    default:
                        sd.Descending(SortSpecialField.Score);
                        break;
                }
            }

            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,各种查资料,终于不负有心人,功能最终都实现了,如果有错误或者有改进之处,望不吝赐教。

### .NET Framework 使用 `Elastic.Clients.Elasticsearch` 操作指南 #### 安装 NuGet 包 为了在.NET Framework项目中集成Elasticsearch客户端,需先安装对应的NuGet包。可以通过Visual Studio的NuGet管理器或者命令行工具来完成此操作。 对于命令行方式,在项目的根目录下执行如下命令: ```shell dotnet add package Elastic.Clients.Elasticsearch ``` 如果使用的是较旧版本的.NET Framework,则可能需要指定兼容的具体版本号[^1]。 #### 创建连接配置并初始化客户端对象 创建一个新的C#类文件用于封装与Elasticsearch交互的功能逻辑。在此类内部定义静态方法或属性以便于访问Elasticsearch服务。下面是一个简单的例子展示如何设置基本认证以及建立到集群节点的链接: ```csharp using System; using Elastic.Clients.Elasticsearch; public class ElasticsearchHelper { private static readonly IElasticClient _client; static ElasticsearchHelper() { var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200")) .BasicAuthentication("username", "password") // 如果有权限控制的话 ; _client = new ElasticClient(settings); } public static IElasticClient GetClient => _client; } ``` 这段代码展示了怎样构建一个带有基础身份验证机制的客户端实例,并将其存储在一个私有的静态字段里供后续调用。 #### 执行索引、查询等操作 有了上述准备之后就可以轻松地向Elasticsearch发送请求了。这里给出几个常见的API示例说明: ##### 添加文档至特定索引内 假设有一个名为`my-index`的目标索引,现在要往里面插入一条记录: ```csharp var documentId = Guid.NewGuid().ToString(); var response = await ElasticsearchHelper.GetClient.IndexDocumentAsync<dynamic>(new { id = documentId, title = "Example Document", content = "This is an example of indexing a document." }, i => i.Index("my-index")); if (!response.IsValid) Console.WriteLine($"Error adding document {documentId}: {response.DebugInformation}"); else Console.WriteLine($"Successfully added document with ID={documentId}."); ``` 此处利用匿名类型的特性简化了数据结构的设计过程;实际应用时建议根据业务场景设计合适的DTO模型。 ##### 对已存在的索引发起搜索请求 当想要检索符合条件的数据项时可以采用如下的做法: ```csharp var searchResponse = await ElasticsearchHelper.GetClient.SearchAsync<dynamic>(s => s .Index("my-index") .Query(q => q.Match(m => m.Field(f => f.title).Query("example"))) ); foreach (var hit in searchResponse.Hits) { Console.WriteLine($"{hit.Id}: {hit.Source?.title ?? "<no title>"}"); } ``` 这个片段实现了针对`my-index`内的所有条目按照标题中含有关键词"example"的标准筛选匹配的结果集,并逐个打印出来。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值