MongoDB执行findOneAndUpdate更新文档报_id不可修改错误
问题场景
我搭建了用于管理MongoDB数据库中所有集合的管理员门户,目前正在开发文档更新功能,需求为根据用户名匹配已存在的用户,将管理员提交的修改内容更新到对应用户文档。
为此在控制器中编写了如下更新接口:
exports.updateUser = async(req, res) => { try{ var user = new User({ firstName: req.body.firstName, lastName: req.body.lastName, username:req.body.username, password: req.body.password, email: req.body.email }); const static_username = req.body.static_username; await User.findOneAndUpdate({static_username} , user, { useFindAndModify: false}) .then(data => { if(!data){ res.status(404).send({ message : `Cannot Update user with ${id}. Maybe user not found!`}) }else{ res.redirect('/admin'); } }) .catch(err =>{ res.status(500).send({ message : "Error Update user information"}) }) } catch { console.log(err); } }
接口设计逻辑:
- 从请求体中提取管理员提交的修改字段存入user实例对象
- 将更新前的用户名赋值给
static_username变量,作为查询待更新用户的匹配条件
接口运行异常表现:
执行时进入了findOneAndUpdate的catch分支,返回如下响应:
{ "message" : "Error Update user information"}
底层抛出的原始错误信息:
MongoServerError: Plan executor error during findAndModify :: caused by :: Performing an update on the path '_id' would modify the immutable field '_id'
故障原因
报错的核心原因是传给findOneAndUpdate的更新参数是Mongoose模型的全新实例,而非普通的更新字段对象:
当你通过new User()创建模型实例时,Mongoose会自动为这个实例生成一个全新的_id属性(即使你没有手动传入该字段)。将这个完整的模型实例作为更新内容提交给MongoDB时,数据库会尝试用新实例的_id覆盖原文档的_id值,但_id是MongoDB的不可变字段,文档写入后就不允许修改,因此直接抛出字段不可修改的错误。
除此之外代码还存在三个潜在问题:
- 404响应分支中使用了未定义的
id变量,触发用户不存在场景时会抛出引用错误 - 混用
async/await和.then()/.catch()语法,外层catch块未声明接收err参数,异常发生时日志打印会报错 - 查询条件
{static_username}是ES6简写语法,等价于{static_username: static_username},即匹配集合中static_username字段等于传入值的文档,如果你的用户集合中没有名为static_username的字段,即使修复_id问题也会无法匹配到目标文档。
修复方法
不要给更新接口传全新的Mongoose模型实例,直接组装普通对象作为更新参数即可,参考修改代码:
exports.updateUser = async(req, res) => { try{ // 直接组装更新字段的普通对象,不创建User模型实例 const updateFields = { firstName: req.body.firstName, lastName: req.body.lastName, username: req.body.username, password: req.body.password, email: req.body.email }; const static_username = req.body.static_username; // 注意核对查询条件,如果你是用username字段匹配旧用户名,需要写成{username: static_username} const updatedUser = await User.findOneAndUpdate( { username: static_username }, updateFields, { useFindAndModify: false, new: true } ) if(!updatedUser){ return res.status(404).send({ message : `Cannot Update user with username ${static_username}. Maybe user not found!`}) } res.redirect('/admin'); } catch (err) { console.log(err); res.status(500).send({ message : "Error Update user information"}) } }
内容的提问来源于stack exchange,提问作者michaelpnrs
相关产品推荐
相关产品推荐

