猫鼬('findOneAndUpdate')中间件:需要访问原始文档



我正在尝试使用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

tl;博士

无法通过中间件实现。首先查询文档,然后单独更新文档的特定版本以防止争用条件。


无法按照您在猫鼬 Github 上尝试的此问题(来自主开发人员(的方式进行操作:

根据设计 - 正在更新的文档甚至可能不在服务器的内存中。为了做到这一点,猫鼬必须在执行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)

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

您还可以使用更朴素的实现,其中不使用锁定,这在您的特定业务案例中可能很好,只要您意识到争用条件的可能性和风险。

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

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}`);
};

我欢迎您对此解决方案看到的任何问题提供反馈。

如果您只想在更新yearlymeeting时更改为图标。然后你可以用this.getUpdate()this.setUpdate()来做到这一点

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

最新更新