嗨,我对最新版本的 Mongoose 有疑问。 我正在使用 Express 和 Mongoose 4.0.1 创建一个 API,我不确定我是否做错了什么,但事实是每当我尝试以相同的方式使用新的pre
更新中间件时我使用pre
保存中间件, this
对象不返回正在更新的对象,而是返回Query
对象。
我试图解释的例子:
ExerciseSchema.pre('save', function (next, done) {
var self = this;
console.log('exercise:', self); // returns exercise object
// some validations here
next();
});
ExerciseSchema.pre('update', function (next, done) {
var self = this;
console.log('exercise:', self); // returns Query object instead of exercise object
// some validations here
next();
});
这是我在中间件中的this
引用对象中得到的,我不知道它对我有什么用处。
{ _mongooseOptions: {},
mongooseCollection:
{ collection: { s: [Object] },
opts: { bufferCommands: true, capped: false },
name: 'exercises',
conn:
... },
...
}
查看源代码,它的一些属性在./node_modules/mongoose/lib/query.js
中定义的Query
函数中定义:
这是出乎意料还是我做错了什么? 有一个解决方案会很有趣,因为我不喜欢在中间件中验证对象保存并在更新时被迫直接在控制器上运行验证的想法。
findOneAndUpdate pre
中间件也会发生同样的事情,但我不希望它在找到之前返回练习。 事实上,老实说,我认为 findAndUpdate 和 findOneAndUpdate pre 中间件会按此顺序触发 find(One) 和更新中间件,而不是拥有自己的中间件,这会很有趣。
在此先感谢您的帮助。
按照设计 - 正在更新的文档甚至可能不在服务器的内存中。 为了做到这一点,猫鼬必须在执行 update() 之前执行 findOne() 来加载文档,这是不可接受的。
该设计使您能够通过添加或删除过滤器、更新参数、选项等来操作查询对象。例如,使用 find() 和 findOne() 自动调用 .populate() ,设置multi: true
某些型号、访问控制和其他可能性的默认选项。
findOneAndUpdate()
有点用词不当,它使用底层的mongodb findAndModify
命令,它与findOne()
+ update()
不同。 作为一个单独的操作,它应该有自己的中间件。
那么,您如何建议在“预”更新中间件期间更新文档字段?
this.update({ field: val });
将在更新操作发生之前将{ $set: { field: val } }
添加到更新操作中。
感谢您的回复,应该在哪个版本上工作? 因为我使用的是 4.0.2 而不是。 我必须在查询中调用“exec”吗?
如果我运行 this.update 我不会调用这个方法http://mongoosejs.com/docs/api.html#query_Query -update 吗?
我最终这样做了。findOneAndUpdate({matcher: "myvalue"}); 希望这是正确的方法。
嗯 4.0.2 应该可以工作。 你的代码是什么样的?
我只是做了这样的事情:
var schema = mongoose.Schema({
name: String,
description: String,
matcher: String,
});
var generateMatcherUpdate= function(next) {
var matcher = "generate matcher function"
this.update({matcher: matcher});
next();
};
schema.pre('update', generateMatcherUpdate);
啊,好吧,我看到您使用的是findOneAndUpdate()
而不是update()
。 这是两个具有不同钩子的不同函数,所以如果你想在预钩子中将更新操作添加到findOneAndUpdate
,你应该这样做
schema.pre('findOneAndUpdate', function() {
this.findOneAndUpdate({}, { $set: { key: 'value' } });
});
man mongoose 文档非常糟糕。 有人可以把这个放在那里吗?
@askdesigners 到底放了什么?
抱歉,我是在压力下写的:)
只是版本兼容性的脆弱性。 这不是一个明显的问题,让很多人摸不着头脑。
不用担心。 你有什么样的版本兼容性问题?
如果我想清理 pre.update 上的值怎么办......我似乎无法找到一种方法来做到这一点。
谢谢!
schema.pre('update', function() {
var v = this.getUpdate().valueToSanitize;
this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});
不错,我试试看!
谢谢!!
嘿@vkarpov15 ,我遇到了一些我想说的意外行为,或者至少应该记录下来。
如果我做类似的事情
MyModel.findOneAndUpdate({a: 1}, {v: '__3'}, function (err, model) {
console.log(model);
});
在我的模型上,我实施了你的建议
mySchema.pre('findOneAndUpdate', function() {
var v = this.getUpdate().v;
this.findOneAndUpdate({}, { $set: { v: sanitize(v) } });
});
日志将输出“__3”,因为原始更新查询将在钩子之后执行。 我可以更改其他模式属性,但是在原始查询中更新的所有字段都将保持不变,即使它们在 pre 挂钩中发生了更改。
@CMatias在这种情况下,您需要小心使用$set
或不一致。 Mongoose 不一定知道是否要包装$set
,所以在你的情况下你应该做this.findOneAndUpdate({}, { v: sanitize(v) });
@vkarpov15谢谢,那行得通。 你能简单解释一下使用$set
和不使用它有什么区别吗? 找不到太多关于它的信息。
因此,如果您直接使用本机 mongodb 驱动程序并执行coll.findOneAndUpdate({ a: 1 }, { __v: 3 })
,则 mongodb 将采用 a = 1 的第一个文档并将其替换为文档{ __v: 3 }
modulo _id
。 换句话说,它将覆盖现有文档。 为了只设置键'__v',你需要做coll.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } })
。
这种行为一直存在争议且容易出错,因此默认情况下 mongoose 会阻止您覆盖文档。 换句话说, MyModel.findOneAndUpdate({ a: 1 }, { __v: 3 })
变为MyModel.collection.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } })
_除非您设置了overwrite: true
选项。 但是,你可以做一些奇怪的事情,比如MyModel.update({}, { $set: { b: 2 } }).findOneAndUpdate({ a: 1 }, { __v: 3 }, { overwrite: true })
,这会让getUpdate()
的当前状态变得非常混乱,所以我们只保留update
的状态。用于中间件。
我正在尝试了解所有这些,但很难确定我是否可以修改将使用这些中间件方法从查找中返回的文档。 具体来说,我想通过添加额外的字段来“装饰”响应。
例如,如果我有一个模式:
const Thing = new mongoose.Schema({
name: {type: String}
});
Thing.post('find', function(doc, next){
doc.newfield = "example text";
next();
}
这可能吗?
newfield
未在模式中定义的原因是,根据Thing
的值,需要不同的字段名称。
谢谢!
我需要任何特定的插件来使用 schema.pre('update') 吗?
不@jrogatis
@vkarpov15
看看你上面给出的例子......:
schema.pre('update', function() {
var v = this.getUpdate().valueToSanitize;
this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});
这对我不起作用。 如示例所示, this.getUpdate().value
不包含我的财产value
。 相反,它在this.getUpdate().$set.property
下可用。
应该是这样吗? 我理解错了吗? API有变化吗? 有这方面的文件吗?
@qqilihq你能用重现脚本打开一个单独的问题吗?
@qqilihq nvm,刚刚看到你做到了:P
@varunjayaraman实际上,我打开的单独问题是另一个问题 :) 但是我很乐意为上面的评论打开一个新问题,一旦我有一些空闲时间来编译一个示例。
@qqilihq啊好的,谢谢!
对我来说和@qqilihq提到的完全一样
因此,可以使用以下代码实现所需的行为:
userSchema.pre('update', function (next) {
const newEmail = this.getUpdate().$set.email
if (newEmail) {
this.update({}, {$set: {email: normalizeEmail(newEmail)}})
}
next()
})
但看起来我正在尝试使用一些不适合此的字段。
pre
方法不应该与this.update
一起使用,因为它是“pre”-save 并且还没有什么要更新的,而this.newProp = "value"
可以完美地工作,谢谢大家。
我的情况是:
UserSchema.pre('save', function(next) {
this.created = Date.now()
next()
})
我理解在“pre”更新挂钩上将查询设置为“this”的原因,但为什么“post”更新挂钩的行为方式类似? 发布更新不应该将实际模型设为“this”吗?
那么 post find 会执行多次还是将this
作为有多个结果的数组? 如果没有结果,应该 post find 不运行吗?
这一切都很奇怪。 恕我直言,pre 钩子的行为应该都是一样的, this
是被修改的对象,还有像 isModified 这样的好方法来检查事情。
其实我想我现在明白了。 我应该只使用 pre 'save' 方法,然后使用 this.isNew 方法。 谢谢!
@ajbraus这有点奇怪,但替代方案是巨大的性能开销。 假设您有一个 updateMany 可以更新数百万个文档 - 猫鼬必须将它们全部加载到内存中。
应该如何在更新之前检查文档字段? 假设我需要检查是否填写了某些字段以将文档设置为“有效”,我如何访问整个文档,而不仅仅是正在更新的字段? this
指的是这里的查询
@PDS42您必须运行单独的查询才能从 mongodb 加载文档
猫鼬文档中是否有一个特定的链接,其中所有这些都得到了更深入的解释? 我很想阅读它并更全面地理解这一点:)。
编辑:
预更新挂钩: https ://github.com/Automattic/mongoose/issues/2812
中间件: https ://mongoosejs.com/docs/middleware.html
查询对象: https ://mongoosejs.com/docs/api.html#Query
查询: https ://mongoosejs.com/docs/queries.html
我很难找到关于应该如何在更新中间件中使用“this”引用的查询的文档。 例如,在以下情况下会发生什么:
this.update({field: "value"}, {$set: {updatedAt: new Date()}});
这是否将{field: "value"}
添加到过滤器中? 还是没有效果? 有没有我没有看到的文档?
@ronakvora有一个Query#getConditions()
函数。
schema.pre('update', function() {
console.log(this.getConditions()); // Will include `{ field: 'value' }`
});
Gotcha,这将帮助我玩东西:)。 谢谢!
Ps 这是上面的Ronak。 显然我有两个帐户在不同的设备上登录。
最有用的评论
啊,好吧,我看到您使用的是
findOneAndUpdate()
而不是update()
。 这是两个具有不同钩子的不同函数,所以如果你想在预钩子中将更新操作添加到findOneAndUpdate
,你应该这样做