Как управлять приоритетом вложенных запросов в Sitecore ContentSearch с помощью Solr Provider?
Сведения о версии: я работаю с Sitecore 7.5 build 141003, используя Solr v4.7 в качестве сервера поисковой системы / индексации. Я также использую стандартный поставщик Sitecore Solr без пользовательских индексаторов.
Целевая цель: я использую Sitecore ContentSearch LINQ с PredicateBuilder для компиляции некоторых гибких и вложенных запросов. В настоящее время мне нужно искать в определенном "корневом элементе", исключая при этом шаблоны с "папкой" в названии, а также исключая элементы с "/testing" в своем пути. В какой-то момент "корневым элементом" может быть более одного элемента, и поэтому путь может содержать (в настоящее время просто "/testing". В этих случаях идея состоит в том, чтобы использовать PredicateBuilder для создания внешнего предиката "И" с внутренним ". ИЛИ для нескольких "корневых элементов" и исключений пути.
Проблема: В данный момент я имею дело с вопросом о порядке размещения и приоритетах этих предикатов / условий. Я тестировал несколько подходов и комбинаций, но проблема, с которой я продолжаю сталкиваться, - это!TemplateName.Contains и Item["_pullpath"]. Содержит приоритеты над 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:(* папка *) И -_fullpath:(*/testing*)) И _path:(730c169987a44ca7a9ce294ad7151f13)
Как видно из вышеприведенного вывода, вокруг двух фильтров "не содержит" есть внутренний набор скобок, который имеет приоритет над фильтром Path. Когда я запускаю этот точный запрос в администраторе Solr, он возвращает 0 результатов. Однако, если я удаляю внутреннюю скобку, чтобы она представляла собой единый набор "И", он возвращает ожидаемые результаты.
Я проверил это далее с различными комбинациями и подходами к 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:(* папка *) И -_fullpath:(*/testing*)) И _path:(730c169987a44ca7a9ce294ad7151f13)
Alt 2 - два предиката, соединенные с первым
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:(* папка *) И -_fullpath:(*/testing*)) И _path:(730c169987a44ca7a9ce294ad7151f13)
Alt 3 - два "внутренних" предиката, один для "Not" и один для "Paths", соединенных с внешним предикатом
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:(* папка *) И -_fullpath:(*/testing*)) И _path:(730c169987a44ca7a9ce294ad7151f13)
Вывод: В конечном счете, я ищу способ управления установлением приоритетов для этих вложенных запросов / условий, или как я могу построить их так, чтобы сначала указывались пути, а после фильтров "Не". Как уже упоминалось, есть условия, когда у нас будет несколько "корневых элементов" и несколько исключений пути, когда мне нужно запросить что-то более похожее на:
(-_templatename:(* папка *) И -_fullpath: (* / testing *) И (_path:(730c169987a44ca7a9ce294ad7151f13) ИЛИ _path:(12c1aa7f60fa4e8d9f0a983bbbb40d8b))
ИЛИ ЖЕ
(-_templatename:(* папка *) AND -_fullpath:(*/testing*) AND (_path:(730c169987a44ca7a9ce294ad7151f13)))
Оба этих запроса возвращают результаты, которые я ожидаю / нуждаюсь, когда запускаю их непосредственно в админе Solr. Однако я не могу придумать подход или порядок операций, использующий Sitecore ContentSearch Linq для вывода запроса таким способом.
У кого-нибудь еще есть опыт, как мне это сделать? В зависимости от предложения, я также готов собрать этот фрагмент запроса без Sitecore Linq, если я смогу вернуть его обратно в 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:(* папка *) AND -_fullpath:(*/testing*)) И (_path:(730c169987a44ca7a9ce294ad7151f13) ИЛИ _path: (12c1aa7f60fa4e8d9f0a983bbbb40d8))
И это все еще те внутренние скобки вокруг условий "_templatename" и "_fullpath", которые вызывают проблемы.
Благодарю.
3 ответа
Хорошо, я поднял этот вопрос здесь и опубликовал ситуацию в поддержку Sitecore, и я только что получил ответ и некоторую дополнительную информацию.
Согласно Solr wiki ( http://wiki.apache.org/solr/FAQ), в разделе "Поиск" возникает вопрос,почему "foo AND -baz" соответствует документам, но "foo AND (-bar)" не? отвечает, почему результаты возвращаются 0.
Булевы запросы должны иметь хотя бы одно "положительное" выражение (т.е. ДОЛЖНО или СЛЕДУЕТ) для соответствия. Solr пытается помочь с этим, и если его попросят выполнить BooleanQuery, который содержит только отрицательные предложения на самом верхнем уровне, он добавляет запрос на сопоставление всех документов (т.е.::)
Если BoolenQuery верхнего уровня содержит где-то внутри него вложенный BooleanQuery, который содержит только отрицательные предложения, этот вложенный запрос не будет изменен, и он (по определению) не будет соответствовать ни одному документу - если это требуется, это означает, что внешний запрос не будет соответствовать.
Я не уверен, что полностью делается для построения запроса в поставщике Sitecore Solr, или почему они группируют негативы во вложенном запросе, но вложенный запрос только с отрицаниями возвращает 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)))
И результаты возвращаются, как и ожидалось. Если кто-то еще сталкивался с этим, я бы использовал ссылочный номер с поддержкой Sitecore и посмотреть, смогут ли они предоставить патч. Вам также придется обновить провайдера, используемого в ваших конфигурационных файлах Solr.Index и Solr.Indexes.Analytics.
Я попробовал следующий код, и он действительно выдал нужный вам выходной запрос. Хитрость заключалась в том, чтобы при создании запроса Path Path использовать PredicateBuilder.True(). Не уверен, является ли это нормальным поведением из Content Search 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);
Если 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))
Если вы пытаетесь исключить определенные шаблоны, вы можете исключить их из своего индекса в первую очередь, обновив ExcludeTemplate
настройки в Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config. Вам не нужно беспокоиться о том, чтобы исключить его из запроса:
<exclude hint="list:ExcludeTemplate">
<MyTemplateId>{11111111-1111-1111-1111-111111111111}</MyTemplateId>
<MyTemplateId>{22222222-2222-2222-2222-222222222222}</MyTemplateId>
</exclude>