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

8

我对MongoDBMongoose相对较新,我对为什么有些中间件在文档上运行,有些在查询上运行感到困惑。我也不明白为什么一些查询方法返回文档,而另一些则返回查询。如果查询返回文档是可以接受的,但为什么一个查询会返回查询,那它真正的作用是什么。

进一步地,我的问题是什么是Document函数、Model或Query函数,因为它们都有一些共同的方法,如updateOne

此外,我从Mongoose文档中获取了所有这些疑问。


我对所有这些东西也感到困惑。 - Sabit Rakhim
你可以查看这篇文章获取一些可能的答案。 - Vaibhav
1个回答

3

Tl;dr: 中间件类型通常会定义前/后钩子中this变量的指向:

中间件钩子 'this' 指向 方法
文档 文档 validate、save、remove、updateOne、deleteOne、init
查询 查询 count、countDocuments、deleteMany、deleteOne、estimatedDocumentCount、find、findOne、findOneAndDelete、findOneAndRemove、findOneAndReplace、findOneAndUpdate、remove、replaceOne、update、updateOne、updateMany
聚合 聚合对象 aggregate
模型 模型 insertMany

长说明:

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

中间件本身只是 mongoose 底层使用的 mongodbs 原生驱动程序的简写/包装。因此,通常可以像使用对象的常规方法一样使用所有中间件,而不必担心它是模型、查询、聚合还是文档中间件,只要它做您想要的。

然而,有几种用例需要区分调用这些方法的上下文。

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

例如: 假设我有以下模式:

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

现在假设您希望每次保存时都增加版本字段,以便自动将版本字段增加1。最简单的方法是定义一个钩子来为我们处理这个问题,这样当保存对象时就不必担心这个问题。例如,如果我们在刚刚创建或从数据库中获取的文档上使用.save(),我们只需要在模式中添加以下前置钩子即可:
 productSchema.pre('save', function(next) {
   this.version = this.version + 1; // or this.version += 1;
   next();
 });

现在,无论我们在此架构/模型的文档上调用.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();
 });

这是因为对象直接在数据库中更新,而不是先下载到nodejs中进行编辑再上传。这意味着,在此钩子中,this指的是查询而不是文档,也就是说,我们不知道version的当前状态。 如果我们要在类似以下的查询中递增版本号,我们需要更新钩子,使其自动添加$inc运算符:
 productSchema.pre('findOneAndUpdate', function(next) {
   this.$inc =  { version: 1 };
   next();
 });

另一种方法是通过手动获取目标文档并使用异步函数进行编辑来模拟先前的逻辑。在这种情况下,效率会降低,因为每次更新都需要两次调用数据库,但可以保持逻辑的一致性:

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

如需更详细的解释,请查看官方文档,其中还有一个专门段落讨论方法名称冲突的问题(例如,在Document和Query中都有remove()作为中间件方法)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接