Это продолжение проекта, изложенного в этом вопросе.
У меня есть следующая модель:
class Product {
public string Id { get; set; }
public string[] Specs { get; set; }
public int CategoryId { get; set; }
}
В массиве "Specs" хранится спецификация продуктапары имя-значение, соединенные специальным символом.Например, если продукт окрашен в синий цвет, строка спецификации будет "Color ~ Blue".Такое представление спецификаций позволяет запрашивать продукты, имеющие несколько значений спецификаций, указанных в запросе.Есть два основных запроса, которые я хотел бы поддержать:
- Получить все продукты в данной категории.
- Получить все продукты в данной категории, которые имеют набор указанных спецификаций.
Это хорошо работает с RavenDB.Однако в дополнение к продуктам, удовлетворяющим заданному запросу, я хотел бы вернуть набор результатов, который содержит все пары имя-значение спецификации для набора продуктов, указанных в запросе.Пары имя-значение спецификации должны быть сгруппированы по имени и значению спецификации и содержать количество продуктов, которые имеют данную пару имя-значение спецификации.Для запроса № 1 я создал следующую карту, уменьшающую индекс:
class CategorySpecGroups {
public int CategoryId { get; set; }
public string Spec { get; set; }
public int Count { get; set; }
}
public class SpecGroups_ByCategoryId : AbstractIndexCreationTask<Product, CategorySpecGroups>
{
public SpecGroups_ByCategoryId()
{
this.Map = products => from product in products
where product.Specs != null
from spec in product.Specs
select new
{
CategoryId = product.CategoryId,
Spec = spec,
Count = 1
};
this.Reduce = results => from result in results
group result by new { result.CategoryId, result.Spec } into g
select new
{
CategoryId = g.Key.CategoryId,
Spec = g.Key.Spec,
Count = g.Sum(x => x.Count)
};
}
}
Затем я могу запросить этот индекс и получить все пары имя-значение спецификации в данной категории.Проблема, с которой я сталкиваюсь, состоит в том, чтобы получить тот же набор результатов, но для запроса, который фильтрует как по категории, так и по набору пар имя-значение спецификации.При использовании SQL этот набор результатов будет получен путем создания группы по набору продуктов, отфильтрованных по категориям и спецификациям.Как правило, этот тип запроса является дорогостоящим, но при фильтрации по категориям и спецификациям наборы продуктов обычно невелики, хотя и не настолько малы, чтобы помещаться на одной странице - они могут содержать до 1000 продуктов.Для справки, MongoDB поддерживает метод group , который можно использовать для достижения того же набора результатов.При этом выполняется серверная группа ad hoc, и производительность приемлема.
Как получить этот тип результатов с помощью RavenDB?
Одним из возможных решений является получение всех продуктов для запроса.и выполнить группировку в памяти, и другой вариант - создать индекс mapreduce, как указано выше, хотя проблема с этим будет заключаться в том, чтобы вывести все возможные выборки спецификаций, которые могут быть сделаны для данной категории, и дополнительно, этот тип индекса может взорваться в размере.
Например, взгляните на эту страницу категории крепежа .Пользователь может отфильтровать свой выбор, выбрав атрибуты.Когда атрибут выбран, он сужает выбор продуктов и отображает атрибуты в новом наборе продуктов.Этот тип взаимодействия обычно называется фасетный поиск .
РЕДАКТИРОВАТЬ
Тем временем я буду пытаться найти решение с использованием Solr, поскольку они поддерживают фасетный поиск из коробки.
РЕДАКТИРОВАТЬ 2
Похоже, что RavenDB также поддерживает фасетный поиск (который изКонечно, имеет смысл, индексы хранятся в Lucene так же, как Solr).Я буду изучать это и публиковать обновления.
РЕДАКТИРОВАТЬ 3
Функциональность граненого поиска RavenDB работает должным образом.Я сохраняю документ настройки фасетов для каждого идентификатора категории, который используется для вычисления фасетов для запроса в данной категории.Проблема, с которой я сталкиваюсь сейчас - это производительность.Для набора из 500 тыс. Продуктов с 4500 различными категориями, в результате чего получается 4500 документов настройки фасетов, запрос по идентификатору категории занимает около 16 секунд при запросе фасетов и около 0,05 секунды при отсутствии запроса фасетов.Конкретная протестированная категория содержит около 6 000 продуктов, 23 различных фасета и 2 000 различных комбинаций имен и диапазонов фасетов.После просмотра кода в FacetedQueryRunner выясняется, что запрос фасетов приведет к запросу Lucene для каждой комбинации имени и значения фасета для получения счетчиков, а также к запросу для каждого имени фасета, чтобы получить условия,Одна проблема с реализацией состоит в том, что она будет извлекать все отдельные термины для данного имени фасета независимо от запроса, что в большинстве случаев значительно сократит количество терминов для фасета и, следовательно, уменьшит количество запросов Lucene.Одним из способов повышения производительности в этом случае было бы сохранение вычисленного набора результатов MapReduce (как показано выше) для каждого документа настройки фасета, который затем можно было бы запросить, чтобы получить все отдельные термины при дальнейшей фильтрации по фасетам.Однако общая производительность может быть слишком медленной.