C# NEST Elastic Search 多条件查询
C# NEST Elastic Search Query Multiple Conditions
我正在做一些测试来改变我的架构。我们想放弃 MongoDB 并改用 ElasticSearch。但我真的不知道这项技术。我正在使用 NEST 作为驱动程序,无法翻译我曾经在 mongo.
中使用的查询
public async Task<IEnumerable<Keyword>> GetKeywordsAsync(string prefix, int startIndex, int totalItems, int minimumTotalSearch, CancellationToken cancellationToken)
{
return await _mongoReader.GetEntitiesAsync<KeywordEntity, Keyword>(CollectionName,
queryable =>
queryable.Where(entity => entity.KeywordName.StartsWith(prefix) && entity.TotalSearch >= minimumTotalSearch)
.OrderBy(entity => entity.KeywordName)
.Select(_keywordConverter.GetConverter())
.Skip(startIndex)
.Take(totalItems),
cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<TModel>> GetEntitiesAsync<TDocument, TModel>(string collectionName,
Func<IMongoQueryable<TDocument>, IMongoQueryable<TModel>> getQueryable,
CancellationToken cancellationToken)
{
var documents = GetDocuments<TDocument>(collectionName);
var query = getQueryable(documents.AsQueryable());
return await query.ToListAsync(cancellationToken).ConfigureAwait(false);
}
这是我为 ElasticSearch 做的一个简单发现:
public async Task<IEnumerable<TModel>> FindAsync<TModel, TValue>(string index,
Expression<Func<TModel, TValue>> findExpression, TValue value, int limit,
CancellationToken cancellationToken) where TModel : class
{
var searchRequest = new SearchRequest<TModel>(index)
{
Query =
Query<TModel>.Match(
a => a.Field(findExpression).Query(string.Format(CultureInfo.InvariantCulture, "{0}", value))),
Size = limit
};
var resGet = await _elasticClientFactory.Create().SearchAsync<TModel>(searchRequest, cancellationToken).ConfigureAwait(false);
return resGet?.Documents;
}
问题是我无法在 Elastic 中翻译我的查询 Mongo ...
这很痛苦,但这是弹性查询:
{
"query": {
"bool": {
"must": [
{"range" : { "totalSearch" : { "gte" : minimumTotalSearch }}},
{"prefix": { "keywordName": prefix}}
]
}
},
"from": startIndex,
"size": totalItems
}
--> 解决方案:
经过一番努力编码后,我找到了一种在 C# 中进行查询的方法:
var result =
ecf.Create()
.Search<KeywordEntity>(
a => a.Query(
z =>
z.Bool(
e =>
e.Must(r => r.Range(t => t.Field(y => y.TotalSearch).GreaterThanOrEquals(minimumTotalSearch)),
t => t.Prefix(y => y.KeywordName, prefix)))).Index("keywords"));
但现在我问自己这是否是执行此查询的最佳方法(没有 skip/take 这很简单)。因为我是新手,所以可能有一个更优化的查询 ...
查询应该是这样的。
client.Search<KeywordEntity>(s => s.Index("<INDEX NAME>")
.Type("<TYPE NAME>")
.Query(q =>q
.Bool(b => b.
Must(prefix => prefix.Prefix(pre => pre.OnField("KeywordName").Value("PREFIX QUERY")))
.Must(range => range.Range(ran => ran.OnField("TotalSearch").GreaterOrEquals(minimumTotalSearch)))
)).SortAscending("KeywordName")
.From(StartIndex)
.Size(totalItems));
如果您发现任何困难,请告诉我。
您的解决方案看起来不错,但有几点值得强调。
- 客户端为thread-safe,大量使用缓存,建议创建单例并复用;不这样做将意味着需要根据每个请求重建缓存,从而降低性能。
- 由于
range
查询查找匹配或不匹配的文档,即它是一个不需要对匹配文档进行评分的谓词,因此 range
查询可以包含在bool
查询filter
子句; Elasticsearch 可以使用 roaring bitmaps. 缓存这些子句
NEST 还重载了 QueryContainer
(根查询类型)上的运算符作为 shorthand 以组合它们以构建 bool
查询。然后你的解决方案就可以变成(加上上面的建议)
var searchResponse = client.Search<KeywordEntity>(s => s
.Index("keywords")
.Query(q => q
.Prefix(p => p.KeywordName, prefix) && +q
.Range(r => r
.Field(y => y.TotalSearch)
.GreaterThanOrEquals(minimumTotalSearch)
)
)
);
您可以使用 .From()
和 .Size()
(别名分别为 .Skip()
和 .Take()
)进行分页,并指定仅返回部分字段集来自使用 source filtering 的来源。一个更完整的例子是
var client = new ElasticClient();
var minimumTotalSearch = 10;
var prefix = "prefix";
var startIndex = 10;
var totalItems = 10;
var searchResponse = client.Search<KeywordEntity>(s => s
.Index("keywords")
.Query(q => q
.Prefix(p => p.KeywordName, prefix) && +q
.Range(r => r
.Field(y => y.TotalSearch)
.GreaterThanOrEquals(minimumTotalSearch)
)
)
// source filtering
.Source(sf => sf
.Includes(f => f
.Fields(
ff => ff.KeywordName,
ff => ff.TotalSearch
)
)
)
// sorting. By default, documents will be sorted by _score descending
.Sort(so => so
.Ascending(a => a.KeywordName)
)
// skip x documents
.Skip(startIndex)
// take next y documents
.Take(totalItems)
);
这将构建查询
{
"from": 10,
"size": 10,
"sort": [
{
"keywordName": {
"order": "asc"
}
}
],
"_source": {
"includes": [
"keywordName",
"totalSearch"
]
},
"query": {
"bool": {
"must": [
{
"prefix": {
"keywordName": {
"value": "prefix"
}
}
}
],
"filter": [
{
"range": {
"totalSearch": {
"gte": 10.0
}
}
}
]
}
}
}
最后一件事 :) 因为在你的 Mongo 查询中,你是按前缀升序排序的,你也可以放弃在 Elasticsearch 查询中对 prefix
查询进行评分,方法是将其设为 [= bool
查询中的 16=] 子句。
我正在做一些测试来改变我的架构。我们想放弃 MongoDB 并改用 ElasticSearch。但我真的不知道这项技术。我正在使用 NEST 作为驱动程序,无法翻译我曾经在 mongo.
中使用的查询public async Task<IEnumerable<Keyword>> GetKeywordsAsync(string prefix, int startIndex, int totalItems, int minimumTotalSearch, CancellationToken cancellationToken)
{
return await _mongoReader.GetEntitiesAsync<KeywordEntity, Keyword>(CollectionName,
queryable =>
queryable.Where(entity => entity.KeywordName.StartsWith(prefix) && entity.TotalSearch >= minimumTotalSearch)
.OrderBy(entity => entity.KeywordName)
.Select(_keywordConverter.GetConverter())
.Skip(startIndex)
.Take(totalItems),
cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<TModel>> GetEntitiesAsync<TDocument, TModel>(string collectionName,
Func<IMongoQueryable<TDocument>, IMongoQueryable<TModel>> getQueryable,
CancellationToken cancellationToken)
{
var documents = GetDocuments<TDocument>(collectionName);
var query = getQueryable(documents.AsQueryable());
return await query.ToListAsync(cancellationToken).ConfigureAwait(false);
}
这是我为 ElasticSearch 做的一个简单发现:
public async Task<IEnumerable<TModel>> FindAsync<TModel, TValue>(string index,
Expression<Func<TModel, TValue>> findExpression, TValue value, int limit,
CancellationToken cancellationToken) where TModel : class
{
var searchRequest = new SearchRequest<TModel>(index)
{
Query =
Query<TModel>.Match(
a => a.Field(findExpression).Query(string.Format(CultureInfo.InvariantCulture, "{0}", value))),
Size = limit
};
var resGet = await _elasticClientFactory.Create().SearchAsync<TModel>(searchRequest, cancellationToken).ConfigureAwait(false);
return resGet?.Documents;
}
问题是我无法在 Elastic 中翻译我的查询 Mongo ...
这很痛苦,但这是弹性查询:
{
"query": {
"bool": {
"must": [
{"range" : { "totalSearch" : { "gte" : minimumTotalSearch }}},
{"prefix": { "keywordName": prefix}}
]
}
},
"from": startIndex,
"size": totalItems
}
--> 解决方案: 经过一番努力编码后,我找到了一种在 C# 中进行查询的方法:
var result =
ecf.Create()
.Search<KeywordEntity>(
a => a.Query(
z =>
z.Bool(
e =>
e.Must(r => r.Range(t => t.Field(y => y.TotalSearch).GreaterThanOrEquals(minimumTotalSearch)),
t => t.Prefix(y => y.KeywordName, prefix)))).Index("keywords"));
但现在我问自己这是否是执行此查询的最佳方法(没有 skip/take 这很简单)。因为我是新手,所以可能有一个更优化的查询 ...
查询应该是这样的。
client.Search<KeywordEntity>(s => s.Index("<INDEX NAME>")
.Type("<TYPE NAME>")
.Query(q =>q
.Bool(b => b.
Must(prefix => prefix.Prefix(pre => pre.OnField("KeywordName").Value("PREFIX QUERY")))
.Must(range => range.Range(ran => ran.OnField("TotalSearch").GreaterOrEquals(minimumTotalSearch)))
)).SortAscending("KeywordName")
.From(StartIndex)
.Size(totalItems));
如果您发现任何困难,请告诉我。
您的解决方案看起来不错,但有几点值得强调。
- 客户端为thread-safe,大量使用缓存,建议创建单例并复用;不这样做将意味着需要根据每个请求重建缓存,从而降低性能。
- 由于
range
查询查找匹配或不匹配的文档,即它是一个不需要对匹配文档进行评分的谓词,因此range
查询可以包含在bool
查询filter
子句; Elasticsearch 可以使用 roaring bitmaps. 缓存这些子句
NEST 还重载了 QueryContainer
(根查询类型)上的运算符作为 shorthand 以组合它们以构建 bool
查询。然后你的解决方案就可以变成(加上上面的建议)
var searchResponse = client.Search<KeywordEntity>(s => s
.Index("keywords")
.Query(q => q
.Prefix(p => p.KeywordName, prefix) && +q
.Range(r => r
.Field(y => y.TotalSearch)
.GreaterThanOrEquals(minimumTotalSearch)
)
)
);
您可以使用 .From()
和 .Size()
(别名分别为 .Skip()
和 .Take()
)进行分页,并指定仅返回部分字段集来自使用 source filtering 的来源。一个更完整的例子是
var client = new ElasticClient();
var minimumTotalSearch = 10;
var prefix = "prefix";
var startIndex = 10;
var totalItems = 10;
var searchResponse = client.Search<KeywordEntity>(s => s
.Index("keywords")
.Query(q => q
.Prefix(p => p.KeywordName, prefix) && +q
.Range(r => r
.Field(y => y.TotalSearch)
.GreaterThanOrEquals(minimumTotalSearch)
)
)
// source filtering
.Source(sf => sf
.Includes(f => f
.Fields(
ff => ff.KeywordName,
ff => ff.TotalSearch
)
)
)
// sorting. By default, documents will be sorted by _score descending
.Sort(so => so
.Ascending(a => a.KeywordName)
)
// skip x documents
.Skip(startIndex)
// take next y documents
.Take(totalItems)
);
这将构建查询
{
"from": 10,
"size": 10,
"sort": [
{
"keywordName": {
"order": "asc"
}
}
],
"_source": {
"includes": [
"keywordName",
"totalSearch"
]
},
"query": {
"bool": {
"must": [
{
"prefix": {
"keywordName": {
"value": "prefix"
}
}
}
],
"filter": [
{
"range": {
"totalSearch": {
"gte": 10.0
}
}
}
]
}
}
}
最后一件事 :) 因为在你的 Mongo 查询中,你是按前缀升序排序的,你也可以放弃在 Elasticsearch 查询中对 prefix
查询进行评分,方法是将其设为 [= bool
查询中的 16=] 子句。