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

时间:2019-01-14 11:49:33

标签: python mongodb mongoengine

我将mongoengine与具有Document属性的EmbeddedDocumentListField一起使用。

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和版本“ current”来更新每个child_type文档,并将其更改为版本“ old”。然后,将Child的“新”版本更改为“当前”版本。

这是我到目前为止尝试过的:

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查询,我想对其进行“翻译”以在mongoengine中使用它:

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

NB:我不认为这是重复的,因为我发现的大多数问题都与给定id的特定EmbeddedDocument更新有关。在这里,我要更新每个EmbeddedDocument,而不对父项进行任何过滤。

2 个答案:

答案 0 :(得分:0)

找不到一种方法来处理单个查询,因此我通过逐个更新每个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")

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

答案 1 :(得分:0)

比我之前建议的解决方案更有效的解决方案是通过获取"~/Dropbox/sample/music".replace("~", os.homedir)的链接collection对象来运行原始查询:

Parent

与逐个更新每个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") 实例相比,这是执行方法更快的方法! 对应的是,尽管有一天might be made public,但我正在使用未记录的Child方法。