基于两个数组中的元素总数更新MongoDB文档

时间:2015-08-24 09:41:56

标签: mongodb mongodb-query

我有一个名为groups的集合,其中包含两个数组:membersinvited。数组中的每个元素都是用户对象,例如{ _id: '...', name: '...', ...}

该文档还包含一个名为maxMembers的字段,该字段是一个整数,表示该组可以拥有的成员数。我希望在更新文档时强制执行规则,以确保members数组中的元素数量+ invited数组中的元素数量永远不会超过maxMembers计数。

maxMembers永远不会改变,所以如果有必要,我可以在手边阅读(虽然原子上读它会很棒)。

如果这只是一个members数组,我会这样做:

var id = ... // the id of the document
var users = {...} // a user's object
var max = ... // the max amount of users read from `maxMembers` previously

var query = { _id: id, 'members._id': { $ne: user._id } }
query['members.' + max] = { $exists: false }

db.groups.update(query, { $push: { members: user } })

但如果我必须考虑invited数组中的元素数量,这将导致无效。

有没有办法原子地做到这一点?

1 个答案:

答案 0 :(得分:3)

因为更新操作符实际上无法“读取”当前状态下的文档属性值,所以没有任何“好”方法可以原子地处理它。像$inc这样的例外情况只是原子地用“递增/递减”修改当前值,具体取决于赋值中的正值还是负值。

您可以“将”更新中的查询条件与$where逻辑相结合,但这本身就有一些限制。

举个例子:

db.things.insert({ "a": [1,2,3], "maxA": 4 })

定义一个包含三个成员的数组的文档,以及另一个字段“maxA”,它是我们用于数组最大长度的“管理器”。

db.things.update(
    { "$where": "return this.a.length+1 <= this.maxA"},
    { "$push": { "a": 4 } }
)

当然,这允许新的附加元素添加仍然小于或等于最大控制长度,因此文档是匹配的并且进行更新。

然而,如果您在更新后尝试过:

db.things.update(
    { "$where": "return this.a.length+1 <= this.maxA"},
    { "$push": { "a": 5 } }
)

然后由于那超过了受控制的总长度,因此文档不会更新,因为没有匹配。

当然,您可以使用$slice对其进行扩展,以允许最后的新项目达到最大长度:

db.things.update(
    { "$where": "return this.a.length == this.maxA"},
    { "$push": { "a": { "$each": [5], "$slice": -4 } } }
)

但是操作不能“真正”自包含,考虑到您需要先读取文档才能获得与$slice一起使用的“maxA”值。但是,只要该值在读取后没有改变,那么该操作“仍然”是“原子”操作,尽管您确实需要先阅读。

但这种方式是可行的,因为JavaScript评估将控制匹配,即使这些值不能用于更新本身。

但是如果您确实想要沿着这条路走下去,那么请尝试仅将$where与其他查询条件一起使用以匹配预期的文档。通过这种方式,本机查询运算符可以使用“索引”来选择,而$where单独不能。所以它实际上只应该用作查询条件中存在的其他条件的“最终过滤器”。