Mongoose数组对象插入或更新?遇重复键错误求助
问题根源分析
你遇到的重复键错误,核心原因是Schema中给数组元素的Index和Name设置的unique: true是集合级别的全局唯一约束,而非同一个Client下的数组内部唯一。这意味着整个telemetries集合里,所有文档的MQTT_FIELD数组中的Index都不能重复,哪怕属于不同Client的文档。另外,你当前的findOneAndUpdate逻辑只能处理插入场景,无法覆盖更新已存在元素的情况,触发upsert时容易违反全局唯一约束。
解决方案
步骤1:修正Schema与索引
先删除数组元素上的全局唯一约束,改为创建复合唯一索引,确保同一个Client下的MQTT_FIELD.Index唯一:
const TelemetrySchema = new mongoose.Schema({ Client: { type: mongoose.Schema.Types.ObjectId, ref: 'Client', }, MQTT_FIELD: [ { _id: false, Index: { type: Number, required: true, // 移除全局unique约束 }, Name: { type: String, trim: true, required: true, // 若不需要全局唯一Name,同样移除该约束 }, }, ], }); // 创建复合唯一索引:同一个Client下,MQTT_FIELD.Index不能重复 TelemetrySchema.index({ Client: 1, 'MQTT_FIELD.Index': 1 }, { unique: true });
步骤2:实现“插入/更新”逻辑
方案一:分两次操作(兼容性好)
先尝试更新已存在的元素,若未找到则执行插入:
const { Client, Field } = req.body; // 先尝试更新已存在的元素 let telemetry = await Telemetry.findOneAndUpdate( { Client, 'MQTT_FIELD.Index': Field.Index }, { $set: { 'MQTT_FIELD.$.Name': Field.Name } }, { new: true, omitUndefined: true } ).exec(); // 若未找到匹配项,执行插入(或upsert创建新文档) if (!telemetry) { telemetry = await Telemetry.findOneAndUpdate( { Client }, { $addToSet: { MQTT_FIELD: Field } }, { upsert: true, new: true, omitUndefined: true } ).exec(); } return res.status(200).json(telemetry);
方案二:聚合管道更新(MongoDB 4.2+支持,一次操作完成)
利用聚合管道的条件判断,一次操作实现“存在则更新,不存在则添加”:
const { Client, Field } = req.body; const telemetry = await Telemetry.findOneAndUpdate( { Client }, [ { $set: { MQTT_FIELD: { $cond: [ // 判断数组中是否存在目标Index { $in: [Field.Index, '$MQTT_FIELD.Index'] }, // 存在则更新对应元素的Name { $map: { input: '$MQTT_FIELD', as: 'field', in: { $mergeObjects: [ '$$field', { $cond: [ { $eq: ['$$field.Index', Field.Index] }, { Name: Field.Name }, {} ] } ] } } }, // 不存在则追加新元素到数组 { $concatArrays: ['$MQTT_FIELD', [Field]] } ] } } } ], { upsert: true, new: true, omitUndefined: true } ).exec(); return res.status(200).json(telemetry);
内容的提问来源于stack exchange,提问作者MeerArtefakt
相关产品推荐
相关产品推荐

