使用Mongoose的`pre`钩子在执行findOneAndUpdate()之前获取文档

3
我正在尝试在我的 MongoDB 后端中使用 Mongoose 的 pre 和 post 钩子,以便比较文档在其保存前后的状态,并根据更改触发其他事件。然而,到目前为止,我无法通过 Mongoose 的 pre 钩子获取文档。根据文档,“pre 钩子适用于 doc.save() 和 doc.update(),在这两种情况下都是指文档本身...”。因此,我尝试了以下方法。首先,在我的模型/架构中,我有以下代码:
let Schema = mongoose
  .Schema(CustomerSchema, {
    timestamps: true
  })
  .pre("findOneAndUpdate", function(next) {
    trigger.preSave(next);
  })
  // other hooks
}

然后在我的触发器文件中,我有以下代码:

exports.preSave = function(next) {
  console.log("this: ", this);
  }
};

但是这是控制台上的日志:

this: { preSave:[Function],postSave:[AsyncFunction] }

很明显这没有起作用。这并没有像我希望的那样记录文档。为什么在此处 this 不是文档本身,因为文档本身似乎表明了这一点?是否有一种方法可以通过 pre 钩子获得文档?如果没有,人们是否有其他方法来完成这个任务?

4个回答

11
混淆出现是由于每种中间件函数内部的this上下文不同。在文档prepost中间件期间,您可以使用this来访问文档模型,但不能在其他钩子中使用。
三种中间件函数,所有这些函数都有前置和后置阶段。
在文档中间件函数中,this指的是文档(模型)。
  • init、validate、save、remove
在查询中间件函数中,this指的是查询。
  • count、find、findOne、findOneAndRemove、findOneAndUpdate、update
在聚合中间件中,this指的是聚合对象。
  • aggregate
这里有更详细的解释。
因此,在pre("init")、pre("init")、pre("validate")、post("validate")、pre("save")、post("save")、pre("remove")、post("remove")期间,您可以简单地访问文档,但在其他任何钩子中都不能这样做。
我看到有些人在其他中间件挂钩中执行更多的查询以再次找到模型,但我认为这听起来相当危险。
简短的答案似乎是,您需要将原始查询更改为面向文档的方式,而不是查询或聚合样式。这确实看起来是一个奇怪的限制。

10

pre钩子函数中,无法获取文档。

根据文档pre是查询中间件,this指的是查询而不是被更新的文档。


3
哈里斯是正确的,这份文档findOneAndUpdate列为查询中间件(同样也适用于update)。事实上,这些方法根本无法访问正在更新的文档。 - JohnnyHK
1
感谢JohnnyHK和@Haris的澄清。那些文档很难理解。 - Muirik
1
您可以在/some/pre钩子中检索文档。这取决于中间件的类型,而不是前/后置。findOneAndUpdate是一种查询类型的中间件 - 但它并不是特定的pre - scipilot
@scipilot,那么像remove()这样的特定于文档的中间件怎么办?我也没有理解上下文。请看下面我的实现:`控制器删除一个对象:await entity.remove();预处理钩子:entitySchema.pre('remove',async (next)=>{console.log("removing",this); // ****** 输出 - removing {} await deleteAllUsersInEntity(this); await deleteAllThreadsInEntity(this);})` - Heet Shah
@HeetShah 最好你开一个新的问题并提供更多上下文,这样人们才能在那里帮助你。但是这应该可以工作:这应该是文档。你的文档是空的吗?{}是一个空对象,而不是什么都没有。 - scipilot

9
根据文档,您的预处理程序无法在函数中获取文档,但可以按如下方式获取查询。
schema.pre('findOneAndUpdate', async function() { 
 const docToUpdate = await this.model.findOne(this.getQuery());
 console.log(docToUpdate); // The document that findOneAndUpdate() will modify
});

1
如果您真的想要在查询中间件函数中访问文档(或ID),则需要进行以下操作。
UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, async function (next) {
  const user: any = this

  if (!user.password) {
    const userID = user._conditions?._id
    const foundUser = await user.model.findById(userID)
    ...
  }

如果有人需要在用户密码更改时使用哈希密码功能

UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, async function (next) {
  const user: any = this

  if (user.password) {
    if (user.isModified('password')) {
      user.password = await getHashedPassword(user.password)
    }
    return next()
  }

  const { password } = user.getUpdate()?.$set
  if (password) {
    user._update.password = await getHashedPassword(password)
  }
  next()
})

当触发"save"时,user.password存在。

user.getUpdate()将返回在触发"update"时更改的属性。


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