我有一个名为groups
的集合,其中包含两个数组:members
和invited
。数组中的每个元素都是用户对象,例如{ _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
数组中的元素数量,这将导致无效。
有没有办法原子地做到这一点?
答案 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
单独不能。所以它实际上只应该用作查询条件中存在的其他条件的“最终过滤器”。