如何在Mongoose的findOneAndUpdate中结合聚合管道使用$set与$setOnInsert
Mongoose聚合管道与upsert字段处理问题解答
问题场景
我使用Mongoose的findOneAndUpdate配合聚合管道的$set操作符处理User模型的registeredAt和createdAt字段,代码如下:
const newUser = await User.findOneAndUpdate( findOneAndUpdateQuery, [ { $set: { registeredAt: { $cond: [ { $not: ["$registeredAt"], }, new Date(), "$registeredAt", ], }, createdAt:{ $cond: [ { $not: ["$createdAt"], }, new Date(), "$createdAt", ], }, }, // {$setOnInsert: { // registeredAt: new Date(), // }}, ], { new: true, runValidators: true, upsert: true, setDefaultsOnInsert: true, } );
User模型定义:
const UserSchema = new mongoose.Schema( { registeredAt: Date, createdAt: { type: Date, default: Date.now, required: true, }, }, );
当前遇到两个问题:
- 执行upsert操作时,若不在$set管道中指定createdAt字段,该字段不会被设置;我需要仅在插入文档时设置registeredAt和createdAt,后续更新时不修改这两个字段,请问能否在上述查询中结合$setOnInsert实现需求?
- 为何使用聚合管道的$set时模型默认值不生效?
解答
1. 可以结合$setOnInsert实现需求
$setOnInsert在聚合管道更新中依然有效,它只会在插入新文档时执行字段赋值,更新已有文档时完全不会触发,正好匹配你的需求。
修改后的代码可以简化为:
const newUser = await User.findOneAndUpdate( findOneAndUpdateQuery, [ { $setOnInsert: { registeredAt: new Date(), createdAt: new Date() // 也可以用Date.now(),和模型默认值逻辑保持一致 } } ], { new: true, runValidators: true, upsert: true, setDefaultsOnInsert: true, } );
如果希望createdAt沿用模型定义的默认值,也可以不在$setOnInsert中指定该字段,保留setDefaultsOnInsert: true即可,插入时Mongoose会自动填充模型默认值:
const newUser = await User.findOneAndUpdate( findOneAndUpdateQuery, [ { $setOnInsert: { registeredAt: new Date() } } ], { new: true, runValidators: true, upsert: true, setDefaultsOnInsert: true, } );
2. 聚合管道$set不触发模型默认值的原因
这是因为聚合管道更新会绕过Mongoose的模型层逻辑,直接和MongoDB原生交互:
- 聚合管道属于MongoDB原生功能,Mongoose不会自动为其应用模型默认值、中间件等逻辑,仅会在显式开启
runValidators时触发验证 setDefaultsOnInsert选项仅对普通对象形式的更新操作生效(比如{ $set: {...}, $setOnInsert: {...} }),对聚合管道更新不起作用- 你之前用
$cond判断字段是否存在并赋值,是在聚合管道内手动实现了类似默认值的逻辑,但这和Mongoose模型的默认值机制完全独立
如果要在聚合管道更新中使用默认值,要么手动在管道内处理字段赋值,要么改用普通对象形式的更新操作。
内容的提问来源于stack exchange,提问作者YulePale
相关产品推荐
相关产品推荐

