本文中,數據(文檔)之間的關係類型,是嵌套文檔
事前準備:
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,各種查資料,終於不負有心人,功能最終都實現了,如果有錯誤或者有改進之處,望不吝賜教。