说我在我的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; } }
我正在考虑将这一切都放在公司文件中,因为联系人和任务没有公司的目的,大多数时候查询任务或联系人也将显示有关联系公司的信息.
问题来自Task实体.说业务要求任务总是与公司相关联,但也可以与任务相关联.
在关系模型中,这很简单,因为您只需要一个“任务”表,并将Company.Task与公司的所有任务相关联,而Contact.Tasks仅显示特定任务的任务.
为了在文档数据库中进行建模,我想到了以下三点:
>模型任务作为单独的文档.这似乎是一种反文档数据库,因为大多数时候你看一个公司或联系人,你会想看到任务列表,因此必须执行很多文档的连接.
>保留与公司联系人不相关的任务.打开列表,并将每个联系人的列表中的联系人的任务放在列表中.不幸的是,如果您想查看公司的所有任务(这可能会很多),则必须将公司的所有任务与每个联系人的所有任务相结合.当您要将联系人的任务与联系人分离时,我也看到这一点很复杂,因为您必须将其从联系人转移到公司
>将所有任务保留在Company.Tasks列表中,并且每个联系人都有与其关联的任务的id值列表.这似乎是一个很好的方法,除了必须手动获取id值,并且必须为联系人制作任务实体的子列表.
解决方法
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;
我觉得很干净:)
作为设计对话 – 一般规则是,如果您需要单独加载子文档,而不是父文档的一部分.无论是编辑还是查看 – 您应该使用自己的Id作为自己的文档进行建模.使用上述方法使这很简单.