Mongoose 是 Node.js 中一个非常流行的,基于 MongoDB 数据库的对象模型工具。在使用 Mongoose 进行数据模型设计时,通常需要使用到一些嵌套数据结构来表达复杂的业务逻辑和数据关系,这便是使用 Mongoose 子文档的场景。
子文档的定义
子文档(subdocument)是 Mongoose 中一个内嵌在另一个文档中的文档。在 Mongoose 中,子文档可以跟一个父文档(parent document)一起被存储在 MongoDB 数据库中。子文档在 Mongoose 中通常使用 Schema.Types.Embedded 或者 Schema.Types.Array 进行定义。
使用 Schema.Types.Embedded 定义子文档
使用 Schema.Types.Embedded 可以直接定义一个内嵌在 Schema 中的子文档,例如下面的示例:
-- -------------------- ---- ------- ----- -------- - -------------------- ----- ------ - ---------------- ----- ------------- - --- -------- ------- ------- ----- ------- ------ ------- ---- ------ --- ----- ------------ - --- -------- ----- ------- ---- ------- -------- ------------- --- ----- ------ - ------------------------ --------------
在这个示例中,我们定义了一个内嵌在 Person Schema 中的 addressSchema 子文档。
使用 Schema.Types.Array 定义子文档
如果你需要在一个文档中存放多个子文档,可以使用 Schema.Types.Array 定义一个子文档数组,例如:
-- -------------------- ---- ------- ----- -------- - -------------------- ----- ------ - ---------------- ----- ----------- - --- -------- ----- ------- ------- ------ --- ----- ------------ - --- -------- ----- ------- ---- ------- ------- ------------- --- ----- ------ - ------------------------ --------------
在这个示例中,我们定义了一个内嵌在 Person Schema 中的 phones 数组,其中每个元素都是一个 phoneSchema 子文档。
子文档的使用
Mongoose 支持多种方式的子文档访问和操作。从基础的对象属性访问到高级的聚合操作都可以包含在子文档的使用中。
基础的子文档访问
在访问和设置嵌套的子文档时,我们可以使用点符号来访问:
-- -------------------- ---- ------- ----- ------ - --- -------- ----- ----- ----- ---- --- -------- - ------- ---- -------- ----- ---- ------ ------ ---- ------- ---- -------- - --- ----------------------------------- -- ---- ------- --------------------- - ---- -------- ----------------------------------- -- ---- -------
在这个示例中,我们创建了一个 Person 文档及它的子文档 address,然后用点符号来修改子文档属性。
使用 Mongoose 查询子文档
Mongoose 提供了丰富的查询语法来查询文档及其与子文档的关系。下面是一个查询 Person 文档中某个子文档的示例:
-- -------------------- ---- ------- ----- ----------- - --- -------- ----- ------- ------- ------ --- ----- ------------ - --- -------- ----- ------- ---- ------- ------- ------------- --- ----- ------ - ------------------------ -------------- ---------------- -------------- -------- -- -------- ----- ------- - -------------------- ---
在这个示例中,我们查询了名为 mobile 的 phone,然后打印了包含该 phone 的 Person 文档。
在子文档数组中添加、修改、删除子文档
在将一个子文档添加到包含子文档的文档数组中时,可以使用 push() 函数:
const person = new Person({ name: 'John Doe', age: 30, phones: [] }); person.phones.push({ type: 'home', number: '1234567890' }); console.log(person.phones.length); // 1
在这个示例中,我们向一个空的 phones 数组中添加了一个 phone,然后使用 length 属性来检查它是否存在。
如果你需要修改一个已经存在的子文档,可以使用 indexOf() 函数和 .set() 函数来实现:
-- -------------------- ---- ------- ----- ------ - --- -------- ----- ----- ----- ---- --- ------- -- ----- ------- ------- ------------ -- --- ----- ----------- - -------------------- -- ------ --- ---------- -- ------------- - ----- ----- - ----------------------------------- --------------------------- - ------------- - ------------------------------------- -- ------------
在这个示例中,我们在 phones 数组中查找了名为 mobile 的 phone,如果存在,则使用 indexOf() 函数找到该元素的索引位置,并使用 .set() 函数来修改它的属性。
如果你需要删除一个子文档,可以使用 splice() 函数:
-- -------------------- ---- ------- ----- ------ - --- -------- ----- ----- ----- ---- --- ------- - - ----- ------- ------- ------------ -- - ----- --------- ------- ------------ -- - ----- --------- ------- ------------ -- - --- ----- ----------- - -------------------- -- ------ --- ---------- -- ------------- - ----- ----- - ----------------------------------- --------------------------- --- - ---------------------------------- -- -
在这个示例中,我们在 phones 数组中查找了名为 office 的 phone,如果存在,则使用 indexOf() 函数找到该元素的索引位置,并使用 splice() 函数来删除它。
子文档的优化
在开发应用程序时,你需要根据具体情况来选择合适的优化策略,以提高查询和更新性能。以下是一些优化子文档的技巧:
使用 select() 选择子文档的字段
在查询文档时,为了避免加载较大的子文档,你可以使用 select() 函数来选择子文档的字段:
Person.find().select('name phones.number').exec(function (err, persons) { /* ... */ });
在这个示例中,我们使用 select() 函数选择了 name 和 phones.number 字段进行查询,其他字段不会返回到文档中。
使用 populate() 函数预先加载子文档
如果你需要在查询文档时预先加载子文档,可以使用 populate() 函数:
-- -------------------- ---- ------- ----- ------------ - --- -------- ----- ------- ---- ------- -------- - ----- ---------------------- ---- --------- - --- ----- ------------- - --- -------- ------- ------- ----- ------- ------ ------- ---- ------ --- ----- ------ - ------------------------ -------------- ----- ------- - ------------------------- --------------- ---------------- ----- ----- ---- ------------------------------------ ----- ------- - ----------------------------------- -- ---- ------- ---
在这个示例中,我们在 Person Schema 中定义了一个 address 域,它是一个存储在 Address 模型中的 ObjectId 对象。使用 populate() 函数和 ref 关键字我们可以预先加载 address 子文档,然后访问它的属性。
使用 splice() 函数更新子文档数组
如果你在一个文档中存储了大量的子文档,并且需要频繁地更新其中的某些子文档,你可以使用 splice() 函数来更新数组中的子文档,这比分别调用 update() 函数来更新子文档的性能更好:
-- -------------------- ---- ------- ----- ------ - --- -------- ----- ----- ----- ---- --- ------- - - ----- ------- ------- ------------ -- - ----- --------- ------- ------------ -- - ----- --------- ------- ------------ -- - --- ----------------------- -- - ----- --------- ------- ------------ --- -- -- ------ -- ----------------------- --- -- -- ------ --
在这个示例中,我们使用 splice() 函数替换了 mobile 号码和删除了 office 号码,这比调用 update() 函数更新子文档的性能更好。
结论
在使用 Mongoose 进行数据模型设计时,使用嵌套的子文档可以更好地表示复杂的业务逻辑和数据关系。通过掌握使用子文档的基本概念、访问方法和优化技巧,你可以在开发应用程序时更加有效地使用 Mongoose 和 MongoDB。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/672269ee2e7021665e0bd9f7