Mongoose中文文档-指南之文档(Documents)与子文档(Subdocuments)

 2018年12月29日    862     声明


在Mongoose中,每个文档(Document)都表示对数据库集中一条记录的映射。Document通过Model的查找(如:findById等)方法创建,创建后可以通过Document对其对应的数据库数据进行相关操作。

  1. 文档
  2. 子文档

1. 文档

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


1.1 检索

Mongoose提供了很多API可以从MongoDB中检索文档,详细信息请参阅Queries一章


1.2 更新

更新文档有很多种方法,在以下示例我们首先使用findById来查找文档:

Tank.findById(id, function (err, tank) {
  if (err) return handleError(err);

  tank.size = 'large';
  tank.save(function (err, updatedTank) {
    if (err) return handleError(err);
    res.send(updatedTank);
  });
});

还可以使用.set()来修改文档。以上示例中的,tank.size ='large'会变成tank.set({ size: 'large' })

Tank.findById(id, function (err, tank) {
  if (err) return handleError(err);

  tank.set({ size: 'large' });
  tank.save(function (err, updatedTank) {
    if (err) return handleError(err);
    res.send(updatedTank);
  });
});

上面修改文档时,首先从Mongo检索文档,然后发出更新命令(通过调用save来触发)。但是,如果我们不需要在应用中返回文档而只是想直接更新数据库中的属性,那么可以使用Model#update

Tank.update({ _id: id }, { $set: { size: 'large' }}, callback);

如果我们需要在应用程序返回的文档而又不想提前查询,那么有一个更好的选项findByIdAndUpdate

Tank.findByIdAndUpdate(id, { $set: { size: 'large' }}, { new: true }, function (err, tank) {
  if (err) return handleError(err);
  res.send(tank);
});

findAndUpdate/Remove静态方法最多只能更改一个文档,只需调用一次数据库就返回。这里有findAndModify几种变体。有关更多详细信息,请阅读API文档。

注意,在数据库中进行更改之前,findAndUpdate/Remove不会执行任何挂钩或验证。你可以使用runValidators选项访问有限的文档验证子集。但是,如果需要挂钩和完整文档验证,则首先需要查询文档然后save()它。


1.3 验证

文档在保存之前会进行验证。有关详细信息,请阅读API文档或验证章节。


1.4 覆盖

可以使用.set()来覆盖整个文档。如果要修改中间件中保存的文档,这很方便。

Tank.findById(id, function (err, tank) {
  if (err) return handleError(err);
  // Now `otherTank` is a copy of `tank`
  otherTank.set(tank);
});


2. 子文档

子文档是嵌入在其他文档中的文档。在Mongoose中,这意味着你可以在其他模式(schema)中嵌套模式。Mongoose有两个不同的子文档概念:子文档数组和单个嵌套子文档:

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

var parentSchema = new Schema({
  // Array of subdocuments
  children: [childSchema],
  // Single nested subdocuments. Caveat: single nested subdocs only work
  // in mongoose >= 4.2.0
  child: childSchema
});

子文档与普通文档类似。其嵌套模式可以包含:中间件自定义验证逻辑、虚拟属性以及顶级模式可以使用的任何其他功能。主要区别在于子文档不会单独保存,其会随顶级父文档的保存而保存。

var Parent = mongoose.model('Parent', parentSchema);
var 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);

子文档和顶级文档一样有savevalidate中间件,在父文档上调用save()会触发其所有子文档的save()中间件,以及同样的validate()中间件。

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

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

子文档的pre('save')pre('validate')中间件,会在顶级文档的pre('save')中间之前,和顶级文档的pre('validate')中间件之后执行。这是因为save()之前的验证实际上是一块内置的中间件。

// Below code will print out 1-4 in order
var childSchema = new mongoose.Schema({ name: 'string' });

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

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

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

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

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


2.1 查找子文档

默认情况下,每个子文档都有一个_id。Mongoose文档数组有一个特殊的id方法,用于搜索文档数组以查找具有指定_id的文档。

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


2.2 子文档添加到数组中

MongooseArray 中的方法(如pushunshiftaddToSet等)会透明地将参数强制转换为正确的类型:

var Parent = mongoose.model('Parent');
var parent = new Parent;

// create a comment
parent.children.push({ name: 'Liesl' });
var 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!');
});

也可以使用MongooseArrays的create方法创建子文档而不将它们添加到数组中。

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


2.3 移除子文档

每个子文档都有remove方法,对于数组子文档,这相当于在子文档上调用.pull()。 对于单个嵌套子文档,remove()等效于将子文档设置为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');
});


2.4 数组的备用声明语法

如果使用对象数组创建模式,mongoose会自动将对象转换为模式:

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


下一步

现在我们已经介绍了DocumentsSubdocuments,接下来让我们来看看验证-quering


变更记录

  • [2018-12-29] 基于Mongoose官方文档v5.4.1首次发布