You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 06:05:23