Mongoose:如何利用当前数据更新嵌套数组中的元素?
MongoDB数组元素更新时$concatArrays不生效的解决方法
集合初始数据
const documents = [ { _id: '1', items: [ { type: 'plastic', color: 'blue', date: '2021-02-01', oldValues: [{ color: 'light-blue', date: '2021-01-01' }], }, { type: 'plastic', color: 'black', date: '2021-03-01', oldValues: [ { color: 'white', date: '2021-01-01' }, { color: 'grey', date: '2021-02-01' }, ], }, ], }, { _id: '2', items: [ { type: 'plastic', color: 'violet', date: '2021-02-01', oldValues: [{ color: 'pink', date: '2021-01-01' }], }, { type: 'wood', color: 'black', date: '2021-03-01', oldValues: [ { color: 'white', date: '2021-01-01' }, { color: 'orange', date: '2021-02-01' }, ], }, ], }, ];
需求说明
找到集合中文档的items数组里,满足type: 'plastic'且color: 'black'的元素:
- 将该元素的
color字段更新为green - 将该元素更新前的
color和date数据追加到其oldValues数组中
错误的更新语句
以下语句执行后,目标元素的color成功更新,但oldValues被直接赋值为聚合表达式对象,未计算出预期的数组:
db.collection.update( { items: { $elemMatch: { color: 'black', type: 'plastic' } } }, { $set: { 'items.$[item].color': 'green', 'items.$[item].date': '2021-04-01', 'items.$[item].oldValues': { $concatArrays: [ { $ifNull: ['$items.$[item].oldValues', []] }, [{ color: '$items.$[item].color', date: '$items.$[item].date' }], ], }, }, }, { arrayFilters: [{ 'item.color': 'black', 'item.type': 'plastic' }] }, );
问题原因
普通$set更新操作不支持直接使用聚合运算符(如$concatArrays、$ifNull),只能接受常量或字段引用。要使用聚合表达式处理更新逻辑,需采用MongoDB 4.2及以上版本支持的管道式更新(即更新操作符参数为数组形式的聚合管道)。
解决方案
方法1:管道式更新(推荐)
利用聚合管道遍历数组,匹配目标元素并执行更新逻辑:
db.collection.update( { "items": { $elemMatch: { color: "black", type: "plastic" } } }, [ { $set: { items: { $map: { input: "$items", as: "item", in: { $cond: { if: { $and: [{ $eq: ["$$item.type", "plastic"] }, { $eq: ["$$item.color", "black"] }] }, then: { type: "$$item.type", color: "green", date: "$$item.date", // 如需修改date可替换为'2021-04-01' oldValues: { $concatArrays: [ { $ifNull: ["$$item.oldValues", []] }, [{ color: "$$item.color", date: "$$item.date" }] ] } }, else: "$$item" } } } } } } ], { multi: true } )
$map遍历items数组,对每个元素进行判断$cond筛选出符合type: plastic且color: black的元素,构造新对象实现color更新和oldValues追加multi: true确保所有匹配的文档都被更新
方法2:事务实现(原子性保障)
如果需要保证多文档更新的原子性,可通过MongoDB事务实现:
// 开启会话与事务 const session = db.getMongo().startSession(); session.startTransaction(); try { // 指定目标库和集合 const targetColl = session.getDatabase("your_db_name").collection("your_collection_name"); // 查询所有符合条件的文档 const matchedDocs = targetColl.find({ "items": { $elemMatch: { color: "black", type: "plastic" } } }).toArray(); // 遍历文档执行更新 matchedDocs.forEach(doc => { const updatedItems = doc.items.map(item => { if (item.type === "plastic" && item.color === "black") { return { ...item, color: "green", oldValues: [ ...(item.oldValues || []), { color: item.color, date: item.date } ] }; } return item; }); // 更新当前文档 targetColl.updateOne( { _id: doc._id }, { $set: { items: updatedItems } } ); }); // 提交事务 session.commitTransaction(); print("事务执行成功"); } catch (error) { print(`事务执行失败,回滚: ${error}`); session.abortTransaction(); } finally { // 关闭会话 session.endSession(); }
- 通过会话开启事务,先查询所有匹配文档
- 在客户端处理数组元素的更新逻辑,再逐个更新文档
- 若过程中出现异常则回滚事务,确保操作原子性
验证结果
执行上述任一方法后,目标元素的color会变为green,oldValues数组会追加更新前的{ color: 'black', date: '2021-03-01' },完全符合预期。
内容的提问来源于stack exchange,提问作者Majid Rafei
相关产品推荐
相关产品推荐

