C# 如何处理文档数据库(如Mongo)中单独存储的对象的引用?

C# 如何处理文档数据库(如Mongo)中单独存储的对象的引用?,c#,mongodb,C#,Mongodb,这个问题在诸如Entity Framework或NHibernate之类的ORM中很容易解决,但在MongoDb的c#driver中我看不到任何现成的解决方案。假设我有一个类型为A的对象集合,引用类型为B的对象,我需要将其存储在单独的集合中,这样一旦特定对象B发生更改,所有引用它的对象A都需要知道更改。换句话说,我需要规范化这个对象关系。同时,我需要B在类中由A引用,不是由Id引用,而是由类型引用引用,如下所示: public class A { public B RefB { get;

这个问题在诸如Entity Framework或NHibernate之类的ORM中很容易解决,但在MongoDb的c#driver中我看不到任何现成的解决方案。假设我有一个类型为A的对象集合,引用类型为B的对象,我需要将其存储在单独的集合中,这样一旦特定对象B发生更改,所有引用它的对象A都需要知道更改。换句话说,我需要规范化这个对象关系。同时,我需要B在类中由A引用,不是由Id引用,而是由类型引用引用,如下所示:

public class A
{
   public B RefB { get; set; }
}
我必须自己处理所有这些引用一致性吗?如果是,哪种方法最好?我是否必须在类中同时保留B的Id和B引用,并以某种方式像这样同步它们的值:

public class A
{
    // Need to implement reference consistency as well
    public int RefBId { get; set; }

    private B _refB;
    [BsonIgnore]
    public B RefB
    {
        get { return _refB; }
        set { _refB = value; RefBId = _refB.Id }
    }
}

我知道有人可能会说关系数据库最适合这种情况,我知道,但我真的必须使用像MongoDb这样的文档数据库,它解决了许多问题,在大多数情况下,我需要为我的项目存储非规范化的对象,但有时我们可能需要在单个存储中进行混合设计。

在文档数据库中,当您执行类似于第一个示例的操作时:

公共A类
{
公共B RefB{get;set;}
}
您正在将
B
的值完全嵌入到RefB属性中。换句话说,您的文档如下所示:

public class DBObject 
{
    public ObjectId Id {get;set;}
}

public class Department : DBObject 
{
  // ...
}

public class EmployeeDB : DBObject
{
    public ObjectId DepartmentId {get;set;}
}
[a/1]
{
AProp:“foo”,
参考文献B:{
BProp:“酒吧”
}
}
它有助于从领域驱动设计(DDD)的角度来看问题。这种嵌入模式通常发生在
B
是“值对象”或“非聚合实体”(使用DDD术语)时

如果您正在存储某个其他聚合实体的时间点快照,也可能发生这种情况。在这种情况下,如果
B
的值发生变化,您不希望更新它们,否则它将不再代表该时间点

另一种模式是将
A
B
视为单独的聚合。如果其中一个需要引用另一个,则只能通过引用其ID来指定

公共A类
{
公共字符串BId{get;set;}
}
然后将存储您的文档,例如:

[a/1]
{
AProp:“foo”,
投标:“b/2”
}
[b/2]
{
BProp:“酒吧”,
}
注意:我相信在MongoDB中,您将使用
ObjectId
类型。在RavenDB中,您通常会使用
字符串
,但是
int
可以稍加调整。其他文档数据库可能允许其他类型

在文档数据库中不起作用的部分是,您在第二个示例
A
中显示了如何保留对
B
的引用,而不将其保留为文档的一部分。这种模式可以在诸如Entity Framework或NHibernate之类的ORM中工作,但它倾向于通过虚拟属性和代理类来实现。这些在文档数据库环境中不起作用

因此,如果它们是单独的文档,而不是加载
A
并使用
A.RefB
访问
B
,您只需分别加载
A
B
。例如,您可以加载
A
,并使用
BId
确定如何加载
B


当然,问题还是在于是嵌入还是链接。这是你必须弄清楚的事情,因为这两种方法都可以做到。对于特定的领域关注点,通常一种方法比另一种更好。但你通常不会同时做这两件事。

这主要是一个建筑方面的问题,可能有点取决于个人品味。我将试着分析利弊(实际上只有缺点,这是相当自以为是的):

在数据库级别上,MongoDB没有提供任何工具来强制引用完整性,所以是的,您必须自己执行。我建议您使用如下所示的数据库对象:

public class DBObject 
{
    public ObjectId Id {get;set;}
}

public class Department : DBObject 
{
  // ...
}

public class EmployeeDB : DBObject
{
    public ObjectId DepartmentId {get;set;}
}
我建议无论发生什么情况,都在数据库级别使用这样的普通DTO。如果你想要额外的糖,把它放在一个单独的层,即使这意味着一点复制。DB对象中的逻辑需要非常好地理解驱动程序对对象进行水合物化的方式,并且可能需要依赖于实现细节

现在,您是否希望使用更“智能”的对象是一个偏好问题。事实上,许多人喜欢使用强类型的自动激活访问器,例如

public class Employee
{
    public Department 
    { get { return /* the department object, magically, from the DB */ } }
}
这种模式带来了许多挑战:

  • 它要求
    Employee
    类(一个模型类)能够从数据库中生成对象。这很棘手,因为它需要注入数据库,或者需要一个静态对象来访问数据库,这也很棘手
  • 访问
    部门
    看起来非常便宜,但实际上,它会触发数据库操作,速度可能很慢,可能会失败。这对来电者是完全隐藏的
  • 在1:n的关系中,事情变得复杂得多。例如,
    部门
    是否也会公开员工名单?如果是这样的话,这真的是一个列表(即,一旦开始阅读第一个,所有员工都必须反序列化?)还是一个懒惰的
    mongocursors或
  • 更糟糕的是,通常不清楚应该使用哪种缓存。假设您获得了myDepartment.Employee[0].Department.Name。显然,这段代码并不聪明,但是想象一下有一个调用堆栈,其中包含一些专门的方法。他们可能会像那样调用代码,即使它更隐蔽。现在,一个简单的实现实际上会再次反序列化ref'd
    部门
    。那太难看了。另一方面,主动缓存是危险的,因为您可能实际上想要重新获取对象
  • 最糟糕的是:更新。到目前为止,这些挑战基本上都是只读的。现在让我们假设我调用
    employeeJohn.Department.Name