MongoEngine - 如何在转换为 json 时有效地引用列表字段

MongoEngine - How to deference a List Field efficiently when converting to json

class Parent(document):
    name = StringField()
    children = ListField(ReferenceField('Child'))

class Child(document):
    name = StringField()
    parents = ListField(ReferenceField(Parent))

@app.route('/home/')
def home():
    parents = Parent.objects.all()
    return render_template('home.html', items=parents)

我有两个与上面类似的集合,它们保持着多对多的关系。

在带有 Angular 的模板中,我将 javascript 变量设置为父列表,如下所示:

$scope.items = {{ parents|tojson }};

这导致父数组 chilren 是对象 ID(引用)数组,而不是取消引用的 child 对象:

$scope.items = [{'$oid': '123', 'name': 'foo', 'children': [{'$oid': '456'}]}];

我希望这个 angular 对象包含所有取消引用的子项。有没有有效的方法来做到这一点?

到目前为止,这是唯一适合我的方法,时间复杂度为 O(n^3)。为了清楚起见,我已经最小化了列表理解。多个 obj['_id'] = {'$oid': str(obj['_id']} 是将 ObjectId 转换为可以序列化为 json.

的必要条件
@app.route('/home/')
def home():
    parents = Parent.objects.all()
    temps = []
    for parent in parents:
        p = parent.to_mongo()
        # At this point, the children of parent and p are references only
        p['_id'] = {'$oid': str(p['_id'])
        temp_children = []
        for child in parent.children:
            # Now the child is dereferenced
            c = child.to_mongo()
            c['_id'] = {$oid': str(c['_id'])}
            # Children have links back to Parent. Keep these as references.
            c['parents'] = [{'oid': str(parent_ref)} for parent_ref in c['parents']]
            temp_children.append(c)

        p['children'] = temp_children
        temps.append(parent.to_mongo())

    return render_template('home.html', items=temps)            

以下内容不起作用,但会导致未取消引用子项:

json.loads(json.dumps(accounts))

因为您只将子项存储为引用,所以在使用上面的 QuerySet.all 方法时,您总是必须返回服务器以取消引用它们。 mongodb 的人知道,当使用像 pymongo 这样的驱动程序时,这是一个很大的性能问题,所以他们有一个 aggregation framework 允许您在服务器上进行取消引用。

与 mongoengine 一起使用的文档是 pretty poor but a look at the unit tests in the mongoengine source 帮助填补空白。

的帮助下,如果您使用的是 mongodb 3.2 或更高版本,那么您可以按如下方式实现您想要的目标:

import mongoengine as db
from bson.json_util import dumps

class Parent(db.Document):
    name = db.StringField()
    children = db.ListField(db.ReferenceField('Child'))


class Child(db.Document):
    name = db.StringField()
    parents = db.ListField(db.ReferenceField(Parent))


pipeline = [{"$unwind": "$children"},
            {"$lookup":
                 {"from": "child",
                  "localField": "children",
                  "foreignField": "_id",
                  "as": "children"
                  }
             },
            {"$group": {
                "_id": "$_id",
                "name": {"$first": "$name"},
                "children": {"$push": "$children"}
            }
            }
            ]


@app.route('/home/')
def home():
    parents = []
    for p in Parent.objects.aggregate(*pipeline):
        parents.append(p)
    items= dumps(parents)
    return render_template('home.html', items=items)

那么在你的home.html中你只需要:

$scope.items = {{ items }};

此处管道中的基本步骤是:

  1. 展开子项:为 children 数组中的每个子元素创建一个单独的文档
  2. 查找子项:转到child 集合并根据_id 进行查找,并将结果存储在每个文档的children 字段中。本质上是用匹配的文档替换 ObjectID。
  3. 对结果进行分组:按 _id 并根据分组中的第一项包含 name,并将所有子字段推送到名为 children [=44= 的字段中]

$lookup 仅适用于 mongodb 3.2,如果您需要 运行 mongodb 的早期版本,那么您别无选择,只能制作多个查询。此外,$lookup 不适用于分片集合。