Отношение многие ко многим RavenDb: структура и индекс документа - PullRequest
0 голосов
/ 09 июня 2018

Как построить модель и индекс NoSQL (предпочтительно для RavenDb v4) для следующей реляционной схемы?

Тип документа Contact, где каждая запись может иметь несколько дополнительных свойств (типсвойство определено в CustomField, а значение в ContactCustomField) enter image description here

Учитывая необходимость фильтровать / сортировать выделенные поля в одном запросе (всеполя из Контакта плюс пользовательские поля).


Возможные варианты, как я вижу:

Опция # 1

Естественно, я быпредставьте себе следующие постоянные модели:

public class Contact
{
    public string Id      { get; set; }
    public string Name    { get; set; }
    public string Address { get; set; }
    public string Phone   { get; set; }
    // Where the key is CustomField.Id and the value is ContactCustomField.Value
    public Dictionary<string, string> CustomValues { get; set; }
}

public class CustomField
{
    public string Id          { get; set; }
    public string Code        { get; set; }
    public string DataType    { get; set; }
    public string Description { get; set; }
}

Однако создание индекса для запроса, как показано ниже (извините за смешанный синтаксис), озадачивает меня:

SELECT Name, Address, Phone, CustomValues
FROM Contact
WHERE Name LIKE '*John*' AND CustomValues.Any(v => v.Key == "11" && v.Value == "student")

Вариант № 2

Другим подходом будет сохранение нормализованной структуры (как показано на рисунке выше).Тогда это сработало бы - мне просто нужно включить ContactCustomField в запрос для Contact.

Недостатком было бы не использование преимуществ NoSQL.

Ответы [ 2 ]

0 голосов
/ 11 июня 2018

Для этого вы, вероятно, захотите использовать индексы с уменьшенной картой.

Карта:

docs.Contacts.SelectMany(doc => (doc, next) => new{
// Contact Fields
doc.Id,
doc.Name,
doc.Address,
doc.Phone,
doc.CustomFieldLoaded = LoadDocument<string>(doc.CustomValueField, "CustomFieldLoaded"),
doc.CustomValues
});

Уменьшение:

from result in results
group result by {result.Id, result.Name, result.Address, result.Phone, result.CustomValues, result.CustomFieldLoaded} into g
select new{
g.Key.Id,
g.Key.Name,
g.Key.Address,
g.Key.Phone,
g.Key.CustomFieldLoaded = new {},
g.Key.CustomValues = g.CustomValues.Select(c=> g.Key.CustomFieldLoaded[g.Key.CustomValues.IndexOf(c)])
}

Ваш документ будет выглядеть примерно так:

{
"Name": "John Doe",
"Address": "1234 Elm St",
"Phone": "000-000-0000",
CustomValues: "{COLLECTION}/{DOCUMENTID}"
}

Это загрузит контакт, а затем загрузит данные реляционных документов.

Я не тестировал этот точный пример, но он основан на рабочем примере, который я реализовал.в моем собственном проекте.Возможно, вам придется внести некоторые изменения.

Конечно, вам потребуется настроить его, чтобы включить много документов, но он должен дать вам базовое представление о том, как использовать отношения.

Вам также следует оформить заказдокументация для отношений документа .

Надеюсь, это поможет.

0 голосов
/ 09 июня 2018

Обновленный ответ (29 июня 2018 г.)

Ключом к успеху является одно недооцененное свойство Равена - Индексы с динамическими полями .Это позволяет сохранить логическую структуру данных и избежать создания индекса разветвления .

. Способ использования состоит в том, чтобы создавать коллекции, как описано выше в варианте № 1:

public class Contact
{
    public string Id      { get; set; }
    public string Name    { get; set; }
    public string Address { get; set; }
    public string Phone   { get; set; }
    public Dictionary<string, object> CustomFields { get; set; }
}

public class CustomField
{
    public string Id          { get; set; }
    public string Code        { get; set; }
    public string DataType    { get; set; }
    public string Description { get; set; }
}

где Contact.CustomFields.Key является ссылкой на CustonField.Id, а Contact.CustomFields.Value хранит значение для этого настраиваемого поля.

Чтобы отфильтровать / выполнить поиск по настраиваемым полям, нам нужен следующий индекс:

public class MyIndex : AbstractIndexCreationTask<Contact>
{
    public MyIndex()
    {
        Map = contacts =>
            from e in contacts
            select new
            {
                _ = e.CustomFields.Select( x => CreateField ($"{nameof(Contact.CustomFields)}_{x.Key}", x.Value))
            };
    }
} 

Этот индекс будет охватывать все пары ключ-значение словаря, поскольку они были обычными свойствами Contact.

Получено

Существуетбольшая проблема, если вы пишете запросы в C # с использованием обычного объекта Query (тип IRavenQueryable), а не RQL или DocumentQuery.Это так, как мы назвали динамические поля - это составное имя в определенном формате: dictionary_name + underscore + key_name.Это позволяет нам создавать запросы типа

var q = s.Query<Person, MyIndex>()
                .Where(p => p.CustomFields["Age"].Equals(4));

, которые изнутри конвертируются в RQL:

from index 'MyIndex' where CustomFields_Age = $p1

Это недокументировано, а здесь - это мое обсуждение с Ореном Эйни(aka Ayende Rahien), где вы можете узнать больше по этому вопросу.

PS Моя общая рекомендация - взаимодействовать с Вороном по DocumentQuery, а не по обычному Query ( ссылка ), поскольку интеграция LINQ все еще довольно слабая, и разработчики могут продолжать сталкиваться с ошибками тут и там.


Первоначальный ответ (9 июня 2018 г.)

Как было предложено Ореном Эйни (он же Ayende Rahien), путь - вариант № 2 - включая отдельную коллекцию ContactCustomField в запросах.

Таким образом, несмотря на использование базы данных NoSQLреляционный подход - единственный путь сюда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...