本文中,数据(文档)之间的关系类型,是嵌套文档
事前准备:
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,各种查资料,终于不负有心人,功能最终都实现了,如果有错误或者有改进之处,望不吝赐教。