【问题标题】:Mongoose ('findOneAndUpdate') Middleware: Need access to original documentMongoose ('findOneAndUpdate') 中间件:需要访问原始文档
【发布时间】:2017-08-25 21:16:05
【问题描述】:

我正在尝试使用pre('findOneAndUpdate') 来更新Meeting 文档的icon 属性。更新基于yearlymeeting 属性的预先存在的值(见下文)。

因为prepostsave()钩子没有在update()上执行,我似乎根本无法访问原始文档。然而,这对于我尝试执行的操作至关重要。有没有办法解决?

例如,我可以在pre('save') 上实现我的目的,如下所示:

meetingSchema.pre('save', function(next) {
  const yearlymeetingSlug = this.yearlymeeting[0].toLowerCase().replace(/[^A-z0-9]/g, '');
  this.icon = `${yearlymeetingSlug}.png`
  next();
});

我想做的是这样的:

meetingSchema.pre('findOneAndUpdate', function(next) {
  const yearlymeetingSlug = originalDocument.yearlymeeting[0].toLowerCase().replace(/[^A-z0-9]/g, '');
  this.icon = `${yearlymeetingSlug}.png`
  next();
});

我了解 pre(findOneAndUpdate) 中的 this 指的是查询,而不是存储的文档本身。有什么方法可以访问该文档,以便我可以根据yearlymeeting 的存储值更新icon

【问题讨论】:

  • 如果您理解了,那么您就会理解“文档”在修改点位于“服务器”上,因此“客户端”上的任何库函数都无法进行更改到那里的文件。 .findOneAndUpdate() 只知道您在指令中实际发送的“当前数据”。如果客户端上已有字段内容,则在将选项填入$set 或类似选项之前进行修改。如果您没有该数据,那么您需要从服务器中检索它。 MongoDB 无法引用现有值

标签: mongodb mongoose middleware


【解决方案1】:

tl;博士

不能通过中间件。首先查询文档,然后单独更新文档的特定版本以防止出现竞争条件。


无法按照this issue on the Mongoose Github (from the main dev) 尝试的方式进行操作:

按照设计 - 正在更新的文档甚至可能不在服务器的内存中。为此,mongoose 必须在执行 update() 之前执行 findOne() 来加载文档,这是不可接受的。

设计是为了让你能够通过添加或删除过滤器、更新参数、选项等来操作查询对象。例如,使用 find() 和 findOne() 自动调用 .populate(),设置 multi: true某些型号、访问控制和其他可能性的默认选项。

findOneAndUpdate() 有点用词不当,它使用底层的 mongodb findAndModify 命令,与 findOne() + update() 不同。作为一个单独的操作,它应该有自己的中间件。

在此之后,问题线程中没有其他建议可以访问中间件本身内部的原始文档。

我所看到的(以及我自己必须做的很多次),只是必须在更新之前查询文档(当然,这可能会导致竞争条件,具体取决于谁是更新文档,何时更新,但您也可以通过查询文档的特定版本来解决这个问题——一种“乐观锁定”):

let meeting = yield Meeting.findOne({}).exec()
let update = {}

// ... some conditional logic to figure out which icon to set
update.icon = // whatever

yield Meeting.update({ _id: meeting._id, version: meeting.version }, update)

这当然是假设您的架构中有一个“版本”字段。这种锁定会阻止您更新旧版本的文档。如果您要使用这种版本控制,您可能还需要添加一些中间件,以便在文档更新/保存时随时更新文档版本。

您还可以使用更简单的实现,即不使用锁定,这在您的特定业务案例中可能没问题,只要您知道竞争条件的可能性和风险。

【讨论】:

    【解决方案2】:

    这可能不是最好的解决方案,但我确实找到了让它发挥作用的方法。我使用了控制器而不是模式预挂钩。这是我的更新控制器现在的样子:

    exports.updateMeeting = async (req, res) => {
      const _id = req.params.id
      let meeting = await Meeting.findOneAndUpdate({ _id }, req.body, {
        new: true,
        runValidators: true
      });
    
      /* New Code: */
      const yearlymeetingSlug = meeting.yearlymeeting[0].toLowerCase().replace(/[^A-z0-9]/g, '');
      meeting.icon = `${yearlymeetingSlug}.png`;
      meeting.save();
    
      req.flash('success', 'meeting successfully updated!');
      res.redirect(`/meetings/${meeting.slug}`);
    };
    

    欢迎您就此解决方案遇到的任何问题提供反馈。

    【讨论】:

    • await meeting.save()。它仍然是一个异步方法。
    猜你喜欢
    • 2019-02-07
    • 2016-04-25
    • 2017-10-14
    • 2017-08-13
    • 2020-09-21
    • 1970-01-01
    • 2021-07-10
    • 2016-10-15
    • 2016-04-30
    相关资源
    最近更新 更多