为啥在猫鼬中声明一对多关联时有两个参考?

Posted

技术标签:

【中文标题】为啥在猫鼬中声明一对多关联时有两个参考?【英文标题】:Why there are two refs in declaring one-to-many association in mongoose?为什么在猫鼬中声明一对多关联时有两个参考? 【发布时间】:2018-04-17 11:42:31 【问题描述】:

我是 mongodb 的新手,看这个one-to-many example

据我了解

这个例子说一个人可以写很多故事或一个故事belongs_to一个人,我认为将person._id存储在故事集合中就足够了

为什么 person 集合有 field stories

获取数据的案例

案例1

获取 id 为 x 的人的所有故事

解决方案:为此,只需在故事集合whereauthor = x 中触发查询

案例2

获取特定故事的author name

解决方案:为此,我们有author 现场故事集

【问题讨论】:

看看这个你会从那里得到一些想法Model One-to-Many Relationships with Embedded Documents 【参考方案1】:

TL;DR

简单地说:因为 MongoDB 中没有显式关系这样的概念。

Mongoose 不知道你想如何解决关系。搜索是否来自给定的故事对象并且作者要找到?还是搜索会查找作者对象的所有故事?所以它确保它无论如何都可以解决关系。

请注意,这种方法存在问题,而且是一个问题。假设我们不是像本例中那样谈论一对多关系,而是“One-To-A-Shitload”™ 关系。由于BSON documents have a size limit of 16MB,您可以通过这种方式管理的关系受到限制。不少,但会有人为的限制。

如何解决这个问题:不要使用 ODM,而是自己进行适当的建模。因为你知道你的用例。下面我给大家举个例子。

详细

让我们先详细说明一下你的案例:

    对于给定用户(又名“我们已经拥有该用户文档的所有数据”),他或她的故事是什么? 在概览页面上列出所有故事以及用户名。 对于选定的(“给定”)故事,作者的详细信息是什么? 仅出于演示目的:给定用户想要更改显示故事的名称,无论是他的用户名还是自然名(发生了!)甚至是化名。李>

好的,现在让我们先把 mongoose 放在一边,让我们考虑如何自己实现它。请记住

MongoDB 中的数据建模是从来自用例的问题中推导出模型,以便以最有效的方式涵盖最常见的用例。

与 RDBMS 建模相反,您可以在其中识别实体、它们的属性和关系,然后跳过一些环节以以某种方式回答您的问题。

所以,看看我们的用户故事,我想我们可以同意 2 是最常见的用例,其次是 3 和 1,与其他用例相比,4 相当罕见。

现在我们可以开始了

建模

我们首先对最常见用例中涉及的数据进行建模。

因此,我们想让故事查询成为最有效的查询。我们想按提交的降序对故事进行排序。很简单:


  _id: new ObjectId(),
  user: "Name to Display",
  story: "long story cut short",

现在假设你想展示你的故事,其中 10 个:

db.stories.find().sort(_id:-1).limit(10)

没有关系,我们需要的所有数据,单个查询,使用default index on _id 进行排序。由于timestamp is part of the ObjectId 是最重要的部分,我们可以使用它按时间对故事进行排序。问题“嘿,但是如果一个人更改了他或她的用户名怎么办?”通常现在就来。简单:

db.stories.update("user":oldname,$set:"user":newname,multi:true)

由于这是一个罕见的用例,它只需要可行并且不必非常高效。但是,稍后我们将看到无论如何我们都必须在user 上放置一个索引。

谈论作者:这真的取决于你想怎么做。但我将向您展示我是如何对类似的东西进行建模的:


   _id: "username",
   info1: "foo",
   info2: "bar",
   active: true,
   ...

我们在这里使用_id 的一些属性:它是一个具有唯一约束的必需索引。正是我们想要的用户名。

但是,它带有一个警告:_id is immutable。因此,如果有人想更改他或她的用户名,我们需要将原始用户复制到具有新用户名_id 的文档中,并相应地更改我们故事中的用户属性。这样做的好处是,即使更改用户名的更新(见上文)在其运行时失败,每个故事仍然可以与用户相关。如果更新成功,我倾向于注销用户并让他再次使用新用户名登录。

如果您想区分用户名和显示名称,这一切都变得更加容易:


  _id: "username",
  displayNames: ["Foo B. Baz","P.S. Eudonym"],
  ...

当然,您可以在故事中使用显示名称。

现在让我们看看如何获​​取给定故事的用户详细信息。我们知道作者的名字,所以很简单:

db.authors.find("_id":authorNameOfStory)

db.authors.find("displayNames": authorNameOfStory)

查找给定用户的所有故事也很简单。它是:

db.stories.find("name":idFieldOfUser)

db.stories.find("name":$in:displayNamesOfUser)

现在我们已经涵盖了您的所有用例,现在我们可以通过以下方式提高它们的效率

索引

故事模型user字段上有一个明显的索引,所以我们这样做:

db.stories.ensureIndex("name":1)

如果您只擅长使用“用户名作为_id”的方式,那么您就完成了索引。使用显示名称,您显然需要为它们编制索引。由于您很可能希望显示名称和假名是唯一的,所以它有点复杂:

db.authors.ensureIndex("displayNames":1,sparse:true, unique:true)

注意:我们需要将其设为sparse index,以防止在有人尚未决定显示名称或化名时出现不必要的错误。确保仅在用户决定显示名称时将此字段明确添加到作者文档。否则,它将评估为null server side ,这一个有效值,您收到约束冲突错误,即“E1100 重复键”。

结论

我们已经涵盖了应用程序处理的关系的所有用例,从而大大简化了我们的数据模型,并为我们最常见的用例提供了最有效的查询。每个用例都包含一个查询,考虑到我们在执行查询时已经拥有的信息。

请注意,由于我们使用隐式关系来发挥自己的优势,因此对用户可以发布的故事数量没有人为限制。

对于更复杂的查询(“每个用户每月提交多少故事?”),请使用aggregation framework。这就是它的用途。

【讨论】:

很好的解释!!

以上是关于为啥在猫鼬中声明一对多关联时有两个参考?的主要内容,如果未能解决你的问题,请参考以下文章

在猫鼬中填充+聚合[重复]

如何在猫鼬中找到随机记录[重复]

关于如何在猫鼬中编写评论模式的任何想法?

关于如何在猫鼬中编写评论模式的任何想法?

当我需要用和子句分隔查询时,在猫鼬中有两个“或”子句[重复]

在猫鼬中填充数据无法正常工作?