Füllen Sie das verschachtelte Array in Mungo

111

Wie kann ich "Komponenten" im Beispieldokument ausfüllen:

  {
    "__v": 1,
    "_id": "5252875356f64d6d28000001",
    "pages": [
      {
        "__v": 1,
        "_id": "5252875a56f64d6d28000002",
        "page": {
          "components": [
            "525287a01877a68528000001"
          ]
        }
      }
    ],
    "author": "Book Author",
    "title": "Book Title"
  }

Dies ist mein JS, wo ich ein Dokument von Mongoose bekomme:

  Project.findById(id).populate('pages').exec(function(err, project) {
    res.json(project);
  });
Anton Shuvalov
quelle
Ist es jetzt leer? Welche Ergebnisse erzielen Sie?
WiredPrairie
2
Wenn ich schreibe, ...populate('pages pages.page.components').exec...bekomme ich dasselbe wie im Beispieldokument angegeben. Nichts wird geändert.
Anton

Antworten:

251

Mongoose 4.5 unterstützen dies

Project.find(query)
  .populate({ 
     path: 'pages',
     populate: {
       path: 'components',
       model: 'Component'
     } 
  })
  .exec(function(err, docs) {});

Und Sie können mehr als einer tiefen Ebene beitreten

Trinh Hoang Nhu
quelle
14
Erstaunlich - so viel sauberer! Dies ist jetzt die moderne und richtige Antwort. Hier dokumentiert .
isTravis
@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes sagte, dass diese Funktion bereits seit 4.0 vorhanden ist. Möglicherweise haben Sie eine falsche Abfrage.
Trinh Hoang Nhu
1
@TrinhHoangNhu Ich habe nicht 4.0 Release Note, aber ich wurde versucht. Meine Abfrage gibt nichts zurück, wenn ich sie als Mungo 4.0 ausführe, aber sie hat gut funktioniert, wenn ich auf die Version 4.5.8 aktualisiere. Meine Anfrage: gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
NgaNguyenDuy
1
@NgaNguyenDuy Ich musste auch auf 4.5.8 aktualisieren, damit dies funktioniert !!
Vinesh
4
Ich bin verwirrt, wie das funktionieren würde, da der Pfad pages.$.page.componentnicht ist pages.$.component. Woher weiß es, im Seitenobjekt auszusehen?
Dominic
111

Das ist für mich in Ordnung:

 Project.find(query)
  .lean()
  .populate({ path: 'pages' })
  .exec(function(err, docs) {

    var options = {
      path: 'pages.components',
      model: 'Component'
    };

    if (err) return res.json(500);
    Project.populate(docs, options, function (err, projects) {
      res.json(projects);
    });
  });

Dokumentation: Model.populate

Anton Shuvalov
quelle
9
Das "Modell: 'Komponente'" ist wirklich wichtig zu behalten!
Totty.js
3
Aber sollte nicht, denn wenn ich den Ref definiere, definiere ich auch das Modell, das ist nicht wirklich trocken. Wie auch immer, danke, es funktioniert;)
Totty.js
Seien Sie vorsichtig mit der Lean-Methode. Sie können keine benutzerdefinierten Methoden aufrufen oder zurückgegebene Objekte speichern.
Daniel Kmak
Lean () ist in meinem Fall nicht notwendig, aber der Rest funktioniert wunderbar.
John
1
Ist es möglich, ein anderes "Level" tiefer zu bevölkern?
Timhc22
35

Wie andere angemerkt haben, Mongoose 4unterstützt dies. Es ist sehr wichtig zu beachten, dass Sie bei Bedarf auch tiefer als eine Ebene zurückgreifen können - obwohl dies in den Dokumenten nicht vermerkt ist:

Project.findOne({name: req.query.name})
    .populate({
        path: 'threads',
        populate: {
            path: 'messages', 
            model: 'Message',
            populate: {
                path: 'user',
                model: 'User'
            }
        }
    })
Nikk Wong
quelle
28

Sie können mehrere verschachtelte Dokumente wie folgt füllen.

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});
Shaul Hameed
quelle
1
Das Auffüllen von Pfaden im Array hat auch für mich funktioniert:populate: ['components','AnotherRef']
Yasin Okumuş
Für mich in Version 5.5.7 hat die von Yasin erwähnte Array-Notation nicht funktioniert, stattdessen funktioniert die Kontaktaufnahme in einer Zeichenfolge. dhpopulate: 'components AnotherRef'
Samih A
8

Es ist die beste Lösung:

Car
 .find()
 .populate({
   path: 'pages.page.components'
})
Tuấn Anh Đào
quelle
Alle anderen Antworten sind unnötig kompliziert, dies sollte die akzeptierte Lösung sein.
SeedyROM
Und dies löst den Fall, in dem pageandere nicht bevölkerungsfähige Eigenschaften vorliegen .
Sira Lam
4

Ich fand es sehr hilfreich, vor dem Hook ein Feathersjs zu erstellen, um eine tiefe Beziehung mit 2 Ref-Levels zu füllen. Die Mungomodelle haben einfach

tables = new Schema({
  ..
  tableTypesB: { type: Schema.Types.ObjectId, ref: 'tableTypesB' },
  ..
}
tableTypesB = new Schema({
  ..
  tableType: { type: Schema.Types.ObjectId, ref: 'tableTypes' },
  ..
}

dann in federn vor dem haken:

module.exports = function(options = {}) {
  return function populateTables(hook) {
    hook.params.query.$populate = {
      path: 'tableTypesB',
      populate: { path: 'tableType' }
    }

    return Promise.resolve(hook)
  }
}

So einfach im Vergleich zu einigen anderen Methoden, dass ich versucht habe, dies zu erreichen.

Travis S.
quelle
Es sei denn, Sie befürchten das Überschreiben einer möglicherweise übergebenen $ populate-Abfrage. In diesem Fall sollten Sie hook.params.query verwenden. $ Populate = Object.assign (hook.params.query. $ Populate || {}, {/ * neues Objekt hier füllen * /})
Travis S
1

Ich fand diese Frage durch eine andere Frage, die KeystoneJS-spezifisch war, aber als Duplikat markiert wurde. Wenn jemand hier nach einer Keystone-Antwort sucht, habe ich auf diese Weise meine Deep-Populate-Abfrage in Keystone durchgeführt.

Mungopopulation auf zwei Ebenen mit KeystoneJs [Duplikat]

exports.getStoreWithId = function (req, res) {
    Store.model
        .find()
        .populate({
            path: 'productTags productCategories',
            populate: {
                path: 'tags',
            },
        })
        .where('updateId', req.params.id)
        .exec(function (err, item) {
            if (err) return res.apiError('database error', err);
            // possibly more than one
            res.apiResponse({
                store: item,
            });
        });
};
Leopold Kristjansson
quelle
1

Sie können dies auch mithilfe der $lookupAggregation tun, und der wahrscheinlich beste Weg, wie jetzt zu bevölkern, ist das Aussterben des Mongos

Project.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id) } },
  { "$lookup": {
    "from": Pages.collection.name,
    "let": { "pages": "$pages" },
    "pipeline": [
      { "$match": { "$expr": { "$in": [ "$_id", "$$pages" ] } } },
      { "$lookup": {
        "from": Component.collection.name,
        "let": { "components": "$components" },
        "pipeline": [
          { "$match": { "$expr": { "$in": [ "$_id", "$$components" ] } } },
        ],
        "as": "components"
      }},
    ],
    "as": "pages"
  }}
])
Ashh
quelle
1

Mongoose 5.4 unterstützt dies

Project.find(query)
.populate({
  path: 'pages.page.components',
  model: 'Component'
})
Nadun Liyanage
quelle
0

Für jemanden, der das Problem hat populateund dies auch tun möchte:

  • chatte mit einfachem Text & schnellen Antworten (Blasen)
  • 4 Datenbank Sammlungen Chat: clients, users, rooms, messasges.
  • Gleiche Nachrichten-DB-Struktur für 3 Arten von Absendern: Bot, Benutzer und Clients
  • refPathoder dynamische Referenz
  • populatemit pathund modelOptionen
  • benutze findOneAndReplace/ replaceOnemit$exists
  • Erstellen Sie ein neues Dokument, wenn das abgerufene Dokument nicht vorhanden ist

KONTEXT

Tor

  1. Speichern Sie eine neue einfache Textnachricht in der Datenbank und füllen Sie sie mit den Benutzer- oder Clientdaten (2 verschiedene Modelle).
  2. Speichern Sie eine neue quickReplies-Nachricht in der Datenbank und füllen Sie sie mit den Benutzer- oder Clientdaten.
  3. Speichern Sie jede Nachricht mit ihrem Absendertyp : clients, users& bot.
  4. Füllen Sie nur die Nachrichten aus, die den Absender clientsoder usersseine Mungomodelle haben. _sender Typ Client-Modelle ist clients, für Benutzer ist users.

Nachrichtenschema :

const messageSchema = new Schema({
    room: {
        type: Schema.Types.ObjectId,
        ref: 'rooms',
        required: [true, `Room's id`]
    },
    sender: {
         _id: { type: Schema.Types.Mixed },
        type: {
            type: String,
            enum: ['clients', 'users', 'bot'],
            required: [true, 'Only 3 options: clients, users or bot.']
        }
    },
    timetoken: {
        type: String,
        required: [true, 'It has to be a Nanosecond-precision UTC string']
    },
    data: {
        lang: String,
        // Format samples on https://docs.chatfuel.com/api/json-api/json-api
        type: {
            text: String,
            quickReplies: [
                {
                    text: String,
                    // Blocks' ids.
                    goToBlocks: [String]
                }
            ]
        }
    }

mongoose.model('messages', messageSchema);

LÖSUNG

Meine serverseitige API-Anfrage

Mein Code

Dienstprogrammfunktion (in chatUtils.jsDatei), um den Nachrichtentyp abzurufen, den Sie speichern möchten:

/**
 * We filter what type of message is.
 *
 * @param {Object} message
 * @returns {string} The type of message.
 */
const getMessageType = message => {
    const { type } = message.data;
    const text = 'text',
        quickReplies = 'quickReplies';

    if (type.hasOwnProperty(text)) return text;
    else if (type.hasOwnProperty(quickReplies)) return quickReplies;
};

/**
 * Get the Mongoose's Model of the message's sender. We use
 * the sender type to find the Model.
 *
 * @param {Object} message - The message contains the sender type.
 */
const getSenderModel = message => {
    switch (message.sender.type) {
        case 'clients':
            return 'clients';
        case 'users':
            return 'users';
        default:
            return null;
    }
};

module.exports = {
    getMessageType,
    getSenderModel
};

Meine Serverseite (mit Nodejs), um die Anforderung zum Speichern der Nachricht zu erhalten:

app.post('/api/rooms/:roomId/messages/new', async (req, res) => {
        const { roomId } = req.params;
        const { sender, timetoken, data } = req.body;
        const { uuid, state } = sender;
        const { type } = state;
        const { lang } = data;

        // For more info about message structure, look up Message Schema.
        let message = {
            room: new ObjectId(roomId),
            sender: {
                _id: type === 'bot' ? null : new ObjectId(uuid),
                type
            },
            timetoken,
            data: {
                lang,
                type: {}
            }
        };

        // ==========================================
        //          CONVERT THE MESSAGE
        // ==========================================
        // Convert the request to be able to save on the database.
        switch (getMessageType(req.body)) {
            case 'text':
                message.data.type.text = data.type.text;
                break;
            case 'quickReplies':
                // Save every quick reply from quickReplies[].
                message.data.type.quickReplies = _.map(
                    data.type.quickReplies,
                    quickReply => {
                        const { text, goToBlocks } = quickReply;

                        return {
                            text,
                            goToBlocks
                        };
                    }
                );
                break;
            default:
                break;
        }

        // ==========================================
        //           SAVE THE MESSAGE
        // ==========================================
        /**
         * We save the message on 2 ways:
         * - we replace the message type `quickReplies` (if it already exists on database) with the new one.
         * - else, we save the new message.
         */
        try {
            const options = {
                // If the quickRepy message is found, we replace the whole document.
                overwrite: true,
                // If the quickRepy message isn't found, we create it.
                upsert: true,
                // Update validators validate the update operation against the model's schema.
                runValidators: true,
                // Return the document already updated.
                new: true
            };

            Message.findOneAndUpdate(
                { room: roomId, 'data.type.quickReplies': { $exists: true } },
                message,
                options,
                async (err, newMessage) => {
                    if (err) {
                        throw Error(err);
                    }

                    // Populate the new message already saved on the database.
                    Message.populate(
                        newMessage,
                        {
                            path: 'sender._id',
                            model: getSenderModel(newMessage)
                        },
                        (err, populatedMessage) => {
                            if (err) {
                                throw Error(err);
                            }

                            res.send(populatedMessage);
                        }
                    );
                }
            );
        } catch (err) {
            logger.error(
                `#API Error on saving a new message on the database of roomId=${roomId}. ${err}`,
                { message: req.body }
            );

            // Bad Request
            res.status(400).send(false);
        }
    });

TIPPS :

Für die Datenbank:

  • Jede Nachricht ist ein Dokument.
  • Anstatt zu verwenden refPath, verwenden wir das util getSenderModel, das für verwendet wird populate(). Das liegt am Bot. Das sender.typekann sein: usersmit seiner Datenbank, clientsmit seiner Datenbank und botohne Datenbank. Die refPathbraucht echte Modellreferenz, wenn nicht, werfen Mongooose einen Fehler.
  • sender._idkann ObjectIdfür Benutzer und Clients oder nullfür den Bot eingegeben werden.

Für die API-Anforderungslogik:

  • Wir ersetzen die quickReplyNachricht (Nachrichten-DB muss nur eine schnelle Antwort haben, aber so viele einfache Textnachrichten, wie Sie möchten). Wir verwenden das findOneAndUpdateanstelle von replaceOneoder findOneAndReplace.
  • Wir führen die Abfrageoperation (die findOneAndUpdate) und die populateOperation mit der callbackvon jedem aus. Dies ist wichtig , wenn Sie nicht wissen , ob die Verwendung async/await, then(), exec()oder callback(err, document). Weitere Informationen finden Sie im Populate Doc .
  • Wir ersetzen die Schnellantwortnachricht durch die overwriteOption und ohne $setAbfrageoperator.
  • Wenn wir die schnelle Antwort nicht finden, erstellen wir eine neue. Sie müssen Mongoose dies mit upsertOption mitteilen .
  • Wir füllen nur einmal für die ersetzte Nachricht oder die neu gespeicherte Nachricht.
  • Wir kehren zu Rückrufen zurück, unabhängig von der Nachricht, die wir mit findOneAndUpdateund für die gespeichert haben populate().
  • In populateerstellen wir eine benutzerdefinierte dynamische Modellreferenz mit dem getSenderModel. Wir können die dynamische Mungo-Referenz verwenden, da das sender.typefor botkein Mungo-Modell hat. Wir verwenden eine bevölkerungsübergreifende Datenbank mit modelund pathoptins.

Ich habe viele Stunden damit verbracht, hier und da kleine Probleme zu lösen, und ich hoffe, das wird jemandem helfen! 😃

Guillem
quelle
0

Ich hatte einen ganzen blutigen Tag damit zu kämpfen. Keine der oben genannten Lösungen hat funktioniert. Das einzige, was in meinem Fall für ein Beispiel wie das folgende funktioniert hat:

{
  outerProp1: {
    nestedProp1: [
      { prop1: x, prop2: y, prop3: ObjectId("....")},
      ...
    ],
    nestedProp2: [
      { prop1: x, prop2: y, prop3: ObjectId("....")},
      ...
    ]
  },
  ...
}

Gehen Sie wie folgt vor: (Angenommen, nach dem Abrufen wird gefüllt - funktioniert aber auch, wenn Sie populate aus der Model-Klasse aufrufen (gefolgt von exec).)

await doc.populate({
  path: 'outerProp1.nestedProp1.prop3'
}).execPopulate()

// doc is now populated

Mit anderen Worten, die äußerste Pfadeigenschaft muss den vollständigen Pfad enthalten. Kein teilweise vollständiger Pfad in Verbindung mit Auffüllungseigenschaften schien zu funktionieren (und die Modelleigenschaft scheint nicht erforderlich zu sein; ist sinnvoll, da sie im Schema enthalten ist). Ich habe einen ganzen verdammten Tag gebraucht, um das herauszufinden! Ich bin mir nicht sicher, warum die anderen Beispiele nicht funktionieren.

(Verwenden von Mongoose 5.5.32)

Samuel G.
quelle
-3

Dokumentreferenz entfernen

if (err) {
    return res.json(500);
}
Project.populate(docs, options, function (err, projects) {
    res.json(projects);
});

Das hat bei mir funktioniert.

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});
user4717265
quelle