MongoEngine - Как эффективно уважать поле списка при преобразовании в 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 являются массивом идентификаторов объектов (ссылок), в отличие от разыменованных 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))

person Matthew Moisen    schedule 07.02.2016    source источник


Ответы (1)


Поскольку вы сохраняете дочерние элементы только как ссылки, вам всегда придется возвращаться на сервер, чтобы разыменовывать их при использовании метода 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 не будет работать с сегментированными коллекциями.

person Steve Rossiter    schedule 08.02.2016