在子文档上使用Mongodb findAndModify

时间:2014-07-22 18:16:38

标签: node.js mongodb mongodb-query

我有一个集合,其中包含一系列名为"标签"的子文档。我希望能够在标签级别上findAndModify。目的是锁定每个标签,以便用户界面不允许多个用户一次查看每个标签而不锁定整个文档。目前我只是试图添加一个名为" lock"我的findAndModify查询点击每个标签。

该查询目前正在部分运行。它会找到一个至少有一个标签不含"锁定的文件。领域。但是,它只会将锁定添加到第一个标签,即使第一个标签已被锁定。这是我目前的查询:

db.manual_ncsc_test.findAndModify({query: {$and: [{"labels.x": 201}, {"labels": {$elemMatch: {$exists: {"lock": false}}}}]}, sort:{"labels.lock": -1}, update: {$set: {"labels.$.lock": "TEST!!!!"}}, upsert: false})

$and的第一部分只是为了确保每次都对同一文档进行测试。然后我使用$elemMatch检查每个标签是否有锁。

我尝试了各种各样的尝试来解决我的问题,但我相信它只是在主文档级别排序,而不是标签本身。

您可以看到我正在使用位置运算符将锁添加到匹配的标签。这真让我困惑的是,位置操作符应该只是命中与查询匹配的标签,但它每次都会修改数组中的第一个标签,无论它是否有锁(通过更改{{1的文本来确认) }})。

编辑: 防止任何其他不正确的假设; 这些标签对象在没有输入的情况下提供给用户。用户只有在node.js服务器选择后才与标签进行交互,因此唯一的要求是它是一个标签,并且它尚未被另一个用户检查(已锁定)。在运行findAndModify之前,服务器无法知道有关标签的任何信息,因此无法搜索ID和其他唯一信息。

1 个答案:

答案 0 :(得分:2)

事实上,你似乎对"排序"选项适用于.findAndModify()以及其他一些使用问题。

"排序"的目的将在您的"查询"声明的一部分匹配多个文档。在这种情况下,由于.findAndModify()一次只对一个文档起作用,因此"排序"用于确定"哪个文件"将使用"更新"进行处理或其他操作,例如"删除"。这不会对数组进行排序,实际上没有任何标准操作,除了聚合框架可以在查询中执行的更新和操作的$sort修饰符之外。

匹配特定"索引"的主要问题对你的阵列是你需要比较"多个"数组元素的属性,以确定positional $匹配。 $elemMatch运算符通过"查询"提供条件的每个元素。

对于你所描述的那种东西,你想要的是一个"唯一标识符"在您要更改或编辑的数组元素上。我将使用每个数组元素的ObjectId()值给出示例:

{
    "_id" : ObjectId("53cf1b1c71c5e451279fce3b"),
    "labels" : [
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce37"),
                    "name" : "A"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce38"),
                    "name" : "B"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce39"),
                    "name" : "C"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce3a"),
                    "name" : "D"
            }
    ]
}

To" fetch"当前修饰符的数据,向.findAndModify()发出断言,即元素" _id"必须匹配,并且使用$exists表示该元素没有锁定属性。在"更新"部分您将$set锁定属性:

db.sample.findAndModify({
    "query": {
        "_id": ObjectId("53cf1b1c71c5e451279fce3b"),
        "labels": {
            "$elemMatch": {           
                "_id": ObjectId("53cf1b1c71c5e451279fce38"),
                "locked": { "$exists": false }
            }
        }
    },
    "update": {
        "$set": { "labels.$.locked": true },
    },
    "new": true
});

您可以使用"字段"选择性地投影匹配的字段。选项,这可能不是某些语言中实际驱动程序实现的一部分,但是将数组元素与查询中的参数匹配是微不足道的。但一般来说,该文件现在看起来像这样:

{
    "_id" : ObjectId("53cf1b1c71c5e451279fce3b"),
    "labels" : [
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce37"),
                    "name" : "A"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce38"),
                    "name" : "B",
                    "locked": true
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce39"),
                    "name" : "C"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce3a"),
                    "name" : "D"
            }
    ]
}

正如您所看到的,具有相同参数的任何后续查询都不会实际匹配任何文档,因为所选数组元素实际上具有"锁定"属性。这会阻止其他进程访问相同的"标签"同时进行编辑。

除了获取" lock"这是必需的。这里的$elemMatch确保满足两个条件并选择正确的元素,当然只有一个元素与给定的查询匹配。因此,以下内容将允许另一个进程锁定"数组中最后一个元素编辑:

db.sample.findAndModify({
    "query": {
        "_id": ObjectId("53cf1b1c71c5e451279fce3b"),
        "labels": {
            "$elemMatch": {           
                "_id": ObjectId("53cf1b1c71c5e451279fce3a"),
                "locked": { "$exists": false }
            }
        }
    },
    "update": {
        "$set": { "labels.$.locked": true },
    },
    "new": true
});

现在在数据库和返回的文件中#2;两个"这些元素目前已被锁定":

{
    "_id" : ObjectId("53cf1b1c71c5e451279fce3b"),
    "labels" : [
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce37"),
                    "name" : "A"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce38"),
                    "name" : "B",
                    "locked": true
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce39"),
                    "name" : "C"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce3a"),
                    "name" : "D",
                    "locked": true
            }
    ]
}

当您想要修改文档时,请发出" locked"对于元素是真的,然后是$unset锁定的属性以及发出更改的值:

db.sample.findAndModify({
    "query": {
        "_id": ObjectId("53cf1b1c71c5e451279fce3b"),
        "labels": {
            "$elemMatch": {           
                "_id": ObjectId("53cf1b1c71c5e451279fce38"),
                "locked": { "$exists": true }
            }
        }
    },
    "update": {
        "$unset": { "labels.$.locked": true },
        "$set": { "labels.$.name": "C" }
    },
    "new": true
});

所以这现在修改文档中的数组元素,该进程具有" lock" on并且不影响其他锁定元素。这使得另一个进程发布它自己的更新以便更改该元素:

{
    "_id" : ObjectId("53cf1b1c71c5e451279fce3b"),
    "labels" : [
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce37"),
                    "name" : "A"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce38"),
                    "name" : "C"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce39"),
                    "name" : "C"
            },
            {
                    "_id" : ObjectId("53cf1b1c71c5e451279fce3a"),
                    "name" : "D",
                    "locked": true
            }
    ]
}

这是获取所需原子更新的一般方法,同时确保在编辑过程中没有其他更新语句可以修改相同的元素。

对于奖励积分,还要添加时间戳属性,或者只是将锁定属性设置为当前时间。这允许您定期检查并解锁"任何可能有"过期的编辑"或以其他方式完成并解锁元素。