घोंसले में नेस्टेड सरणी आबाद


111

मैं उदाहरण दस्तावेज़ में "घटकों" को कैसे आबाद कर सकता हूं:

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

यह मेरा JS है जहाँ मुझे Mongoose द्वारा दस्तावेज़ मिला है:

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

क्या यह अब खाली है? आपको क्या परिणाम मिल रहे हैं?
वायर्डपाइरी

2
अगर मैं लिखता ...populate('pages pages.page.components').exec...हूँ तो मुझे वही चीज़ मिलती है जो उदाहरण दस्तावेज़ में दी गई है। कुछ भी नहीं बदला है।
एंटोन शुवालोव

जवाबों:


251

Mongoose 4.5 इसे सपोर्ट करता है

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

और आप एक से अधिक गहरे स्तर से जुड़ सकते हैं


14
अद्भुत - इतना साफ! यह अब आधुनिक और सही उत्तर है। यहां दस्तावेज दिया गया है
isTravis

@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes ने कहा कि यह सुविधा 4.0 के बाद से वहां मौजूद है। आपको गलत क्वेरी मिल सकती है।
त्रिनह होंग न्ह

1
@TrinhHoangNhu मैंने 4.0 रिलीज़ नोट नहीं किया, लेकिन मुझे कोशिश की गई थी। अगर मैं इसे mongoose 4.0 के रूप में चलाता हूं, तो मेरी क्वेरी कुछ भी वापस नहीं करती है, लेकिन जब मैंने 4.5.8 संस्करण में अपग्रेड किया तो यह ठीक काम किया। मेरी क्वेरी: gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
NgaNguyenDuy

1
@NgaNguyenDuy मुझे इस काम को करने के लिए 4.5.8 को अद्यतन करने की भी आवश्यकता थी !!
विनेश

4
मैं उलझन में हूं कि यह कैसे काम करेगा क्योंकि रास्ता pages.$.page.componentनहीं है pages.$.component। यह पेज ऑब्जेक्ट में कैसे दिखता है?
डोमिनिक

111

ये मेरे लिए सही है:

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

प्रलेखन: Model.populate


9
"मॉडल: 'कंपोनेंट" को रखना बहुत महत्वपूर्ण है!
Totty.js

3
लेकिन नहीं होना चाहिए क्योंकि जब मैं रेफरी को परिभाषित करता हूं तो मैं मॉडल को भी परिभाषित करता हूं, यह वास्तव में DRY नहीं है। वैसे भी, धन्यवाद, यह काम करता है;)
Totty.js

दुबले विधि से सावधान रहें। आप कस्टम तरीकों को कॉल करने में सक्षम नहीं होंगे या यहां तक ​​कि लौटी वस्तुओं पर भी बचत कर सकते हैं।
डेनियल Kmak

दुबला () मेरे मामले में आवश्यक नहीं है, लेकिन बाकी खूबसूरती से काम करता है।
जॉन

1
क्या एक और 'स्तर' को गहरा करना संभव है?
तैमुक 22

35

जैसा कि अन्य ने उल्लेख किया है, इसका Mongoose 4समर्थन करता है। यह नोट करना बहुत महत्वपूर्ण है कि आप एक स्तर से भी अधिक गहराई तक पुनरावृत्ति कर सकते हैं, यदि आवश्यक हो - हालांकि यह डॉक्स में नोट नहीं किया गया है:

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

28

आप इस तरह कई नेस्टेड दस्तावेज़ों को आबाद कर सकते हैं।

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

1
सरणी में आबादी वाले रास्ते भी मेरे लिए काम करते हैं:populate: ['components','AnotherRef']
यासीन ओकुमुस

मेरे लिए संस्करण 5.5.7 में, सरणी संकेतन यासीन का उल्लेख नहीं किया गया था, एक स्ट्रिंग में संपर्क करने के बजाय काम करता है। अर्थातpopulate: 'components AnotherRef'
समीह

8

यह सबसे अच्छा समाधान है:

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

अन्य सभी उत्तर अनावश्यक रूप से जटिल हैं, यह स्वीकृत समाधान होना चाहिए।
SeedyROM

और यह उस मामले को हल करता है जहां pageअन्य गैर-आबादी-सक्षम गुण हैं।
सिरा लाम

4

मुझे यह बहुत मददगार पाया कि एक 2 रेफरी स्तर के गहरे संबंध को आबाद करने के लिए हुक से पहले पंख बनाते हैं। आम मॉडल केवल है

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

फिर हुक से पहले पंखों में:

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

    return Promise.resolve(hook)
  }
}

तो कुछ अन्य तरीकों की तुलना में सरल मैं इसे हासिल करने की कोशिश कर रहा था।


जब तक कि एक $ पॉप्युलेट क्वेरी को ओवरराइट करने के बारे में चिंतित न हों, जो उस स्थिति में पारित हो सकती है। उस स्थिति में आपको हुक का उपयोग करना चाहिए। यहाँ नई आबादी वाली वस्तु * /})
ट्रैविस एस

1

मुझे यह प्रश्न एक और प्रश्न के माध्यम से मिला जो कि कीस्टोनजेएस विशिष्ट था लेकिन इसे डुप्लिकेट के रूप में चिह्नित किया गया था। अगर यहां कोई किसी कीस्टोन उत्तर की तलाश में हो सकता है, तो यह है कि मैंने कीस्टोन में अपनी गहरी आबादी की क्वेरी कैसे की।

KeystoneJs [डुप्लिकेट] का उपयोग करते हुए मानगो दो स्तरीय जनसंख्या

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

आप इसे $lookupएकत्रीकरण का उपयोग करके भी कर सकते हैं और शायद सबसे अच्छा तरीका है क्योंकि अब आबाद आबादी मूंगो से विलुप्त हो रही है

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


0

किसी के साथ समस्या है populateऔर यह भी करना चाहता है:

  • सरल पाठ और त्वरित उत्तर (बुलबुले) के साथ चैट करें
  • चैट के लिए 4 डेटाबेस संग्रह: clients, users, rooms, messasges
  • 3 प्रकार के प्रेषकों के लिए एक ही संदेश DB संरचना: बॉट, उपयोगकर्ता और ग्राहक
  • refPathया गतिशील संदर्भ
  • populateके साथ pathऔरmodel विकल्पों के
  • उपयोग findOneAndReplace/replaceOne साथ$exists
  • यदि नया दस्तावेज़ मौजूद नहीं है तो एक नया दस्तावेज़ बनाएँ

संदर्भ

लक्ष्य

  1. डेटाबेस के लिए एक नया सरल पाठ संदेश सहेजें और इसे उपयोगकर्ता या ग्राहक डेटा (2 अलग-अलग मॉडल) के साथ आबाद करें।
  2. डेटाबेस के लिए एक नया क्विक रिप्लेज़ संदेश सहेजें और इसे उपयोगकर्ता या क्लाइंट डेटा के साथ पॉप्युलेट करें।
  3. प्रत्येक संदेश सहेजें अपनी इस प्रकार: clients, usersऔरbot
  4. केवल उन्हीं संदेशों को पॉप्युलेट करें जिनके पास प्रेषक है clientsया usersउसके Mongoose मॉडल के साथ है। _sender टाइप क्लाइंट मॉडल है clients, उपयोगकर्ता के लिए है users

संदेश स्कीमा :

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

उपाय

मेरा सर्वर साइड एपीआई अनुरोध

मेरा कोड

उपयोगिता फ़ंक्शन ( chatUtils.jsफ़ाइल पर ) उस संदेश का प्रकार प्राप्त करने के लिए जिसे आप सहेजना चाहते हैं:

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

संदेश को सहेजने का अनुरोध प्राप्त करने के लिए मेरा सर्वर पक्ष (नोड्स का उपयोग):

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

युक्तियाँ :

डेटाबेस के लिए:

  • हर संदेश एक दस्तावेज ही होता है।
  • उपयोग करने के बजाय refPath, हम getSenderModelउस उपयोग का उपयोग करते हैं जिसका उपयोग किया जाता है populate()। यह बॉट की वजह से है। यह sender.typeहो सकता है: usersअपने डेटाबेस के clientsसाथ , अपने डेटाबेस के साथ और डेटाबेस के botबिना। refPathसही मॉडल संदर्भ जरूरत है, अगर नहीं, Mongooose एक त्रुटि फेंक देते हैं।
  • sender._idObjectIdउपयोगकर्ताओं और ग्राहकों के nullलिए या बॉट के लिए टाइप किया जा सकता है ।

एपीआई अनुरोध तर्क के लिए:

  • हम quickReplyसंदेश को प्रतिस्थापित करते हैं (संदेश DB के पास केवल एक त्वरित रूप से होना चाहिए, लेकिन आप चाहते हैं कि कई सरल पाठ संदेश)। हम या के findOneAndUpdateबजाय का उपयोग करें ।replaceOnefindOneAndReplace
  • हम क्वेरी ऑपरेशन ( findOneAndUpdate) और प्रत्येक के populateसाथ ऑपरेशन को निष्पादित करते हैं callback। यह महत्वपूर्ण है अगर आप यदि उपयोग नहीं जानता है async/await, then(), exec()या callback(err, document)। अधिक जानकारी के लिए पॉपुलेट डॉक देखें
  • हम overwriteविकल्प के साथ और बिना त्वरित उत्तर संदेश को प्रतिस्थापित करते हैं$set क्वेरी ऑपरेटर के ।
  • यदि हमें त्वरित उत्तर नहीं मिलता है, तो हम एक नया बनाते हैं। आपको इसके साथ Mongoose को बताना होगाupsert विकल्प के ।
  • हम बदले हुए संदेश या नए सहेजे गए संदेश के लिए केवल एक बार पॉप्युलेट करते हैं।
  • हम कॉलबैक पर लौटते हैं, जो भी संदेश हमने findOneAndUpdateऔर उसके लिए सहेजा है populate()
  • में populate, हम के साथ एक कस्टम डायनामिक मॉडल संदर्भ बनाते हैं getSenderModel। हम Mongoose डायनामिक संदर्भ का उपयोग कर सकते हैं क्योंकि इसके sender.typeलिए botकोई Mongoose मॉडल नहीं है। हम एक पॉप्युलेटिंग एक्रॉस डेटाबेस का उपयोग करते हैंmodel और pathऑप्टिंस के ।

मैं यहाँ और वहाँ छोटी समस्याओं को हल करने में कई घंटे बिताता हूं और मुझे आशा है कि यह किसी की मदद करेगा! 😃


0

मैं इसके लिए पूरे खूनी दिन संघर्ष करता रहा। ऊपर दिए गए समाधानों में से कोई भी काम नहीं किया। केवल एक चीज जो मेरे मामले में निम्नलिखित उदाहरण के लिए काम करती है:

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

निम्नलिखित करने के लिए है: (लाने के बाद आबादी को मान लेना - लेकिन यह भी काम करता है जब मॉडल वर्ग से आबादी को बुलाओ (बाद में निष्पादित करें)

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

// doc is now populated

दूसरे शब्दों में, बाहरी पथ संपत्ति में पूर्ण पथ समाहित करना होता है। पॉपुलेट गुणों के साथ युग्मित कोई आंशिक रूप से पूर्ण पथ काम नहीं करता था (और मॉडल संपत्ति आवश्यक प्रतीत नहीं होती है; समझ में आता है क्योंकि यह स्कीमा में शामिल है)। मुझे यह पता लगाने के लिए एक पूरे दिन ले लिया! सुनिश्चित नहीं हैं कि अन्य उदाहरण क्यों काम नहीं करते हैं।

(मानगो 5.5.32 का उपयोग करते हुए)


-3

डॉक्स संदर्भ निकालें

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

इसने मेरे लिए काम किया।

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.