如何使用 Solr Provider 控制 Sitecore ContentSearch 中嵌套查询的优先级?

How do I control the priority of nested queries in Sitecore ContentSearch with the Solr Provider?

版本详细信息: 我正在使用 Sitecore 7.5 build 141003,使用 Solr v4.7 作为搜索 engine/indexing 服务器。我还在使用没有自定义索引器的标准 Sitecore Solr 提供程序。

目标: 我将 Sitecore ContentSearch LINQ 与 PredicateBuilder 结合使用来编译一些灵活的嵌套查询。目前,我需要在特定 "Root item" 内搜索,同时排除名称中带有 "folder" 的模板,同时排除路径中带有“/testing”的项目。在某些时候 "Root item" 可能不止一项,路径也可能包含(目前只是“/testing”)。在这些情况下,我们的想法是使用 PredicateBuilder 构建一个外部 "AND" 谓词内部 "OR"s 用于多个 "Root item"s 和路径排除。

问题: 目前,我正在处理有关这些 predicates/conditions 的嵌套顺序和优先级的问题。我一直在测试几种方法和组合,但我一直 运行 关注的问题是 !TemplateName.Contains 和 Item["_fullpath"].Contains 优先于 Paths.Contains,它结束每次都得到 0 个结果。

我正在使用 Search.log 检查查询输出,我一直在针对 Solr 管理员进行手动测试,运行宁查询以比较结果。下面,您将找到我尝试使用 Sitecore Linq 的组合示例,以及它们为 Solr 生成的查询。

原始代码示例:

对根项进行列表的原始测试

// sometimes will be 1, sometimes will be multiple
var rootItems = new List<ID> { pathID };  // simplified to 1 item for now
var query = context.GetQueryable<SearchResultItem>();
var folderFilter = PredicateBuilder.True<SearchResultItem>().And(i => !i.TemplateName.Contains("folder") && !i["_fullpath"].Contains("/testing"));
var pathFilter = PredicateBuilder.False<SearchResultItem>();
pathFilter = rootItems.Aggregate(pathFilter, (current, id) => current.Or(i => i.Paths.Contains(id)));
folderFilter = folderFilter.And(pathFilter);
query.Filter(folderFilter).GetResults();

查询输出: (-_templatename:(*文件夹*) AND -_fullpath:(*/测试*)) 和 _path:(730c169987a44ca7a9ce294ad7151f13)

正如您在上面的输出中看到的,两个 "not contains" 过滤器周围有一组内部括号,优先于路径过滤器。当我在 Solr 管理员中 运行 这个确切的查询时,它 returns 0 结果。但是,如果我删除内部括号,那么它就是一个 "AND" 集,它 return 就是预期的结果。

我使用 PredicateBuilder 的不同组合和方法对此进行了进一步测试,并且每个组合都会产生相同的查询。我什至尝试将两个单独的过滤器 ("query.Filter(pred1).Filter(pred2)") 添加到我的主查询对象,结果是相同的输出。

其他代码示例:

替代。 1 - 将 "Paths.Contains" 直接添加到文件夹过滤器

var query = context.GetQueryable<SearchResultItem>();
var folderFilter = PredicateBuilder.True<SearchResultItem>().And(i => !i.TemplateName.Contains("folder") && !i["_fullpath"].Contains("/testing"));
folderFilter = folderFilter.And(i => i.Paths.Contains(pathID));
query.Filter(folderFilter).GetResults();

查询输出: (-_templatename:(*文件夹*) AND -_fullpath:(*/测试*)) 和 _path:(730c169987a44ca7a9ce294ad7151f13)

Alt 2 - 两个谓词连接到 first

var query = context.GetQueryable<SearchResultItem>();
var folderFilter = PredicateBuilder.True<SearchResultItem>().And(i => !i.TemplateName.Contains("folder") && !i["_fullpath"].Contains("/testing"));
var pathFilter = PredicateBuilder.False<SearchResultItem>().Or(i => i.Paths.Contains(pathID));
folderFilter = folderFilter.And(pathFilter);
query.Filter(folderFilter).GetResults();

查询输出: (-_templatename:(*文件夹*) AND -_fullpath:(*/测试*)) 和 _path:(730c169987a44ca7a9ce294ad7151f13)

Alt 3 - 两个 "inner" 谓词,一个用于 "Not"s,一个用于 [=125] =] 连接到外部谓词

var query = context.GetQueryable<SearchResultItem>();
var folderFilter = PredicateBuilder.True<SearchResultItem>().And(i => !i.TemplateName.Contains("folder") && !i["_fullpath"].Contains("/testing"));
var pathFilter = PredicateBuilder.False<SearchResultItem>().Or(i => i.Paths.Contains(pathID));
var finalPredicate = PredicateBuilder.True<SearchResultItem>().And(folderFilter).And(pathFilter);
query.Filter(finalPredicate).GetResults();

查询输出: (-_templatename:(*文件夹*) AND -_fullpath:(*/测试*)) 和 _path:(730c169987a44ca7a9ce294ad7151f13)

结论: 最终,我正在寻找一种方法来控制这些嵌套 queries/conditions 的优先级,或者我如何构建它们以将路径放在第一位,然后再放置 "Not" 过滤器。如前所述,在某些情况下,我们将有多个 "Root items" 和多个路径排除,我需要查询更多类似的内容:

(-_templatename:(*folder*) AND -_fullpath:(*/testing*) AND (_path:(730c169987a44ca7a9ce294ad7151f13) OR _path:(12c1aa7f60fa4e8d9f0a983bbbb40d8b)))

(-_templatename:(*folder*) AND -_fullpath:(*/testing*) AND (_path:(730c169987a44ca7a9ce294ad7151f13)))

这两个查询 return 结果我 expect/need 当我 运行 他们直接在 Solr 管理中。但是,我似乎无法想出使用 Sitecore ContentSearch Linq 以这种方式输出查询的方法或操作顺序。

有没有其他人对我如何完成此操作有经验?根据建议,我也愿意在没有 Sitecore Linq 的情况下 assemble 这条查询,如果我可以将它结合回 IQueryable 以调用 "GetFacets" 和 "GetResults".

更新: 我没有包括我所做的所有修订,因为 SO 可能会杀了我多长时间。也就是说,我确实在原始示例(顶部)的基础上尝试了另一个细微的变化,结果与其他示例相似:

var folderFilter = PredicateBuilder.True<SearchResultItem>().And(i => !i.TemplateName.Contains("folder")).And(i => !i["_fullpath"].Contains("/testing"));
var rootItems = new List<ID> { pathID, path2 };
// or paths separately
var pathFilter = PredicateBuilder.False<SearchResultItem>();
pathFilter = rootItems.Aggregate(pathFilter, (current, id) => current.Or(i => i.Paths.Contains(id)));   
var finalPredicate = folderFilter.And(pathFilter);
var query = context.GetQueryable<SearchResultItem>();
query.Filter(finalPredicate).GetResults();

查询输出: ((-_templatename:(*folder*) AND -_fullpath:(* /testing*)) AND (_path:(730c169987a44ca7a9ce294ad7151f13) 或 _path:(12c1aa7f60fa4e8d9f0a983bbbb40d8b)))

仍然是围绕“_templatename”和“_fullpath”条件的那些内括号导致了问题。

谢谢。

如果末尾的 2 个工作示例是正确的,那么您需要将查询的各个部分单独 AND 在一起,而不是在单个调用中包含 2 个语句,这是导致嵌套的原因您声明的开头部分:

// the path part of the query. OR together all the locations
var pathFilter = PredicateBuilder.False<SearchResultItem>();
pathFilter = pathFilter.Or(i => i.Paths.Contains(pathID));
pathFilter = pathFilter.Or(i => i.Paths.Contains(pathID2));
...

// the exclusions, build them up seprately
var query = PredicateBuilder.True<SearchResultItem>();
query = query.And(i => !i.TemplateName.Contains("folder"));
query = query.And(i => !i["_fullpath"].Contains("/testing"));

// join both parts together
query = query.And(pathFilter);

这应该给你(伪):

!templateName.Contains("folder") 
AND !_fullpath.Contains("/testing") 
AND (path.Contains(pathID1) || path.Contains(pathID2))

如果您试图排除某些模板,那么您可以首先通过更新 Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config 中的 ExcludeTemplate 设置将它们从您的索引中排除.您无需担心在查询中明确排除它:

<exclude hint="list:ExcludeTemplate">
  <MyTemplateId>{11111111-1111-1111-1111-111111111111}</MyTemplateId>
  <MyTemplateId>{22222222-2222-2222-2222-222222222222}</MyTemplateId>
</exclude>

我试过下面的代码,它确实产生了你需要的输出查询,诀窍是在创建路径过滤器查询时使用 PredicateBuilder.True(),不确定如果这是内容搜索 API 的正常行为,或者它是一个错误

var query = context.GetQueryable<Sitecore.ContentSearch.SearchTypes.SearchResultItem>();
var folderFilter = PredicateBuilder.True<SearchResultItem>().And(i => !i.TemplateName.Contains("folder") && !i["_fullpath"].Contains("/testing"));
var pathFilter = PredicateBuilder.True<SearchResultItem>();
pathFilter = pathFilter.Or(i => i.Paths.Contains(Path1) || i.Paths.Contains(Path2));

folderFilter = folderFilter.And(pathFilter);

好的,我在这里提出了这个问题并将情况也发布到 Sitecore 支持,我刚刚收到回复和一些其他信息。

根据 Solr wiki (http://wiki.apache.org/solr/FAQ),在“搜索”部分,问题 为什么 'foo AND -baz' 匹配文档,但 'foo AND (-bar)' 不匹配t ? 回答了结果返回 0 的原因。

Boolean queries must have at least one "positive" expression (ie; MUST or SHOULD) in order to match. Solr tries to help with this, and if asked to execute a BooleanQuery that does contains only negatived clauses at the topmost level, it adds a match all docs query (ie: :)

If the top level BoolenQuery contains somewhere inside of it a nested BooleanQuery which contains only negated clauses, that nested query will not be modified, and it (by definition) an't match any documents -- if it is required, that means the outer query will not match.

我不确定在 Sitecore Solr 提供程序中构造查询的全部内容,或者他们为什么在嵌套查询中将否定组合在一起,但只有否定的嵌套查询是 returning 0 结果符合预期,根据 Solr 文档。那么,诀窍就是向子查询添加一个“全部匹配”查询 (*:*)。

认为 可能遇到这种情况的任何查询都不必手动执行此操作,支持代表提供了一个补丁 DLL 来替换提供程序,它将自动修改嵌套查询来解决这个问题。

他们还将此记录为错误并提供了该问题的参考编号 398622

现在,生成的查询如下所示:

((-_templatename:(*folder*) AND -_fullpath:(*/testing*) AND *:*) AND _path:(730c169987a44ca7a9ce294ad7151f13))

或者,对于多个查询:

((-_templatename:(*folder*) AND -_fullpath:(*/testing*) AND *:*) AND (_path:(730c169987a44ca7a9ce294ad7151f13) OR _path:(12c1aa7f60fa4e8d9f0a983bbbb40d8b)))

并且结果 return 符合预期。如果其他人遇到此问题,我会使用带有 Sitecore 支持的参考编号,看看他们是否可以提供补丁。您还必须更新 Solr.Index 和 Solr.Indexes.Analytics 配置文件中使用的提供程序。