mongoose子文档

子文档是在一个 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({
// Array of subdocuments
// 数组子文档
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
// 对象子文档
child: childSchema
});

数组子文档的其它定义形式

如果创建一个对象数组,Mongoose 会自动将其转换成 Schema 定义

1
2
3
4
5
6
7
const parentSchema = new Schema({
children: [{ name: 'string' }]
});
// Equivalent
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: {
// Do not do this! This makes `nested` a mixed path in Mongoose 5
type: { prop: String },
required: true
}
});

const schema = new Schema({
nested: {
// This works correctly
type: new Schema({ prop: String }),
required: true
}
});

取消子文档中的 _id

1
2
3
// disabled _id
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
// Subdocument
// 子文档
const subdocumentSchema = new mongoose.Schema({
child: new mongoose.Schema({ name: String, age: Number })
});
const Subdoc = mongoose.model('Subdoc', subdocumentSchema);

// Nested path
// 嵌套路径
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; // true
doc1.child.name = 'test'; // Throws TypeError: cannot read property...

const doc2 = new Nested({});
doc2.child === undefined; // false
console.log(doc2.child); // Prints 'MongooseDocument { undefined }'
doc2.child.name = 'test'; // Works

子文档默认值

  • 默认值为 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;

// create a comment
parent.children.push({ name: 'Liesl' });
const subdoc = parent.children[0];
console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
subdoc.isNew; // true

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
// 移除方式1
parent.children.id(_id).remove();

// 移除方式2
parent.children.pull(_id)

// 整体移除1
parent.child.remove();

// 整体移除2
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; // true

// 子文档是数组
doc.docArr[0].parent() === doc; // true

如果是多级嵌套,可以使用 ownerDocument() 来获取根文档。

致谢

本文参考以下文章,在此致以诚挚感谢!

  1. 官方文档: Subdocuments
  2. Mongoose v6.2.9: Schemas (mongoosejs.com)