使用MongoDB Rust驱动时的子文档合并问题及解决
问题分析
你这段代码的核心问题是把聚合表达式用在了普通更新操作的字段值里:MongoDB只有在聚合管道式更新(更新参数为数组形式的聚合阶段)中,才会解析$mergeObjects这类聚合操作符;而你最初的写法是把$mergeObjects作为$set的字段值传入,Mongo会直接把它当作普通嵌套文档存储,不会执行合并逻辑。
错误代码的具体问题
你的初始代码:
match collection .update_many( doc! { "game": game_name}, doc! { "$set": { "memory_blocks": { "$mergeObjects": [ memory_breakpoints_update, "$memory_blocks" ] } }}, None, ) .await
这里第二个参数是单个Document,属于MongoDB的传统更新语法——这种模式下,$set的值只会被当作普通BSON文档处理,完全不会识别其中的$mergeObjects聚合操作符,最终就会出现你看到的:$mergeObjects被原样存在了数据库里。
临时解决方案(查询后本地覆盖)
你采用的先查询文档、本地合并字段再覆盖更新的方式是可行的,但存在竞态风险:如果查询和更新之间有其他操作修改了目标文档,会导致数据覆盖丢失。代码示例:
pub async fn update_memory_breakpoints( db: &State<Database>, game_name: &String, start_address: i32, end_address: i32, read: bool, write: bool, ) -> Result<bool, ....> { let collection: Collection<CodeData> = db.collection(CODE_DATA_COLLECTION); if !read && !write { // 删除逻辑 } // 查询目标文档 let code_data: CodeData = match collection.find_one(doc! { "game": game_name }, None).await { Ok(Some(doc)) => doc, Ok(None) => return ...., Err(_) => return ..., }; // 构建要更新的断点数据 let mut memory_breakpoints_update = HashMap::new(); for address in start_address..=end_address { memory_breakpoints_update.insert(address.to_string(), MemoryBreakpoint { read, write }); } // 本地合并新旧断点(新数据覆盖旧数据) let memory_breakpoints: HashMap<String, MemoryBreakpoint> = code_data .memory_breakpoints .into_iter() .chain(memory_breakpoints_update) .collect(); // 转换为BSON文档 let mut memory_breakpoints_bson = Document::new(); memory_breakpoints.iter().for_each(|(k, v)| { memory_breakpoints_bson.insert(k.to_string(), doc! { "read": v.read, "write": v.write }); }); // 执行覆盖更新 match collection .update_many( doc! { "game": game_name }, doc! { "$set": { "memory_breakpoints": memory_breakpoints_bson }}, None, ) .await { ... } }
最终正确方案(聚合管道更新)
使用数组形式的聚合管道作为update_many的第二个参数,MongoDB会自动解析其中的聚合操作符,正确执行$mergeObjects的合并逻辑。同时还处理了删除场景(当read和write都为false时,用$unset移除对应字段):
let collection: Collection<CodeData> = db.collection(CODE_DATA_COLLECTION); // 构建要合并的断点文档 let mut update_object = Document::new(); for address in start_address..=end_address { update_object.insert(address.to_string(), doc! { "read": read, "write": write }); } // 合并更新的聚合管道 let append = vec![doc! { "$set": { "memory_breakpoints": { "$mergeObjects": [ update_object, "$memory_breakpoints" ] } }}]; // 删除指定断点的聚合管道 let delete_object: Vec<String> = (start_address..=end_address) .map(|address| format!("memory_breakpoints.{}", address)) .collect(); let delete = vec![doc! { "$unset": delete_object }]; // 根据条件选择更新或删除操作 let update_op = if read || write { append } else { delete }; // 执行聚合管道式更新 match collection .update_many(doc! { "game": game_name }, update_op, None) .await
内容的提问来源于stack exchange,提问作者DraganS
相关产品推荐
相关产品推荐

