Subdocuments子文档

Subdocuments are documents embedded in other documents. 子文档是嵌入到其他文档中的文档。In Mongoose, this means you can nest schemas in other schemas. 在Mongoose中,这意味着您可以在其他模式中嵌套模式。Mongoose has two distinct notions of subdocuments: arrays of subdocuments and single nested subdocuments.Mongoose有两个不同的子文档概念:子文档数组和单个嵌套子文档。

const childSchema = new Schema({ name: 'string' });

const parentSchema = new Schema({
// Array of subdocuments子文档数组
children: [childSchema],
// Single nested subdocuments单个嵌套子文档
child: childSchema
});

Note that populated documents are not subdocuments in Mongoose. 请注意,在Mongoose中,填充的文档不是子文档。Subdocument data is embedded in the top-level document. 子文档数据嵌入到顶级文档中。Referenced documents are separate top-level documents.引用的文档是单独的顶级文档。

const childSchema = new Schema({ name: 'string' });
const Child = mongoose.model('Child', childSchema);

const parentSchema = new Schema({
child: {
type: mongoose.ObjectId,
ref: 'Child'
}
});
const Parent = mongoose.model('Parent', parentSchema);

const doc = await Parent.findOne().populate('child');
// NOT a subdocument. `doc.child` is a separate top-level document.
doc.child;

What is a Subdocument?什么是子文档?

Subdocuments are similar to normal documents. 子文档与普通文档类似。Nested schemas can have middleware, custom validation logic, virtuals, and any other feature top-level schemas can use. The major difference is that subdocuments are not saved individually, they are saved whenever their top-level parent document is saved.嵌套模式可以具有中间件自定义验证逻辑、虚拟机以及顶层模式可以使用的任何其他功能。主要区别在于,子文档不是单独保存的,而是在保存其顶级父文档时保存的。

const Parent = mongoose.model('Parent', parentSchema);
const parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] });
parent.children[0].name = 'Matthew';

// `parent.children[0].save()` is a no-op, it triggers middleware but does **not** actually save the subdocument. 是一个非操作,它触发中间件,但实际上并**没有**保存子文档。
// You need to save the parent doc.您需要保存父文档。
parent.save(callback);

Subdocuments have save and validate middleware just like top-level documents. 子文档具有savevalidate中间件,就像顶级文档一样。Calling save() on the parent document triggers the save() middleware for all its subdocuments, and the same for validate() middleware.对父文档调用save()会触发其所有子文档的save()中间件,validate()中间件也是如此。

childSchema.pre('save', function(next) {
if ('invalid' == this.name) {
return next(new Error('#sadpanda'));
}
next();
});

const parent = new Parent({ children: [{ name: 'invalid' }] });
parent.save(function(err) {
console.log(err.message); // #sadpanda
});

Subdocuments' pre('save') and pre('validate') middleware execute before the top-level document's pre('save') but after the top-level document's pre('validate') middleware. 子文档的pre('save')pre('validate')中间件在顶层文档的pre('save')中间件之前执行,但在顶层文档的pre('validate')中间件之后执行。This is because validating before save() is actually a piece of built-in middleware.这是因为在save()之前进行验证实际上是一个内置的中间件。

// Below code will print out 1-4 in order以下代码将按顺序打印1-4
const childSchema = new mongoose.Schema({ name: 'string' });

childSchema.pre('validate', function(next) {
console.log('2');
next();
});

childSchema.pre('save', function(next) {
console.log('3');
next();
});

const parentSchema = new mongoose.Schema({
child: childSchema
});

parentSchema.pre('validate', function(next) {
console.log('1');
next();
});

parentSchema.pre('save', function(next) {
console.log('4');
next();
});

Subdocuments versus Nested Paths子文档与嵌套路径的对比

In Mongoose, nested paths are subtly different from subdocuments. 在Mongoose中,嵌套路径与子文档有细微的不同。For example, below are two schemas: one with child as a subdocument, and one with child as a nested path.例如,下面是两个模式:一个使用child以子文档形式表示,另一个使用child以嵌套路径形式表示。

// 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);

These two schemas look similar, and the documents in MongoDB will have the same structure with both schemas. 这两个模式看起来很相似,MongoDB中的文档将与这两个架构具有相同的结构。But there are a few Mongoose-specific differences:但有一些特定于Mongoose的差异:

First, instances of Nested never have child === undefined. You can always set subproperties of child, even if you don't set the child property. But instances of Subdoc can have child === undefined.

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

Subdocument Defaults子文档默认值

Subdocument paths are undefined by default, and Mongoose does not apply subdocument defaults unless you set the subdocument path to a non-nullish value.默认情况下,子文档路径是未定义的,除非将子文档路径设置为非null值,否则Mongoose不会应用子文档默认值。

const subdocumentSchema = new mongoose.Schema({
child: new mongoose.Schema({
name: String,
age: {
type: Number,
default: 0
}
})
});
const Subdoc = mongoose.model('Subdoc', subdocumentSchema);

// Note that the `age` default has no effect, because `child`
// is `undefined`.
const doc = new Subdoc();
doc.child; // undefined

However, if you set doc.child to any object, Mongoose will apply the age default if necessary.然而,如果您将doc.child设置为任何对象,Mongoose将在必要时应用age默认值。

doc.child = {};
// Mongoose applies the `age` default:
doc.child.age; // 0

Mongoose applies defaults recursively, which means there's a nice workaround if you want to make sure Mongoose applies subdocument defaults: make the subdocument path default to an empty object.Mongoose递归地应用默认值,这意味着如果你想确保Mongoose应用子文档默认值,有一个很好的解决方法:使子文档路径默认为空对象。

const childSchema = new mongoose.Schema({
name: String,
age: {
type: Number,
default: 0
}
});
const subdocumentSchema = new mongoose.Schema({
child: {
type: childSchema,
default: () => ({})
}
});
const Subdoc = mongoose.model('Subdoc', subdocumentSchema);

// Note that Mongoose sets `age` to its default value 0, because
// `child` defaults to an empty object and Mongoose applies
// defaults to that empty object.
const doc = new Subdoc();
doc.child; // { age: 0 }

Finding a Subdocument查找子文档

Each subdocument has an _id by default. 默认情况下,每个子文档都有一个_idMongoose document arrays have a special id method for searching a document array to find a document with a given _id.Mongoose文档数组有一个特殊的id方法,用于搜索文档数组以找到具有给定_id的文档。

const doc = parent.children.id(_id);

Adding Subdocs to Arrays向数组中添加子对象

MongooseArray methods such as push, unshift, addToSet, and others cast arguments to their proper types transparently:MongooseArray方法,如pushunshiftaddToSet和其他方法,可以透明地将参数转换为正确的类型:

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!');
});

You can also create a subdocument without adding it to an array by using the create() method of Document Arrays.您也可以使用文档数组的create()方法创建子文档,而无需将其添加到数组中。

const newdoc = parent.children.create({ name: 'Aaron' });

Removing Subdocs删除子文档

Each subdocument has its own remove method. For an array subdocument, this is equivalent to calling .pull() on the subdocument. For a single nested subdocument, remove() is equivalent to setting the subdocument to null.

// Equivalent to `parent.children.pull(_id)`
parent.children.id(_id).remove();
// Equivalent to `parent.child = null`
parent.child.remove();
parent.save(function(err) {
if (err) return handleError(err);
console.log('the subdocs were removed');
});

Parents of SubdocsSubdocs的父级

Sometimes, you need to get the parent of a subdoc. 有时,您需要获取subdoc的父级。You can access the parent using the parent() function.您可以使用parent()函数访问父级。

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

If you have a deeply nested subdoc, you can access the top-level document using the ownerDocument() function.如果你有一个嵌套很深的子域,你可以使用ownerDocument()函数访问顶级文档。

const schema = new Schema({
level1: new Schema({
level2: new Schema({
test: String
})
})
});
const Model = mongoose.model('Test', schema);

const doc = new Model({ level1: { level2: 'test' } });

doc.level1.level2.parent() === doc; // false
doc.level1.level2.parent() === doc.level1; // true
doc.level1.level2.ownerDocument() === doc; // true

Alternate declaration syntax for arrays数组的备用声明语法

If you create a schema with an array of objects, Mongoose will automatically convert the object to a schema for you:如果您创建了一个带有对象数组的模式,Mongoose会自动将对象转换为您的模式:

const parentSchema = new Schema({
children: [{ name: 'string' }]
});
// Equivalent
const parentSchema = new Schema({
children: [new Schema({ name: 'string' })]
});

Next Up下一步

Now that we've covered Subdocuments, let's take a look at querying.现在我们已经介绍了子文档,让我们来看看查询