İç içe diziyi firavun faresi ile doldur


111

Örnek belgedeki "bileşenleri" nasıl doldurabilirim:

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

Mongoose'dan belge aldığım JS'm:

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

Şimdi boş mu? Hangi sonuçları alıyorsun?
WiredPrairie

2
Eğer ...populate('pages pages.page.components').exec...yazarsam, örnek belgede belirtildiği gibi aynı şeyi alırım. Hiçbir şey değişmedi.
Anton Shuvalov

Yanıtlar:


251

Mongoose 4.5 bunu destekliyor

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

Ve birden fazla derin seviyeye katılabilirsiniz


14
Şaşırtıcı - çok daha temiz! Bu artık modern ve doğru cevap. Burada belgelenmiştir .
isTravis

@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes , bu özelliğin 4.0'dan beri zaten orada olduğunu söyledi. Yanlış sorgu almış olabilirsiniz.
Trinh Hoang Nhu

1
@TrinhHoangNhu 4.0 Sürüm Notunu almadım ama denedim. Mongoose 4.0 olarak çalıştırırsam sorgum hiçbir şey döndürmüyor, ancak 4.5.8 sürümüne yükselttiğimde iyi çalıştı. Sorgum
NgaNguyenDuy

1
@NgaNguyenDuy Ayrıca bu işi yapabilmek için 4.5.8'e güncellemem gerekiyordu !!
vinesh

4
Yol pages.$.page.componentolmadığı için bunun nasıl çalışacağı konusunda kafam karıştı pages.$.component. Sayfa nesnesine bakmayı nasıl biliyor?
Dominic

111

Bu benim için çalışıyor:

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

Belgeler: Model.populate


9
"Model:" Bileşen "" tutulması gerçekten önemlidir!
Totty.js

3
Ancak olmamalı çünkü ref'i tanımladığımda modeli de tanımlıyorum, bu gerçekten KURU değil. Neyse, teşekkürler, işe yarıyor;)
Totty.js

Yalın yöntemde dikkatli olun. Özel yöntemler çağıramaz ve hatta döndürülen nesnelerden tasarruf edemezsiniz.
Daniel Kmak

Benim durumumda lean () gerekli değil ama gerisi güzel çalışıyor.
John

1
Daha derin başka bir 'seviye' yerleştirmek mümkün mü?
timhc22

35

Diğerlerinin de belirttiği gibi, bunu Mongoose 4destekliyor. Dokümanlarda belirtilmese de, gerekirse bir düzeyden daha derin yineleme yapabileceğinizi unutmamak çok önemlidir:

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

28

Bunun gibi birden çok iç içe geçmiş belgeyi doldurabilirsiniz.

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

1
dizideki yolları doldur benim için de çalıştı:populate: ['components','AnotherRef']
Yasin Okumuş

Benim için 5.5.7 sürümünde, Yasin'in bahsettiği dizi gösterimi işe yaramadı, bunun yerine tek dizede iletişim kurmak işe yarıyor. yanipopulate: 'components AnotherRef'
Samih A

8

En iyi çözüm bu:

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

Diğer tüm cevaplar gereksiz yere karmaşıktır, kabul edilen çözüm bu olmalıdır.
SeedyROM

Ve bu page, diğer doldurulamayan özelliklere sahip olan durumu çözer .
Sira Lam

4

Bunu, 2 ref düzeyli derin bir ilişki kurmak için kancadan önce bir tüy oluştururken çok yararlı buldum. Firavun faresi modellerinde

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

sonra tüylerde kancadan önce:

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

    return Promise.resolve(hook)
  }
}

Bunu başarmaya çalıştığım bazı diğer yöntemlere kıyasla çok basit.


Aktarılmış olabilecek bir $ populate sorgusunun üzerine yazma konusunda endişelenmedikçe. Bu durumda hook.params.query kullanmalısınız. $ Populate = Object.assign (hook.params.query. $ Populate || {}, {/ * yeni nesneyi buraya doldurun * /})
Travis S

1

Bu soruyu KeystoneJS'ye özgü ancak yinelenen olarak işaretlenmiş başka bir soruyla buldum. Burada herhangi biri bir Keystone cevabı arıyor olabilirse, Keystone'daki derin doldurma sorgumu böyle yaptım.

KeystoneJ'leri kullanarak firavun faresi iki seviyeli popülasyon [duplicate]

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

Bunu $lookuptoplama kullanarak da yapabilirsiniz ve muhtemelen şu anda nüfusun en iyi yolu mongo'dan yok olmaktır.

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 bunu destekliyor

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

0

Sorun yaşayan populateve bunu yapmak isteyen biri için:

  • basit metin ve hızlı yanıtlarla (baloncuklar) sohbet edin
  • Sohbet için 4 veritabanı koleksiyonları: clients, users, rooms, messasges.
  • 3 tür gönderici için aynı mesaj DB yapısı: bot, kullanıcılar ve istemciler
  • refPathveya dinamik referans
  • populateile pathve modelseçenekler
  • Kullanım findOneAndReplace/ replaceOneile$exists
  • getirilen belge yoksa yeni bir belge oluşturun

BAĞLAM

Hedef

  1. Veritabanına yeni bir basit metin mesajı kaydedin ve bunu kullanıcı veya müşteri verileriyle doldurun (2 farklı model).
  2. Veritabanına yeni bir QuickReplies mesajı kaydedin ve bunu kullanıcı veya müşteri verileriyle doldurun.
  3. Her mesaj kendi gönderen türünü kaydedin: clients, users&bot .
  4. Yalnızca gönderene sahip olan clientsveya usersonun Mongoose Modelleri olan iletileri doldurun. _sender tipi istemci modelleri, clientskullanıcı içindir users.

Mesaj şeması :

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

ÇÖZÜM

Sunucu tarafı API isteğim

Benim kodum

chatUtils.jsKaydetmek istediğiniz mesajın türünü almak için yardımcı program işlevi ( dosyada):

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

İletiyi kaydetme isteğini almak için sunucu tarafım (Nodejs kullanarak):

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

İPUÇLARI :

Veritabanı için:

  • Her mesaj bir belgenin kendisidir.
  • Bunun yerine kullanmanın refPath, biz util kullanmak getSenderModelkullanılır populate(). Bunun nedeni bot. sender.typeOlabilir: usersOnun veritabanı ile, clientsonun veritabanı ile ve botbir veritabanı olmadan. refPathDeğilse, Mongooose hata atmak, gerçek Modeli referansı gerekiyor.
  • sender._idObjectIdkullanıcılar ve müşteriler nulliçin veya bot için yazılabilir .

API istek mantığı için:

  • quickReplyMesajı değiştiririz (Mesaj DB'nin yalnızca bir hızlı Yanıt olması gerekir, ancak istediğiniz kadar basit metin mesajı gerekir). Veya findOneAndUpdateyerine kullanıyoruz .replaceOnefindOneAndReplace
  • Sorgu işlemini (the findOneAndUpdate) ve populateişlemi callbackher biri ile gerçekleştiriyoruz. Eğer kullanım eğer bilmiyorsanız bu önemlidir async/await, then(), exec()veya callback(err, document). Daha fazla bilgi için Populate Doc'a bakın .
  • Hızlı yanıt mesajını overwriteseçenekle ve $setsorgu operatörü olmadan değiştiriyoruz .
  • Hızlı cevabı bulamazsak yeni bir tane yaratırız. Bunu Mongoose'a upsertseçenekle söylemelisin .
  • Değiştirilen mesaj veya yeni kaydedilmiş mesaj için yalnızca bir kez doldururuz.
  • İle findOneAndUpdateve için kaydettiğimiz mesaj ne olursa olsun geri aramalara geri dönüyoruz populate().
  • Olarak populatebiz ile özel bir dinamik model referans oluşturmak getSenderModel. Çünkü Gelincik dinamik başvuru kullanabilirsiniz sender.typeiçin botherhangi Gelincik Modeli değildir sahiptir. Biz kullanmak Veritabanı karşısında doldurulmaya ile modelve pathoptins.

Burada ve orada küçük sorunları çözmek için çok zaman harcadım ve umarım bu birilerine yardımcı olur! 😃


0

Kanlı bir gün boyunca bununla mücadele ettim. Yukarıdaki çözümlerin hiçbiri işe yaramadı. Aşağıdaki gibi bir örnek için benim durumumda işe yarayan tek şey:

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

aşağıdakileri yapmaktır: (Getirmeden sonra doldurulduğunu varsayarsak - ancak Model sınıfından populate çağrılırken de çalışır (ardından exec gelir))

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

// doc is now populated

Başka bir deyişle, en dıştaki yol özelliği tam yolu içermelidir. Yerleşim özellikleriyle birleştirilen kısmen tamamlanmış hiçbir yol çalışmıyor gibi görünmektedir (ve model özelliği gerekli görünmemektedir; şemaya dahil edildiği için anlamlıdır). Bunu çözmem bütün bir günümü aldı! Diğer örneklerin neden işe yaramadığından emin değilim.

(Mongoose 5.5.32 kullanarak)


-3

Doküman referansını kaldır

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

Bu benim için çalıştı.

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.