使用Mongoose更新嵌套子文档时如何保留子文档_id?
问题:Mongoose更新嵌套子文档时自动移除子文档_id的解决办法
我尝试通过request.body更新Mongoose嵌套子文档,未传入子文档的_id,更新功能可以正常执行,但Mongoose会自动移除子文档的_id属性。
相关Schema定义
// schemas.js const mongoose = require('mongoose'); const motorSchema = new mongoose.Schema({ type: String, volume: Number, }); const carSchema = new mongoose.Schema({ manufacturer: String, model: String, motors: [motorSchema], }); const userSchema = new mongoose.Schema({ username: String, email: String, cars: [carSchema] });
当前更新代码(已修正拼写/语法错误)
const mongoose = require('mongoose'); // 导入schemas const userSchema = require('./userSchema'); const carSchema = require('./carSchema'); const motorSchema = require('./motorSchema'); // 创建模型 const User = mongoose.model("User", userSchema); const Car = mongoose.model("Car", carSchema); const Motor = mongoose.model("Motor", motorSchema); module.exports.updateCar = async function (request, response) { const condition = { _id: new mongoose.Types.ObjectId(request.body.userId), "cars._id": new mongoose.Types.ObjectId(request.body.carId) }; // 构建部分更新对象 const set = {}; for(let field in request.body){ set[`cars.$.${field}`] = request.body[field]; } const update = { $set: set }; User.findOneAndUpdate(condition, update, {new: true, overwrite: false}, (error, user) => { if(error) {response.send(error)} response.json(user); } ); }
问题核心:当request.body中传入motors数组(未包含原有_id)时,Mongoose会用新数组完全覆盖原有motors,并自动为新数组中的每个motor生成新的_id,原有_id被移除。
解决办法
方法1:避免完全覆盖motors数组,针对单个字段更新
如果只是更新motor的个别属性(比如type或volume),不要直接替换整个motors数组,而是用Mongoose的数组定位器针对具体motor进行更新:
// 假设request.body传入motor索引和要更新的字段 const condition = { _id: new mongoose.Types.ObjectId(request.body.userId), "cars._id": new mongoose.Types.ObjectId(request.body.carId) }; const update = { $set: { `cars.$.motors.${request.body.motorIndex}.type`: request.body.type, `cars.$.motors.${request.body.motorIndex}.volume`: request.body.volume } }; User.findOneAndUpdate(condition, update, {new: true}, (error, user) => { if(error) response.send(error); response.json(user); });
这种方式只会修改指定motor的目标字段,不会触动原有_id。
方法2:合并原有_id到更新数据中
如果必须替换整个motors数组,先查询出当前car的原有motors数据,提取每个motor的_id,再和request.body中的新motor数据合并后再更新:
module.exports.updateCar = async function (request, response) { try { // 查询目标用户及对应车辆 const user = await User.findOne({ _id: new mongoose.Types.ObjectId(request.body.userId), "cars._id": new mongoose.Types.ObjectId(request.body.carId) }); if(!user) return response.status(404).send('用户或车辆不存在'); // 定位目标车辆 const targetCar = user.cars.id(request.body.carId); // 合并原有motor的_id与新数据 const updatedMotors = request.body.motors.map((newMotor, index) => { return targetCar.motors[index] ? {...newMotor, _id: targetCar.motors[index]._id} : newMotor; }); // 构建更新字段,排除userId和carId const set = Object.fromEntries( Object.entries(request.body) .filter(([key]) => !['userId', 'carId'].includes(key)) ); set['cars.$.motors'] = updatedMotors; const updatedUser = await User.findOneAndUpdate( condition, { $set: set }, { new: true } ); response.json(updatedUser); } catch(error) { response.send(error); } };
这种方法确保原有motor的_id被保留,新添加的motor则自动生成_id。
方法3:显式配置子文档Schema保留_id
虽然Mongoose默认会为子文档生成_id,但可以显式声明强化这一行为(需配合前两种方法使用,无法单独解决覆盖问题):
const motorSchema = new mongoose.Schema({ type: String, volume: Number, }, { _id: true }); // 显式启用_id生成,默认值即为true,此处用于明确声明
内容的提问来源于stack exchange,提问作者MachineLearner
相关产品推荐
相关产品推荐

