Isi array bersarang di luwak


111

Bagaimana cara mengisi "komponen" di dokumen contoh:

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

Ini adalah JS saya di mana saya mendapatkan dokumen oleh Mongoose:

  Project.findById(id).populate('pages').exec(function(err, project) {
    res.json(project);
  });

Apakah sekarang sudah kosong? Hasil apa yang Anda dapatkan?
WiredPrairie

2
Jika saya menulis ...populate('pages pages.page.components').exec...saya mendapatkan hal yang sama seperti yang tertera pada contoh dokumen. Tidak ada yang berubah.
Anton Shuvalov

Jawaban:


251

Mongoose 4.5 mendukung ini

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

Dan Anda dapat bergabung dengan lebih dari satu level yang dalam


14
Luar biasa - jauh lebih bersih! Ini sekarang adalah jawaban yang modern dan benar. Didokumentasikan di sini .
isTravis

@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes mengatakan bahwa fitur ini sudah ada sejak 4.0. Anda mungkin mendapatkan kueri yang salah.
Trinh Hoang Nhu

1
@TrinhHoangNhu Saya tidak 4.0 Catatan Rilis, tetapi saya mencoba. Permintaan saya tidak mengembalikan apa pun jika saya menjalankannya sebagai mongoose 4.0, tetapi berfungsi dengan baik ketika saya meningkatkan ke versi 4.5.8. Kueri saya: gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
NgaNguyenDuy

1
@NgaNguyenDuy Saya juga perlu memperbarui ke 4.5.8 agar ini berfungsi !!
vinesh

4
Saya bingung bagaimana ini akan bekerja karena jalurnya pages.$.page.componenttidak pages.$.component. Bagaimana cara mengetahui untuk melihat objek halaman?
Dominic

111

Itu berhasil untuk saya:

 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);
    });
  });

Dokumentasi: Model.populate


9
"Model: 'Komponen'" sangat penting untuk dijaga!
Totty.js

3
Tetapi seharusnya tidak karena ketika saya menentukan ref, saya juga menentukan model, ini tidak terlalu KERING. Bagaimanapun, terima kasih, itu berhasil;)
Totty.js

Hati-hati dengan metode lean. Anda tidak akan dapat memanggil metode khusus atau bahkan menyimpan objek yang dikembalikan.
Daniel Kmak

lean () tidak diperlukan dalam kasus saya, tetapi sisanya berfungsi dengan baik.
john

1
Apakah mungkin untuk mengisi 'level' lain lebih dalam?
timhc22

35

Seperti yang telah dicatat orang lain, Mongoose 4dukung ini. Sangat penting untuk dicatat bahwa Anda juga dapat mengulang lebih dalam dari satu level, jika perlu — meskipun tidak dicatat dalam dokumen:

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

28

Anda dapat mengisi beberapa dokumen bersarang seperti ini.

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});

1
mengisi jalur dalam array juga bekerja untuk saya:populate: ['components','AnotherRef']
Yasin Okumuş

Bagi saya di versi 5.5.7, notasi array yang disebutkan Yasin tidak berfungsi, menghubungi dalam satu string malah berfungsi. yaitupopulate: 'components AnotherRef'
Samih A

8

Ini adalah solusi terbaik:

Car
 .find()
 .populate({
   path: 'pages.page.components'
})

Semua jawaban lain tidak perlu rumit, ini harus menjadi solusi yang diterima.
SeedyROM

Dan ini memecahkan kasus di mana pagememiliki properti lain yang tidak dapat diisi.
Sira Lam

4

Saya menemukan ini sangat membantu membuat feathersjs sebelum kait untuk mengisi hubungan dalam tingkat 2 referensi. Model luwak begitu saja

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

lalu di feathersjs before hook:

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

    return Promise.resolve(hook)
  }
}

Sangat sederhana dibandingkan dengan beberapa metode lain yang saya coba untuk mencapai ini.


Kecuali khawatir akan menimpa kueri $ populate yang mungkin telah diteruskan. Dalam hal ini Anda harus menggunakan hook.params.query. $ Populate = Object.assign (hook.params.query. $ Populate || {}, {/ * objek populasi baru di sini * /})
Travis S

1

Saya menemukan pertanyaan ini melalui pertanyaan lain yang khusus KeystoneJS tetapi ditandai sebagai duplikat. Jika ada orang di sini yang mungkin mencari jawaban Keystone, beginilah cara saya melakukan kueri deep populate di Keystone.

Luwak populasi dua tingkat menggunakan 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,
            });
        });
};

1

Anda dapat melakukan ini dengan menggunakan $lookupagregasi juga dan mungkin cara terbaik karena sekarang populasinya mulai punah dari mongo

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"
  }}
])

1

Mongoose 5.4 mendukung ini

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

0

Untuk seseorang yang memiliki masalah dengan populatedan juga ingin melakukan ini:

  • mengobrol dengan teks sederhana & balasan cepat (gelembung)
  • 4 koleksi database untuk chatting: clients, users, rooms, messasges.
  • struktur DB pesan yang sama untuk 3 jenis pengirim: bot, pengguna & klien
  • refPathatau referensi dinamis
  • populatedengan pathdan modelopsi
  • gunakan findOneAndReplace/ replaceOnedengan$exists
  • buat dokumen baru jika dokumen yang diambil tidak ada

KONTEKS

Tujuan

  1. Simpan pesan teks sederhana baru ke database & isi dengan data pengguna atau klien (2 model berbeda).
  2. Simpan pesan quickReplies baru ke database dan isi dengan data pengguna atau klien.
  3. Simpan setiap pesan jenis pengirimnya: clients, users& bot.
  4. Isi hanya pesan yang memiliki pengirim clientsatau usersdengan Model Mongoose-nya. _sender type client models is clients, for user is users.

Skema pesan :

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);

LARUTAN

Permintaan API sisi server saya

Kode saya

Fungsi utilitas (dalam chatUtils.jsfile) untuk mendapatkan jenis pesan yang ingin Anda simpan:

/**
 * 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
};

Sisi server saya (menggunakan Nodejs) untuk mendapatkan permintaan menyimpan pesan:

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);
        }
    });

TIPS :

Untuk database:

  • Setiap pesan adalah dokumen itu sendiri.
  • Alih-alih menggunakan refPath, kami menggunakan util getSenderModelyang digunakan di populate(). Ini karena bot. The sender.typedapat: usersdengan database-nya, clientsdengan database dan bottanpa database. The refPathperlu referensi Model yang benar, jika tidak, Mongooose melempar kesalahan.
  • sender._iddapat berupa tipe ObjectIduntuk pengguna dan klien, atau nulluntuk bot.

Untuk logika permintaan API:

  • Kami mengganti quickReplypesan (Message DB harus memiliki hanya satu quickReply, tetapi sebanyak yang Anda inginkan). Kami menggunakan findOneAndUpdatealih - alih replaceOneatau findOneAndReplace.
  • Kami menjalankan operasi kueri (the findOneAndUpdate) dan populateoperasi dengan callbackmasing-masing. Hal ini penting jika Anda tidak tahu apakah penggunaan async/await, then(), exec()atau callback(err, document). Untuk info lebih lanjut lihat Populate Doc .
  • Kami mengganti pesan balasan cepat dengan overwriteopsi dan tanpa $setoperator kueri.
  • Jika kami tidak menemukan balasan cepat, kami membuat yang baru. Anda harus memberitahu Mongoose ini dengan upsertopsi.
  • Kami hanya mengisi satu kali, untuk pesan yang diganti atau pesan baru yang disimpan.
  • Kami kembali ke callback, apa pun pesan yang telah kami simpan dengan findOneAndUpdatedan untuk populate().
  • Di populate, kami membuat referensi Model dinamis kustom dengan getSenderModel. Kita dapat menggunakan referensi dinamis Mongoose karena sender.typefor bottidak memiliki Model Mongoose. Kami menggunakan Populating Across Database dengan modeldan pathoptins.

Saya telah menghabiskan banyak waktu untuk memecahkan masalah kecil di sana-sini dan saya harap ini akan membantu seseorang! 😃


0

Saya berjuang dengan ini selama satu hari penuh darah. Tidak ada solusi di atas yang berhasil. Satu-satunya hal yang berhasil dalam kasus saya untuk contoh seperti berikut:

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

adalah melakukan hal berikut: (Dengan asumsi mengisi setelah pengambilan - tetapi juga berfungsi saat memanggil populate dari kelas Model (diikuti oleh exec))

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

// doc is now populated

Dengan kata lain, properti jalur terluar harus berisi jalur lengkap. Tidak ada jalur yang sebagian lengkap yang digabungkan dengan properti populate yang tampaknya berfungsi (dan properti model tampaknya tidak diperlukan; masuk akal karena disertakan dalam skema). Butuh waktu satu hari penuh untuk memikirkan ini! Tidak yakin mengapa contoh lainnya tidak berhasil.

(Menggunakan Mongoose 5.5.32)


-3

Hapus referensi dokumen

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

Ini berhasil untuk saya.

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.