Mongoose的.pre('save')无法触发问题

22

我有以下的mongoose.model('quotes')模型:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var quotesSchema = new Schema({
    created: { type: String, default: moment().format() },
    type: { type: Number, default: 0 },
    number: { type: Number, required: true },

    title: { type: String, required: true, trim: true},
    background: { type: String, required: true },

    points: { type: Number, default: 1 },
    status: { type: Number, default: 0 },
    owner: { type: String, default: "anon" }
});

var settingsSchema = new Schema({
    nextQuoteNumber: { type: Number, default: 1 }
});

// Save Setting Model earlier to use it below
mongoose.model('settings', settingsSchema);
var Setting = mongoose.model('settings');

quotesSchema.pre('save', true, function(next) {
  Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
    if (err) { console.log(err) };
    this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
    next();
  });
});

mongoose.model('quotes', quotesSchema);

为了存储递增的唯一索引Quote.number,我需要为mongoose.model('settings')添加一个额外的模式。在每次保存之前,调用quotesSchema.pre('save')来读取、增加并将nextQuoteNumber作为this.number传递给相应的next()函数。

然而,当在其他地方保存Quote时,整个.pre('save')函数似乎不会触发。由于未定义number并且没有任何console.log()输出到函数中,因此Mongoose中止保存。

5个回答

60

使用pre('validate')而不是pre('save')来设置必填字段的值。Mongoose在保存文档之前验证文档,因此如果存在验证错误,您的save中间件将不会被调用。将中间件从save切换到validate将使您的函数在进行验证之前设置数字字段的值。

quotesSchema.pre('validate', true, function(next) {
  Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
    if (err) { console.log(err) };
    this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
    next();
  });
});

1
谢谢。最终我只是在实际保存时在我的 pre('save') 中执行了该操作,因为它最终并不经常发生。如果我没有在我的 number 上设置 required: true,那么我的变量也能正常工作,对吗? - JKunstwald
非常感谢!我有一个未初始化的“必填”、“唯一”字段,尝试使用预保存钩子并没有起作用,直到我像你说的那样切换到验证。 - Taimoor Ahmad

7
对于被Google重定向到此处的人,请确保在声明methods和hooks之后再调用mongoose.model()

2
在某些情况下,我们可以使用
UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, function (next) {

但是我在函数中使用"this"来获取数据,但在findOneAndUpdate触发器中无法正常工作

我需要使用

  async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
    const result = await this.userModel.findById(id)
    Object.assign(result, doc)
    await result?.save()
    return result
  }

与其

  async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
    const result = await this.userModel.findByIdAndUpdate(id, doc, { new: true, useFindAndModify: false })
    return result
  }

可以使用内置的mongoose方法result.set(doc)来代替Object.assign(...) - Shubham P

2
简单的解决方法是使用findOne和save。
const user = await User.findOne({ email: email });
user.password = "my new passord";
await user.save();

0
我遇到了一个情况,pre('validate')没有起作用,因此我使用了pre('save')。我读到一些操作是直接在数据库上执行的,因此mongoose中间件不会被调用。我更改了我的路由端点,这将触发.pre('save')。我使用Lodash解析body并仅更新传递给服务器的字段。
router.post("/", async function(req, res, next){
    try{
        const body = req.body;
        const doc  = await MyModel.findById(body._id);
        _.forEach(body, function(value, key) {
            doc[key] = value;
        });

        doc.save().then( doc => {
            res.status(200);
            res.send(doc);
            res.end();
        });

    }catch (err) {
        res.status(500);
        res.send({error: err.message});
        res.end();
    }

});

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