我如何在面向文档的数据库系统(如RavenDB)中建立层次和关系数据?

Kal*_*exx 12 data-modeling document-based-database document-database ravendb

面向文档的数据库(特别是RavenDB)真的很吸引我,而且我想和他们玩一下.然而,作为一个非常习惯于关系映射的人,我试图想到如何在文档数据库中正确建模数据.

假设我的C#应用​​程序中有以下实体的CRM(省略了不需要的属性):

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; }
}
Run Code Online (Sandbox Code Playgroud)

我想把这一切都放在一个Company文档中,因为联系人和任务没有公司的目的,大多数时候查询任务或联系人也会显示关联公司的信息.

问题来自Task实体.假设业务要求任务始终与公司相关联,但也可选择与任务相关联.

在关系模型中,这很简单,因为您只有一个Tasks表并且Company.Tasks与公司的所有任务相关,而Contact.Tasks只显示特定任务的任务.

为了在文档数据库中对此进行建模,我想到了以下三个想法:

  1. 将任务建模为单独的文档.这似乎是一种反文档数据库,因为大多数情况下,当您查看公司或联系人时,您将希望查看任务列表,因此必须对文档执行大量连接.

  2. 保留与Company.Tasks列表中的联系人无关的任务,并将与联系人关联的任务列在每个联系人的列表中.遗憾的是,如果您想查看公司的所有任务(可能会很多),您必须将公司的所有任务与每个联系人的所有任务相结合.当你想要将任务与联系人解除关联时,我也看到这很复杂,因为你必须将它从联系人移到公司

  3. 将所有任务保留在Company.Tasks列表中,每个联系人都有一个与其关联的任务的id值列表.这似乎是一种很好的方法,除了必须手动获取id值并且必须Task为联系人制作实体的子列表.

在面向文档的数据库中建模此数据的推荐方法是什么?

小智 10

使用非规范化引用:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你的文档看起来像 - 我已经实现了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; }
}
Run Code Online (Sandbox Code Playgroud)

现在保存任务的工作方式与之前完全相同:

var task = new Task{
    Company = myCompany,
    Contact = myContact
};
Run Code Online (Sandbox Code Playgroud)

然而,将所有这些拉回来将意味着您只会获得子对象的非规范化引用.为了保湿这些,我使用一个索引:

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
                                                    };
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用您的索引来检索水合任务是:

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

我认为这很干净:)

作为一个设计的谈话-通常的规则是,如果你以往任何时候都需要加载的子文档单独作为-不父文档的一部分.无论是编辑还是查看 - 您都应该使用它自己的Id来建模,因为它是自己的文档.使用上述方法使这非常简单.