Mongoose Upsert操作是否会更新Schema默认字段值?
你遇到的这个情况完全符合Mongoose的设计逻辑——当执行upsert操作时,Mongoose默认会对插入新文档和更新已有文档两种场景都应用Schema中定义的默认值,哪怕你没有显式传入这些字段。
为什么会出现这个现象?
Mongoose在处理upsert请求时,会自动将你传入的更新文档与Schema的默认值做合并。也就是说,即使你没指定createDate和updateDate,它也会把default: Date.now生成的当前时间自动添加到操作中,不管是插入新数据还是更新已有数据。这是因为upsert的本质是“不存在则插入,存在则更新”,Mongoose默认会在这两个分支都执行默认值注入逻辑。
如何解决这个问题?
核心思路是区分两个日期字段的职责:createDate仅在文档首次插入时设置,updateDate在插入和更新时都要更新。这里有几种靠谱的方案:
1. 使用Mongoose内置的timestamps选项(最推荐)
Mongoose自带了时间戳自动处理功能,只要在Schema配置中加上timestamps: true,它会自动创建createdAt和updatedAt字段:
- 插入文档时:
createdAt和updatedAt都会设为当前时间 - 更新文档时:仅
updatedAt会更新为当前时间 - Upsert操作时:完全符合你的需求——新文档生成两个时间,已有文档只更新
updatedAt
代码示例:
const yourSchema = new Schema({ // 你的业务字段,比如name、key等 }, { timestamps: true });
这样就不用手动定义createDate和updateDate了,Mongoose会帮你处理所有逻辑,省心又不易出错。
2. 手动控制字段的更新逻辑
如果你想保留自定义的字段名(比如createDate和updateDate),可以这样做:
- 给
createDate保留default: Date.now,确保首次插入时自动赋值 - 移除
updateDate的默认值,改为在每次更新操作时用$set显式设置当前时间
代码示例:
// Schema定义 const yourSchema = new Schema({ // 其他字段 createDate: { type: Date, default: Date.now }, updateDate: { type: Date } }); // Upsert操作 const upsertDoc = { $set: { // 你的业务更新字段 updateDate: new Date() // 显式设置更新时间 } }; Model.update({ key: 123 }, upsertDoc, { upsert: true });
这样:
- 插入新文档时:
createDate用默认值,updateDate被$set设为当前时间 - 更新已有文档时:
createDate保持不变,只有updateDate被更新
3. 配合setDefaultsOnInsert和$setOnInsert精准控制
如果你需要更精细的控制,可以用Mongoose的setDefaultsOnInsert选项,结合$setOnInsert来确保createDate仅在插入时生效:
// Schema定义不变 const yourSchema = new Schema({ // 其他字段 createDate: { type: Date, default: Date.now }, updateDate: { type: Date } }); // Upsert操作 const upsertDoc = { $set: { // 业务字段 updateDate: new Date() }, $setOnInsert: { createDate: new Date() // 或者依赖默认值,配合setDefaultsOnInsert: true } }; Model.update({ key: 123 }, upsertDoc, { upsert: true, setDefaultsOnInsert: true });
setDefaultsOnInsert: true会让Mongoose仅在插入新文档时应用默认值,更新时不会触发,再加上$setOnInsert兜底,能确保createDate不会被误更新。
内容的提问来源于stack exchange,提问作者Talha Awan




