如何在DocumentDB中安全更新嵌套字段(父字段可为空且不覆写数据)
我需要对DocumentDB中的复杂文档执行更新操作,要求仅更新变更字段,避免显式设置对象(及嵌套对象)以防覆写现有数据。我用TypeScript写了一个函数,把更新改成用点符号处理所有字段,但遇到了文档字段为null的情况——比如当foo或foo.bar为null时,无法更新foo.bar.baz这类嵌套字段。
集合中的示例文档
{ "foo": { "bar": { "baz": "myValue1" } } }
{ "foo": { "bar": null } }
{ "foo": null }
我的需求是:为任意上述文档设置foo.bar.qux为myValue2,且方案要支持任意层级嵌套字段的更新,无需了解具体schema或数据。
尝试过的无效方法
我尝试在更新查询中使用$ifNull:
db.collection.updateOne( { _id: 1 }, { $set: { "foo": { $ifNull: ["$foo", {}] }, "foo.bar": { $ifNull: ["$foo.bar", {}] }, "foo.bar.qux": "myValue2", } } )
但这个方法行不通:如果foo不为null,同一操作中同时设置foo和foo.bar会报错:Cannot update 'foo.bar.qux' and 'foo.bar.qux' at the same time。
原本聚合管道结合$mergeObjects是合适的方案,但DocumentDB不支持基于聚合管道的更新,请问有哪些可选方案?
分阶段更新,先处理null层级再设置目标字段
拆分更新操作,先把所有为null的父级字段转为空对象,再设置目标字段。以更新foo.bar.qux为例:- 先处理
foo为null的情况:db.collection.updateOne( { _id: 1, foo: null }, { $set: { "foo": {} } } ) - 再处理
foo.bar为null的情况:db.collection.updateOne( { _id: 1, "foo.bar": null }, { $set: { "foo.bar": {} } } ) - 最后设置目标字段:
db.collection.updateOne( { _id: 1 }, { $set: { "foo.bar.qux": "myValue2" } } )
可以用TypeScript写工具函数,自动解析目标字段的点符号路径,生成对应层级的前置更新步骤,无需手动编写每个操作。
- 先处理
条件更新结合
$exists和$type判断
针对每个可能的null层级场景,编写独立的条件更新语句,避免冲突:// 处理foo为null的场景 db.collection.updateOne( { _id: 1, foo: null }, { $set: { "foo": { bar: { qux: "myValue2" } } } } ) // 处理foo存在但foo.bar为null的场景 db.collection.updateOne( { _id: 1, foo: { $exists: true }, "foo.bar": null }, { $set: { "foo.bar": { qux: "myValue2" } } } ) // 处理foo和foo.bar都不为null的场景 db.collection.updateOne( { _id: 1, "foo.bar": { $ne: null } }, { $set: { "foo.bar.qux": "myValue2" } } )同样可以通过TypeScript函数动态生成这些条件更新,根据目标路径自动拆分层级判断逻辑。
预检查文档结构,动态生成更新语句
先查询目标文档的当前结构,判断哪些层级是null,再合并必要的更新操作:- 查询目标文档:
const doc = await db.collection.findOne({ _id: 1 }) - 解析目标路径(如
foo.bar.qux),逐层检查字段是否为null,收集需要转为空对象的层级:- 若
doc.foo为null,记录需要设置"foo": {} - 若
doc.foo存在但doc.foo.bar为null,记录需要设置"foo.bar": {}
- 若
- 合并所有操作并执行更新:
const updateOps = { $set: {} } // 假设检查后需要处理foo和foo.bar updateOps.$set["foo"] = {} updateOps.$set["foo.bar"] = {} updateOps.$set["foo.bar.qux"] = "myValue2" db.collection.updateOne({ _id: 1 }, updateOps)
注意:这种方法存在竞态条件,适合低并发场景使用。
- 查询目标文档:
内容的提问来源于stack exchange,提问作者gshpychka

