文档

Mongoose 文档 代表与存储在 MongoDB 中的文档的一对一映射。每个文档都是其 模型 的一个实例。

文档 vs 模型

文档模型 是 Mongoose 中的两个不同的类。模型类是文档类的子类。当你使用 模型构造函数 时,你创建了一个新的文档。

const MyModel = mongoose.model('Test', new Schema({ name: String }));
const doc = new MyModel();

doc instanceof MyModel; // true
doc instanceof mongoose.Model; // true
doc instanceof mongoose.Document; // true

在 Mongoose 中,“文档” 通常指模型的实例。你不应该在没有通过模型的情况下创建文档类的实例。

检索

当你使用模型函数(如 findOne())从 MongoDB 加载文档时,你会得到一个 Mongoose 文档。

const doc = await MyModel.findOne();

doc instanceof MyModel; // true
doc instanceof mongoose.Model; // true
doc instanceof mongoose.Document; // true

使用 save() 更新

Mongoose 文档跟踪更改。你可以使用原生 JavaScript 赋值修改文档,Mongoose 会将其转换为 MongoDB 更新操作符

doc.name = 'foo';

// Mongoose sends an `updateOne({ _id: doc._id }, { $set: { name: 'foo' } })`
// to MongoDB.
await doc.save();

save() 方法返回一个 Promise。如果 save() 成功,Promise 会解析为保存的文档。

doc.save().then(savedDoc => {
  savedDoc === doc; // true
});

如果找不到具有相应 _id 的文档,Mongoose 会报告 DocumentNotFoundError

const doc = await MyModel.findOne();

// Delete the document so Mongoose won't be able to save changes
await MyModel.deleteOne({ _id: doc._id });

doc.name = 'foo';
await doc.save(); // Throws DocumentNotFoundError

设置嵌套属性

Mongoose 文档有一个 set() 函数,你可以用它来安全地设置深层嵌套属性。

const schema = new Schema({
  nested: {
    subdoc: new Schema({
      name: String
    })
  }
});
const TestModel = mongoose.model('Test', schema);

const doc = new TestModel();
doc.set('nested.subdoc.name', 'John Smith');
doc.nested.subdoc.name; // 'John Smith'

Mongoose 文档还有一个 get() 函数,它允许你安全地读取深层嵌套属性。get() 允许你避免显式检查空值,类似于 JavaScript 的 可选链操作符 ?.

const doc2 = new TestModel();

doc2.get('nested.subdoc.name'); // undefined
doc2.nested?.subdoc?.name; // undefined

doc2.set('nested.subdoc.name', 'Will Smith');
doc2.get('nested.subdoc.name'); // 'Will Smith'

你可以在 Mongoose 文档中使用可选链 ?. 和空值合并 ??。但是,在使用 空值合并赋值 ??= 创建嵌套路径时要小心。

// The following works fine
const doc3 = new TestModel();
doc3.nested.subdoc ??= {};
doc3.nested.subdoc.name = 'John Smythe';

// The following does **NOT** work.
// Do not use the following pattern with Mongoose documents.
const doc4 = new TestModel();
(doc4.nested.subdoc ??= {}).name = 'Charlie Smith';
doc.nested.subdoc; // Empty object
doc.nested.subdoc.name; // undefined.

使用查询更新

save() 函数通常是使用 Mongoose 更新文档的正确方法。使用 save(),你可以获得完整的 验证中间件

对于 save() 不够灵活的情况,Mongoose 允许你使用转换、中间件有限的验证 创建你自己的 MongoDB 更新

// Update all documents in the `mymodels` collection
await MyModel.updateMany({}, { $set: { name: 'foo' } });

请注意,update()updateMany()findOneAndUpdate() 等不会执行 save() 中间件。如果你需要保存中间件和完整的验证,请先查询文档,然后 save() 它。

验证

在文档保存之前,会对其进行转换和验证。Mongoose 首先将值转换为指定的类型,然后验证它们。在内部,Mongoose 在保存之前调用文档的 validate() 方法

const schema = new Schema({ name: String, age: { type: Number, min: 0 } });
const Person = mongoose.model('Person', schema);

const p = new Person({ name: 'foo', age: 'bar' });
// Cast to Number failed for value "bar" at path "age"
await p.validate();

const p2 = new Person({ name: 'foo', age: -1 });
// Path `age` (-1) is less than minimum allowed value (0).
await p2.validate();

Mongoose 还支持使用 runValidators 选项 对更新进行有限的验证。Mongoose 默认情况下会将参数转换为查询函数(如 findOne()updateOne())。但是,Mongoose 默认情况下不会对查询函数参数运行验证。你需要设置 runValidators: true 才能让 Mongoose 进行验证。

// Cast to number failed for value "bar" at path "age"
await Person.updateOne({}, { age: 'bar' });

// Path `age` (-1) is less than minimum allowed value (0).
await Person.updateOne({}, { age: -1 }, { runValidators: true });

阅读 验证 指南以了解更多详情。

覆盖

有两种不同的方法可以覆盖文档(替换文档中的所有键)。一种方法是使用 Document#overwrite() 函数,然后 save()

const doc = await Person.findOne({ _id });

// Sets `name` and unsets all other properties
doc.overwrite({ name: 'Jean-Luc Picard' });
await doc.save();

另一种方法是使用 Model.replaceOne()

// Sets `name` and unsets all other properties
await Person.replaceOne({ _id }, { name: 'Jean-Luc Picard' });

下一步

现在我们已经介绍了文档,让我们来看看 子文档