Индекс RavenDb для фильтрации и сортировки по свойствам вложенной структуры / коллекции (индекс разветвления)
Я ищу способ создания статического индекса для обслуживания запросов фильтрации / сортировки для комбинации значений свойств во вложенной структуре (коллекции объектов) вместе со структурой-контейнером. Кажется, это не тривиально по следующим причинам:
- Если свойства вложенной структуры / коллекции разделены на отдельные поля индекса (отдельные коллекции), то это делает невозможным использование
AND
условие при фильтрации по 2+ свойствам вложенной структуры / коллекции. - Сложность индекса разветвления (см. Пример), которая делает любое решение слишком медленным.
Учитывая следующую постоянную модель:
public class Document
{
public string Title { get; set; }
public List<UserChange> RecentModifications { get; set; }
}
где
public class UserChange
{
public string UserId { get; set; }
public DateTime Timestamp { get; set; }
}
Вопрос: Как построить индекс для Document
фильтровать / сортировать по комбинации всех полей: Title
, UserId
а также Timestamp
?
Возможные варианты использования:
- получить все документы, содержащие слово "контракт" для определенного пользователя и диапазон дат
- сортировать документы, содержащие слово "контракт", по последним изменениям, внесенным пользователем.
PS Я понимаю, что ограничения индексации можно обойти, реструктурировав модель персистентности - сохраняя структуру для недавно измененных документов в User
документ, но это наложило бы некоторые другие ограничения, которых я хотел бы избежать.
2 ответа
Проблема может быть решена с помощью индексов с динамическими полями. Это позволяет сохранить логическую структуру данных и избежать создания индекса разветвления.
Решение
Создайте следующий индекс для Document
Коллекция из вышеперечисленного:
public class MyIndex : AbstractIndexCreationTask<Document, DocumentIndDto>
{
public MyIndex()
{
// Add fields that are used for filtering and sorting
Map = docs =>
from e in docs
select new
{
Title = e.Title,
_ = e.RecentModifications.Select( x => CreateField ($"{nameof(Document.RecentModifications)}_{x.UserId}", x.Timestamp))
};
}
}
public class DocumentIndDto
{
public string Title { get; set; }
public Dictionary<string,DateTime> RecentModifications { get; set; }
}
Запрос на MyIndex
лайк
var q = s.Query<DocumentIndDto, MyIndex>()
.Where(p => p.Title == "Super" && p. RecentModifications["User1"] < DateTime.Now);
объяснение
Указанный индекс с динамическими полями будет генерировать дополнительные поля и термины для каждой записи в следующем формате:
RecentModifications_User1 = '2018-07-01';
RecentModifications_User2 = '2018-07-02';
Формат важен, потому что когда вы используете словарь в запросе высокого уровня, как myDic[key]
трансформируется в myDic_key
в сгенерированном RQL. Следовательно, это позволит нам использовать эти поля в запросах.
Если вы запросите с помощью обычного Query
скорее, чем DocumentQuery
(см. документы), тогда вам необходим правильный тип данных для работы LINQ. Для этого я создал DocumentIndDto
класс, где мой RecentModifications
стал словарем, так что я мог бы использовать его в запросе высокого уровня и получить правильный RQL, как
from index 'MyIndex' where Title = $p0 and RecentModifications_User1 = $p1
Для более подробной информации, смотрите мою дискуссию на эту тему с Ореном Эйни (он же Ayende Rahien).
Используйте следующий RQL в своем определении индекса:
from doc in docs.Documents
from modification in doc.RecentModifications
select new {
modification.UserId,
modification.Timestamp
}
Примечание: 'UserId' и 'timestamp' НЕ разделяются в базовой записи индекса.
Поэтому фильтрация по комбинации UserId='A' AND Timestamp='2018-01-01' вернет записи, измененные пользователем 'A' на '2018-01-01'.
Смотрите также индексы разветвления
Примечание 2: "Заголовок" также можно индексировать и искать при помощи:
from doc in docs.Documents
from modification in doc.RecentModifications
select new {
doc.Title,
modification.UserId,
modification.Timestamp
}
Таким образом, каждая результирующая "запись индекса" будет содержать "UserId" и "Timestamp", как и прежде, и соответствующий "Title"