我如何 属性 在 NEST 中汇总 and/or 组合?

How do I property roll up and/or combinations in NEST?

背景

我想做什么

我正在使用的过滤器对象

public class MakeModelFilter : IMakeModelFilter
{
    public string Make { get; set; }
    public List<string> Models { get; set; }
}

整个 API 调用的样子

{
    "MakeModelFilters": [
        {"Make": "BMW", "Models": ["X3", "X5"]}
    ],
    "TypeFilter": [],
    "GenericColorFilter": [],
    "FeaturesFilter": [],
    "MaxMileage" : 100000,
    "PriceRange": {"Min": 1, "Max": 1000000},
    "SearchText": ""
}

我关心的部分是 MakeAndModelFilters 列表(其余部分按当前设计工作)。

我目前获取搜索结果的方式:

var vehicles = _esClient.Search<Vehicle>(s => s
    .From(0).Size(10000) 
    .Query(q => q
        .Filtered(fq => fq
            .Filter(ff => ff
                .Bool(b => b
                    .Must(m=> m.And(
                                m.Or(makeModelFilterList.ToArray()),
                                m.Or(featureFilters.ToArray()), 
                                m.Or(typeFilters.ToArray()),
                                priceRangeFilter, 
                                mileageFilter))
                )
            )
            .Query(qq => qq
                .QueryString(qs => qs.Query(criteria.SearchText))
            )
        )
    )
);

问题

无论我如何构造过滤器,它似乎都过滤掉了所有文档——这不符合我们的最佳利益。 :) 我的布尔逻辑有问题。

我认为问题出在哪里

or一起生成的品牌和型号过滤器列表是通过这种方法生成的:

private List<FilterContainer> GenerateMakeModelFilter(List<MakeModelFilter> makeModelFilters)
{
    var filterList = new List<FilterContainer>();
    foreach (var filter in makeModelFilters)
    {
        filterList.Add(GenerateMakeModelFilter(filter));
    }

    return filterList;
}

此方法调用单独的方法为我拥有的每个 make/model 过滤器生成一个布尔值。

我觉得问题的方法是什么

据我所知,以下方法执行以下操作:

代码如下:

private FilterContainer GenerateMakeModelFilter(MakeModelFilter makeModelFilter)
{
    if (string.IsNullOrWhiteSpace(makeModelFilter.Make)) { throw new ArgumentNullException(nameof(makeModelFilter));}

    var makeFilter = new TermFilter { Field = Property.Path<Vehicle>(it => it.Make), Value = makeModelFilter.Make };
    var boolMake = new BoolFilter { Must = new List<FilterContainer> { makeFilter } };

    var modelFilters = GenerateFilterList(Property.Path<Vehicle>(it => it.Model), makeModelFilter.Models);

    if (!modelFilters.Any())
    {
        // If it has a make but no model, generate boolFilter make only.
        return boolMake;
    }

    var orModels = new OrFilter {Filters = modelFilters};
    var boolModels = new BoolFilter {Must = new List<FilterContainer> {orModels}};

    var boolMakeAndModels = new AndFilter {Filters = new List<FilterContainer> {boolMake, boolModels}};

    return new BoolFilter {Must = new List<FilterContainer> {boolMakeAndModels}};
}

仅供参考,GenerateFilterList 只是创建了一个术语过滤器列表,return 是该列表。

仅供参考:生成的 ElasticSearch JSON

这可能是我哪里出错的线索(尽管它很大)。我只是盯着它看了太久,我想我看不到它。

{
  "from": 0,
  "size": 10000,
  "query": {
    "filtered": {
      "filter": {
        "bool": {
          "must": [
            {
              "and": {
                "filters": [
                  {
                    "or": {
                      "filters": [
                        {
                          "bool": {
                            "must": [
                              {
                                "and": {
                                  "filters": [
                                    {
                                      "bool": {
                                        "must": [
                                          {
                                            "term": {
                                              "make": "BMW"
                                            }
                                          }

                                        ]
                                      }
                                    },
                                    {
                                      "bool": {
                                        "must": [
                                          {
                                            "or": {
                                              "filters": [
                                                {
                                                  "term": {
                                                    "model": "x3"
                                                  }
                                                },
                                                {
                                                  "term": {
                                                    "model": "x5"
                                                  }
                                                }

                                              ]
                                            }
                                          }
                                        ]
                                      }
                                    }
                                  ]
                                }
                              }
                            ]
                          }
                        }
                      ]
                    }
                  },
                  { },
                  { },
                  {
                    "range": {
                      "sellingPriceUSD": {
                        "lte": "1000000",
                        "gte": "1"
                      }
                    }
                  },
                  {
                    "range": {
                      "miles": {
                        "lte": "100000"

                      }
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}

重构 1:更多地转向按位运算

根据 Martijn 的回答和他引用的 Zachary's post,我已将我的 GenerateFilterList 更新为 return 串联的 filterContainer:

private FilterContainer GenerateFilterList(PropertyPathMarker path, List<string> filter)
{
    if (filter == null || filter.Count <= 0){ return null; }

    FilterContainer returnFilter = null;
    foreach (var aFilter in filter)
    {
        returnFilter |= new TermFilter {Field = path, Value = aFilter.ToLowerInvariant()};
    }

    return returnFilter;
}

然后对于我的 GenerateMakeModelFilter,我对 "model filters" 执行 "and",这应该是按位或基于上面的代码:

private FilterContainer GenerateMakeModelFilter(MakeModelFilter makeModelFilter)
{
    if (string.IsNullOrWhiteSpace(makeModelFilter.Make)) { throw new ArgumentNullException(nameof(makeModelFilter)); }

    var makeFilter = new TermFilter { Field = Property.Path<Vehicle>(it => it.Make), Value = makeModelFilter.Make };

    var modelFilters = GenerateFilterList(Property.Path<Vehicle>(it => it.Model), makeModelFilter.Models);

    return makeFilter && modelFilters;
}

这缩短了检索查询的部分:

QueryContainer textQuery = new QueryStringQuery() {Query = criteria.SearchText };
FilterContainer boolFilter = makeModelFilter || featureFilter || typeFilter || priceRangeFilter || mileageFilter;

var vehicles = _esClient.Search<Vehicle>(s => s
.From(0).Size(10000) //TODO: Extract this into a constant or setting in case the inventory grows to 10k+. This prevents it from paging.
.Query(q => q
    .Filtered(fq => fq
        .Filter(filter => filter.Bool(bf => bf.Must(boolFilter)))
        .Query(qq => textQuery)
        )
    )   
);

return vehicles.Documents.ToList<IVehicle>();

...但我仍然没有文件 returned。我到底错过了什么?如果我有一辆型号为 "Civic" 和 "Accord" 的本田汽车,以及一辆没有型号的 "BMW" 汽车,我应该会收到所有带有 honda + civic || 的车辆本田+雅阁||宝马+(任何型号)。我会坚持的。

And,or, & not 过滤器可能无法满足您的要求。它们是一种特殊的过滤器结构,在组合不对位集进行操作的过滤器时性能更好。必须阅读此主题:

https://www.elastic.co/blog/all-about-elasticsearch-filter-bitsets

知道何时使用 and/or/not 过滤器与 bool 过滤器可能会非常混乱,使用 Elasticsearch 2.0,您可以在所有上下文中使用 bool 过滤器,它会知道如何最好地执行 filters/queries其条款。不用你指点了!

此外,尽管 bool filter/query 被命名为 bool,但它执行的是一元 bool,而您可能认为它是二进制 bool。

这就是布尔子句是 must/should/must_not 与 and/or/not 的原因。

在 NEST 中,如果您使用 && || ! 运算符并结合括号,我们将组成一个或多个 bool 查询,以便它以您编写的二进制 bool 方式运行在 C# 中。

例如:

.Query(q=>q
    (q.Term("language", "php")
        && !q.Term("name", "Elastica")
    )
    ||
    q.Term("name", "NEST")
)

如果您需要更动态的列表,您可以使用赋值运算符 !=&=:

private FilterContainer GenerateMakeModelFilter(List<MakeModelFilter> makeModelFilters)
{
    FilterContainer filter = null;
    foreach (var filter in makeModelFilters)
    {
        filter |= GenerateMakeModelFilter(filter);
    }

    return filter;
}

同样,如果您重构 GenerateMakeModelFilter 以利用 C# 布尔运算符重载,您最终会得到一个更易于阅读和调试的查询。无论是 C# 还是发送到 Elasticsearch 的查询。

我们的文档对此进行了更详细的介绍 http://nest.azurewebsites.net/nest/writing-queries.html

更新

很棒的重构!现在我们可以专注于 elasticsearch 中的映射。当您索引 json 属性 时,它会通过 an analysis chain 获取单个字符串并尝试从中提取 1 个或多个术语,这些术语将存储在 lucene 的倒排索引中。

默认情况下,elasticsearch 将使用 standard analyzer

分析所有字符串字段

在您的情况下,BMW 将通过 standard analyzer 拆分空格(Unicode 标准附件 #29 确切地说 )并将其小写。

因此倒排索引中的术语是bmw。在 elasticsearch 中,一些查询也会在查询时进行分析,因此 a match query for BMW 在查询倒排索引之前也会被分析并转换为 bmw,因此无论大小写如何,都会找到文档BMW 在查询时。

您正在使用的术语 query/filter 在查询时 未被 分析,因此它将尝试在倒排索引中查找 BMW索引只有 bmw。如果您只想要精确的术语匹配,这很好。如果你设置 your mapping so that a field is not analyzed you could for instance do exact matches on New York without worrying its actually stored as two separate terms new and york and inadvertently also get results from New New York