Как бы я смоделировал данные, которые являются иерархическими и реляционными в системе документов-ориентированной базы данных, такой как RavenDB? - PullRequest
12 голосов
/ 09 июня 2011

Документно-ориентированные базы данных (особенно RavenDB) действительно меня заинтриговали, и я хочу немного поиграть с ними.Однако, как человек, который очень привык к реляционному отображению, я пытался придумать, как правильно моделировать данные в базе данных документов.

Скажем, у меня есть CRM со следующими объектами в моем приложении C # (оставляя без необходимостисвойства):

public class Company
{
    public int Id { get; set; }
    public IList<Contact> Contacts { get; set; }
    public IList<Task> Tasks { get; set; }
}

public class Contact
{
    public int Id { get; set; }
    public Company Company { get; set; }
    public IList<Task> Tasks { get; set; }
}

public class Task
{
    public int Id { get; set; }
    public Company Company { get; set; }
    public Contact Contact { get; set; }
}

Я думал о том, чтобы поместить все это в документ Company, поскольку контакты и задачи не имеют цели вне компании, и большую часть времени выполняют запрос для задачи иликонтакты также покажут информацию о ассоциированной компании.

Проблема связана с Task сущностями.Скажем, бизнес требует, чтобы задача ВСЕГДА ассоциировалась с компанией, но при необходимости также была связана с задачей.

В реляционной модели это легко, поскольку у вас просто есть таблица Tasks и Company.Tasksотносятся ко всем задачам для компании, в то время как Contact.Tasks показывает только задачи для конкретной задачи.

Для моделирования этого в базе данных документов я подумал о следующих трех идеях:

  1. Задачи модели в виде отдельного документа.Это кажется своего рода антидокументарной базой данных, так как большую часть времени, когда вы смотрите на компанию или контакт, вы хотите видеть список задач, поэтому приходится много выполнять объединения документов.

  2. Сохраняйте задачи, не связанные с контактом, в списке Company.Tasks и помещайте задачи, связанные с контактом, в список для каждого отдельного контакта.К сожалению, это означает, что если вы хотите увидеть все задачи для компании (которых, вероятно, будет много), вам нужно объединить все задачи для компании со всеми задачами для каждого отдельного контакта.Я также вижу, что это сложно, когда вы хотите отсоединить задачу от контакта, так как вам нужно переместить ее из контакта в компанию

  3. Сохранить все задачи в списке Company.Tasksи у каждого контакта есть список значений идентификаторов для задач, с которыми он связан.Это кажется хорошим подходом, за исключением необходимости вручную принимать значения идентификатора и создания подсписка Task сущностей для контакта.

Какой рекомендуемый способ моделированияэти данные в документно-ориентированной базе данных?

Ответы [ 2 ]

10 голосов
/ 09 июня 2011

Используйте денормализованные ссылки:

http://ravendb.net/faq/denormalized-references

по сути, у вас есть класс DenormalizedReference:

public class DenormalizedReference<T> where T : INamedDocument
{
    public string Id { get; set; }
    public string Name { get; set; }

    public static implicit operator DenormalizedReference<T> (T doc)
    {
        return new DenormalizedReference<T>
        {
            Id = doc.Id,
            Name = doc.Name
        }
    }
}

ваши документы выглядят так - я реализовал INamedDocumentинтерфейс - это может быть то, что вам нужно, хотя:

public class Company : INamedDocument
{
    public string Name{get;set;}
    public int Id { get; set; }
    public IList<DenormalizedReference<Contact>> Contacts { get; set; }
    public IList<DenormalizedReference<Task>> Tasks { get; set; }
}

public class Contact : INamedDocument
{
    public string Name{get;set;}
    public int Id { get; set; }
    public DenormalizedReference<Company> Company { get; set; }
    public IList<DenormalizedReference<Task>> Tasks { get; set; }
}

public class Task : INamedDocument
{
    public string Name{get;set;}
    public int Id { get; set; }
    public DenormalizedReference<Company> Company { get; set; }
    public DenormalizedReference<Contact> Contact { get; set; }
}

Теперь сохранение Задачи работает точно так же, как и раньше:

var task = new Task{
    Company = myCompany,
    Contact = myContact
};

Однако, потянув все это назад, вы будетеВы только собираетесь получить денормализованную ссылку на дочерние объекты.Для их гидратации я использую индекс:

public class Tasks_Hydrated : AbstractIndexCreationTask<Task>
{
    public Tasks_Hydrated()
    {
        Map = docs => from doc in docs
                      select new
                                 {
                                     doc.Name
                                 };

        TransformResults = (db, docs) => from doc in docs
                                         let Company = db.Load<Company>(doc.Company.Id)
                                         let Contact = db.Load<Contact>(doc.Contact.Id)
                                         select new
                                                    {
                                                        Contact,
                                                        Company,
                                                        doc.Id,
                                                        doc.Name
                                                    };
    }
}

И используя ваш индекс для получения гидратированных задач:

var tasks = from c in _session.Query<Projections.Task, Tasks_Hydrated>()
                    where c.Name == "taskmaster"
                    select c;

Который я считаю довольно чистым:)

В качестве конструктивного разговора - общее правило состоит в том, что если вам когда-либо необходимо загрузить дочерние документы в одиночку как в - а не как часть родительского документа.Будь то для редактирования или просмотра - вы должны смоделировать его с его собственным идентификатором, как его собственный документ.Использование описанного выше метода делает это довольно простым.

1 голос
/ 09 июня 2011

Я тоже новичок в документировании dbs ... так что с долей соли ...

В качестве контрастного примера ... если вы находитесь в Твиттере и у вас есть список подписчиков, который содержит список их твитов ... вы бы не переместили их в свою учетную запись Twitter, чтобы читать их, и если вы будете чирикать в Твиттере, у вас будет только копия, а не оригинал.

Таким образом, мое мнение так же: если Задачи принадлежат компании, они остаются внутри Компании. Компания - Совокупный Корень Задач. Контакты могут содержать только ссылки (идентификаторы) или копии Задач и не могут изменять их напрямую. Если у вашего контакта есть «копия» задания, это нормально, но для того, чтобы изменить задание (например, пометить его как завершенное), вы должны изменить задание через его Совокупный корень (компания). Поскольку копия может быстро устареть, создается впечатление, что вы хотите, чтобы копия существовала только в памяти и при сохранении Контакта, вы сохраняли бы только ссылки на Задачи.

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