2
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'}]}];

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

到目前为止,这是唯一适合我的方法,时间为 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))
4

1 回答 1

2

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

将其与 mongoengine 一起使用的文档非常差,但查看mongoengine 源代码中的单元测试有助于填补空白。

这个答案的帮助下,如果您使用的是 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

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

于 2016-02-08T14:10:09.533 回答