C#NEST弹性搜索查询多个条件

时间:2017-02-09 16:22:03

标签: c# .net elasticsearch nest

我正在做一些测试来改变我的架构。我们想要删除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中翻译我的Query Mongo ...

这很痛苦,但这是弹性查询:

{
  "query": {
    "bool": {
      "must": [
        {"range" : { "totalSearch" : { "gte" : minimumTotalSearch }}},
        {"prefix": { "keywordName": prefix}}
      ]
    }
  },
  "from": startIndex,
  "size": totalItems
}

- &GT;方案: 经过一些斗争编码后,我找到了一种在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"));

但现在我问自己这是否是进行此查询的最佳方式(没有跳过/接受这很容易)。因为我是新的,可能有更优化的查询...

2 个答案:

答案 0 :(得分:1)

您的解决方案看起来不错,但有几点值得强调。

  1. 客户端是线程安全的并且广泛使用缓存,因此建议创建单个实例并重用它;不这样做意味着需要在每次请求时重建缓存,从而降低性能。
  2. 由于range查询找到匹配或不匹配的文档,即它是不需要对匹配文档进行评分的谓词,因此range查询可以包含在{bool中。 1}} query filter子句; Elasticsearch可以使用roaring bitmaps缓存这些子句。
  3. NEST还会将QueryContainer上的运算符(根查询类型)重载为将它们组合起来构建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查询进行评分,同时将其作为filter子句bool查询。

答案 1 :(得分:0)

查询将是这样的。

// object for refering text
var obj = {};

// get all tr except the first one which holds header
$('table tr:not(:first-child)').filter(function() {
  // get td contents and trim out space
  var txt = $('td:last-child', this).text().trim();
  // check already defined in object or not
  if (txt in obj)
    // if defined return true, it's dupe
    return true;
  // else define the property
  obj[txt] = true;
  // add class to filtered tr
}).addClass('dup')

如果您发现任何困难,请告诉我。