为啥在猫鼬中声明一对多关联时有两个参考?
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
的人的所有故事
解决方案:为此,只需在故事集合where
author = 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。这就是它的用途。
【讨论】:
很好的解释!!以上是关于为啥在猫鼬中声明一对多关联时有两个参考?的主要内容,如果未能解决你的问题,请参考以下文章