文档中间件、模型中间件、聚合中间件、查询中间件有什么区别?

Pra*_*dey 6 mongoose mongodb node.js

我对MongoDBMongoose相当陌生,我真的很困惑为什么有些中间件适用于文档,有些适用于查询。我也对为什么一些查询方法返回文档和一些返回查询感到困惑。如果查询返回文档是可以接受的,但为什么查询返回查询以及它到底是什么。

向我的问题添加更多内容,什么是 Document 函数和 Model 或 Query 函数,因为它们都有一些通用方法,例如updateOne.

此外,我从猫鼬文档中收集了所有这些疑问。

Ben*_*wer 3

Tl;dr:中间件的类型最常定义thispre/post hook 中的变量所指的内容:

中间件钩子 “这个”指的是 方法
文档 文档 验证、保存、删除、updateOne、deleteOne、init
询问 询问 计数、countDocuments、deleteMany、deleteOne、estimatedDocumentCount、查找、findOne、findOneAndDelete、findOneAndRemove、findOneAndReplace、findOneAndUpdate、删除、replaceOne、更新、updateOne、updateMany
聚合 聚合对象 总计的
模型 模型 插入多个

长解释:

中间件什么也不是,只是以不同方式与数据库交互的内置方法。然而,由于与数据库交互的方式不同,每种方式都有不同的优点或首选用例,因此它们的行为也彼此不同,因此即使它们具有相同的名称,它们的中间件也可能表现不同。

就其本身而言,中间件只是在 mongoose 引擎盖下使用的 mongodbs 本机驱动程序的简写/包装器。因此,您通常可以使用所有中间件,就像使用对象的常规方法一样,而不必关心它是模型中间件、查询中间件、聚合中间件还是文档中间件,只要它执行您想要的操作即可。

但是,在一些用例中,区分调用这些方法的上下文非常重要。

最突出的用例是钩子。即*.pre()*.post()钩子。这些钩子是您可以“注入”到猫鼬设置中的方法,以便它们在特定事件之前或之后执行。

例如:假设我有以下架构:

const productSchema = new Schema({
  name: 'String',
  version: {
    type: 'Number',
    default: 0
  }
});
Run Code Online (Sandbox Code Playgroud)

现在,假设您总是希望在每次保存时增加版本字段,以便它自动将版本字段增加 1。

最简单的方法是定义一个钩子来为我们处理这个问题,这样我们在保存对象时就不必关心这个问题。例如,如果我们.save()在刚刚创建或从数据库中获取的文档上使用,我们只需将以下预挂钩添加到架构中,如下所示:

 productSchema.pre('save', function(next) {
   this.version = this.version + 1; // or this.version += 1;
   next();
 });
Run Code Online (Sandbox Code Playgroud)

现在,每当我们调用.save()此模式/模型的文档时,它总是会在实际保存之前增加版本,即使我们只更改了名称。

但是,如果我们不使用该中间件.save()或任何其他纯文档中间件,而是使用查询中间件(例如findOneAndUpdate()更新对象)怎么办?

然后,我们将无法使用该pre('save')钩子,因为.save()不会被调用。在这种情况下,我们必须为 实现类似的钩子findOneAndUpdate()

然而,在这里,我们最终遇到了中间件的差异,因为钩子findOneAndUpdate()不允许我们这样做,因为它是查询钩子,这意味着它无法访问实际文档,而只能访问查询本身。因此,如果我们仅更改产品名称,则以下中间件将无法按预期工作:

 productSchema.pre('findOneAndUpdate', function(next) {
   // this.version is undefined in the query and would therefor be NaN
   this.version = this.version + 1;
   next();
 });
Run Code Online (Sandbox Code Playgroud)

原因是,对象是直接在数据库中更新的,而不是先“下载”到nodejs,然后再次编辑和“上传”。这意味着,在这个钩子this中指的是查询而不是文档,这意味着我们不知道当前的状态version是什么。如果我们要在这样的查询中增加版本,我们需要按如下方式更新挂钩,以便它自动添加 $inc 运算符:

 productSchema.pre('findOneAndUpdate', function(next) {
   this.$inc =  { version: 1 };
   next();
 });
Run Code Online (Sandbox Code Playgroud)

或者,我们可以通过手动获取目标文档并使用异步函数对其进行编辑来模拟之前的逻辑。在这种情况下,效率会较低,因为每次更新它总是会调用数据库两次,但会保持逻辑一致:

productSchema.pre('findOneAndUpdate', async function() {
  const productToUpdate = await this.model.findOne(this.getQuery());
  this.version = productToUpdate.version + 1;
  next();
});
Run Code Online (Sandbox Code Playgroud)

更详细的解释,请查看官方文档,其中也有专门的段落来解决方法命名冲突的问题(例如,remove()同时是文档和查询中间件方法)