Индекс 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"

Другие вопросы по тегам