如果符合条件,则更新 EmbeddedDocumentListField 中的所有 EmbeddedDocuments

Update all EmbeddedDocuments in EmbeddedDocumentListField if it matches criteria

我正在使用 mongoengine 和具有 EmbeddedDocumentListField 属性的 Document

class Child(mongoengine.EmbeddedDocument):
    value = mongoengine.IntField(required=True)
    child_type = mongoengine.StringField(required=True, choices=["type1", "type2", "type3"], unique_with=["version"])
    version = mongoengine.StringField(required=True, choices=["old", "current", "new"])


class Parent(mongoengine.Document):
    children = mongoengine.EmbeddedDocumentListField(Child)

我正在用这种方式填充我的数据库:

def populate():
    # for each child_type
    for child_type in ["type1", "type2", "type3"]:
        for parent_id, value in compute_stuff(child_type):
            # create a new Child embedded document with version "new" and append it to the corresponding Parent                
            parent = Parent.get(parent_id)
            child = Child(value=value, child_type=child_type, version="new")
            parent.children.append(child)
            parent.save()

        update_versions(child_type)

现在,我遇到的问题是我的 update_versions 函数。基本上,我想用当前 child_type 和版本 "current" 更新每个 Child 文档,并将其更改为版本 "old"。然后,通过将版本 "new" 的 Child 更改为版本 "current".

来执行相同的操作

这是我目前尝试过的方法:

def update_versions(child_type):
    # update "current" to "old"        
    Parent.objects(
        children__version="current",
        children__child_type=child_type
    ).update(set__children__S__version="old")
    # update "new" to "current"
    Parent.objects(
        children__version="new",
        children__child_type=child_type
    ).update(set__children__S__version="current")

不幸的是,更新没有正确完成,因为我尝试制作的 child_type 上的过滤器似乎没有完成。这是我在数据库中得到的结果:

> // 1. before first populating -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [ ]
}
> // 2. after first populating of type1 -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "new"
        }
    ]
}
> // 3. after updating versions -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "current"  // <- this is OK
        }
    ]
}
> // 4. after first populating of type2 -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "current"  // <- this is OK
        },
        {
            "value" : 17,
            "child_type": "type2",
            "version": "new"  // <- this is OK
        }
    ]
}
> // 5. after updating versions (only "current" to "old") -> NOT OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "old"  // <- this is NOT OK, expecting to stay "current"
        },
        {
            "value" : 17,
            "child_type": "type2",
            "version": "new"  // <- this is OK
        }
    ]
}

我错过了什么?

编辑:这个查询似乎符合我的要求,但这是一个原始的 Mongo 查询,我想 "translate it" 将它与 mongoengine 一起使用:

db.parent.updateMany(
    {"children.child_type": "type1", "children.version": "current"},
    {"$set": {"children.$[element].version": "old"}},
    {arrayFilters: [{"element.child_type": "type1", "element.version": "current"}]}
)

注意:我不认为这是重复的,因为我发现的大多数问题都是关于更新特定的 EmbeddedDocument,给定它的 ID。在这里,我想更新每个 EmbeddedDocument,而不对父级进行任何过滤。

没有找到让它与单个查询一起工作的方法,所以我通过一个一个地更新每个 Child 实例来做到这一点:

def update_versions(child_type):

    def _update_from_to(current_version, new_version):
        # find all the Parents with a matching Child
        parents_to_update = Parent.objects(
            children__version=current_version,
            children__child_type=child_type
        )
        for parent in parents_to_update:
            # find the matching Child in the children list
            for child in parent.children:
                if (child.version == current_version and
                        child.child_type == child_type):
                    # and update it
                    child.version = new_version
                    break
            # each parent is updated one by one, this is not efficient...
            parent.save()

    _update_from_to("current", "old")
    _update_from_to("new", "current")

编辑:查看我的其他答案以获得更有效(但有点老套)的解决方案

比我之前建议的解决方案更有效的解决方案是 运行 通过获取 Parent 的链接 collection 对象的原始查询:

def update_versions(child_type):

    def _update_from_to(current_version, new_version):
        Parent._get_collection().update_many(
            filter={
                "children.child_type": child_type,
                "children.version": current_version
            },
            update={
                "$set": {"children.$[element].version": new_version}
            },
            array_filters=[{
                "element.child_type": child_type,
                "element.version": current_version
            }],
            upsert=False
        )

    _update_from_to("current", "old")
    _update_from_to("new", "current")

这比一个一个地更新每个 Child 实例要快得多! 对应的是我正在使用未记录的 _get_collection 方法,尽管它有一天 might be made public