MongoDB findAndModify更新contexts对象失败,求排查与修正
MongoDB批量更新脚本问题排查
集合结构
{ '_id': objectId, 'uuid': string, 'contexts' object -> (0:"wifi6", 1:"anydesk", _seriablizable_class:"App\\Collection\\Context") }
实际文档示例
{ "_id": { "$oid": "6399a76340573591210b9cf3" }, "uuid": "ae7470a9-af8f-4baf-9981-1581313e3923", "name": "text", "description": "text", "device_info": {}, "contexts": { "0": "wifi6", "1": "4g", "2": "2.4", "_seriablizable_class": "App\\Device\\Domain\\ValueObject\\ContextCollection" }, "filter_contexts": [ "wifi6", "4g", "2.4" ], "deleted": false, "created_at": { "$date": { "$numberLong": "1671014243165" } }, "updated_at": { "$date": { "$numberLong": "1671014243166" } } }
预期更新后文档
{ "_id": { "$oid": "6399a76340573591210b9cf3" }, "uuid": "ae7470a9-af8f-4baf-9981-1581313e3923", "name": "text", "description": "text", "device_info": {}, "contexts": { "0": "wifi6", "1": "4g", "2": "2.4", "3": "5G", "_seriablizable_class": "App\\Device\\Domain\\ValueObject\\ContextCollection" }, "filter_contexts": [ "wifi6", "4g", "2.4", "5G" ], "deleted": false, "created_at": { "$date": { "$numberLong": "1671014243165" } }, "updated_at": { "$date": { "$numberLong": "1671014243166" } } }
需求描述
批量更新集合中所有包含contexts.wifi6的文档:
- 在
contexts对象中,按现有数字索引的数量(排除_seriablizable_class字段)添加新字段,键为对应数字、值为"5G" - 同时更新
filter_contexts数组,追加"5G"元素
问题脚本(未生效)
db.devices.findAndModify( {'query':{contexts:"Wifi6"}, 'update': { [ '$group': { "_id": "$_id", "count": { "$sum": { "$size": { "$objectToArray": "$contexts" } } }, contexts: {$first:'$contexts'} }, '$set': { 'contexts': { $mergeObjects: [ { "$count-1": "5G"}, "$contexts" ] } } ] } })
脚本问题分析
- 查询条件错误:原查询
{contexts:"Wifi6"}是匹配整个contexts对象等于字符串"Wifi6",实际需要匹配contexts中存在值为wifi6的字段(注意大小写,原数据是小写),且匹配逻辑错误。 - API使用错误:
findAndModify的update参数仅支持标准更新操作符(如$set、$push),不能直接传入聚合管道阶段($group是聚合阶段,无法在update中执行)。 - 计数逻辑错误:
$size: {$objectToArray: "$contexts"}会包含_seriablizable_class字段,导致计数包含非数字键;且单个文档无需用$sum求和。 - 动态键名生成错误:
{ "$count-1": "5G"}无法被MongoDB解析为动态键名,需要用$arrayToObject配合计算逻辑生成键值对。
正确解决方案
使用updateMany配合聚合管道实现动态更新,脚本如下:
db.devices.updateMany( // 查询条件:匹配contexts中包含值为wifi6的文档 { "contexts": { "$elemMatch": { "$eq": "wifi6" } } }, [ { $set: { // 合并原contexts与新生成的5G字段 contexts: { $mergeObjects: [ "$contexts", { $arrayToObject: [ [ { // 计算新键名:数字键的数量转为字符串 k: { $toString: { $size: { $filter: { input: { $objectToArray: "$contexts" }, // 过滤出键为纯数字的元素 cond: { $regexMatch: { input: "$$this.k", regex: "^\\d+$" } } } } }, }, v: "5G" } ] ] } ] }, // 追加5G到filter_contexts数组 filter_contexts: { $concatArrays: ["$filter_contexts", ["5G"]] }, // 更新updated_at为当前时间(可选) updated_at: new Date() } } ] )
脚本说明
- 查询逻辑:通过
$elemMatch匹配contexts对象中的值包含wifi6,确保仅更新符合条件的文档。 - 动态键生成:
$objectToArray将contexts转为键值对数组$filter筛选出键为纯数字的元素$size获取数字键的数量,作为新字段的键名(转为字符串)$arrayToObject将键值对转为对象,再通过$mergeObjects合并到原contexts中
- 数组更新:用
$concatArrays将"5G"追加到filter_contexts数组末尾 - 时间更新:可选将
updated_at设为当前时间,符合业务更新逻辑
内容的提问来源于stack exchange,提问作者Erics Nguyen
相关产品推荐
相关产品推荐

