子文档是在一个 Schema
中嵌入另一个
Schema
,它的形式主要有两种:
子文档是数组
子文档是对象
注意,子文档是对象的情况只适用于 4.2.0 及以上的版本
什么是子文档?
子文档和顶层的文档具有相同的特性,它们唯一的区别是子文档不是单独存储的,它与顶层文档存储在一起。
由于子文档的特性,虽然子文档也具有 save()
方法,但是调用它却不会保存修改,如果想要保存修改,需要在顶层文档调用
save()
方法。
顶层文档与子文档的 pre('save')
和
pre('validate')
中间件执行顺序
1 2 3 4 5 6 7 8
| graph LR
ps[父save] pv[父validate] cs[子save] cv[子validate]
pv-->cv-->cs-->ps
|
子文档的定义
它们的定义如下:
1 2 3 4 5 6 7 8 9 10 11
| const childSchema = new Schema({ name: 'string' });
const parentSchema = new Schema({ children: [childSchema], child: childSchema });
|
数组子文档的其它定义形式
如果创建一个对象数组,Mongoose 会自动将其转换成 Schema 定义
1 2 3 4 5 6 7
| const parentSchema = new Schema({ children: [{ name: 'string' }] });
const parentSchema = new Schema({ children: [new Schema({ name: 'string' })] });
|
对象子文档的其它定义形式
如果创建的是一个嵌套对象,则不会转换成 Schema
定义,它是一个嵌套路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const schema = new Schema({ nested: { prop: String } });
const schema = new Schema({ nested: { type: { prop: String }, required: true } });
const schema = new Schema({ nested: { type: new Schema({ prop: String }), required: true } });
|
取消子文档中的 _id
1 2 3
| const childSchema = new Schema({ name: String }, { _id: false }); const parentSchema = new Schema({ children: [childSchema] });
|
_id
设置只能用于子文档中
子文档与嵌套路径
两者的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
const subdocumentSchema = new mongoose.Schema({ child: new mongoose.Schema({ name: String, age: Number }) }); const Subdoc = mongoose.model('Subdoc', subdocumentSchema);
const nestedSchema = new mongoose.Schema({ child: { name: String, age: Number } }); const Nested = mongoose.model('Nested', nestedSchema);
|
区别:
子文档的 child 没有值时为 undefined,而嵌套路径则不是:
1 2 3 4 5 6 7 8
| const doc1 = new Subdoc({}); doc1.child === undefined; doc1.child.name = 'test';
const doc2 = new Nested({}); doc2.child === undefined; console.log(doc2.child); doc2.child.name = 'test';
|
子文档默认值
- 默认值为
undefined
- 如果设置一个非空值,比如
{}
,则会给子文档赋予默认值
子文档查询
每一个子文档都有一个 _id
,可以通过 id()
方法来查询子文档
1
| const doc = parent.children.id(_id);
|
向子文档数组中插入新值
可以使用 push
, unshift
,
addToSet
等方法添加:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Parent = mongoose.model('Parent'); const parent = new Parent;
parent.children.push({ name: 'Liesl' }); const subdoc = parent.children[0]; console.log(subdoc) subdoc.isNew;
parent.save(function (err) { if (err) return handleError(err) console.log('Success!'); });
|
也可以使用 create
直接创建:
1
| const newdoc = parent.children.create({ name: 'Aaron' });
|
移除子文档
1 2 3 4 5 6 7 8 9 10 11
| parent.children.id(_id).remove();
parent.children.pull(_id)
parent.child.remove();
parent.child = null
|
获取子文档的父级
通过 parent()
方法获取父级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const schema = new Schema({ docArr: [{ name: String }], singleNested: new Schema({ name: String }) }); const Model = mongoose.model('Test', schema);
const doc = new Model({ docArr: [{ name: 'foo' }], singleNested: { name: 'bar' } });
doc.singleNested.parent() === doc;
doc.docArr[0].parent() === doc;
|
如果是多级嵌套,可以使用 ownerDocument()
来获取根文档。
致谢
本文参考以下文章,在此致以诚挚感谢!
- 官方文档:
Subdocuments
- Mongoose
v6.2.9: Schemas (mongoosejs.com)