Популяція вкладеного масиву в мангуста


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

Зараз це порожньо? Які результати ви отримуєте?
WiredPrairie

2
якщо я пишу, ...populate('pages pages.page.components').exec...я отримую те саме, що зазначено в прикладі документа. Нічого не змінюється.
Антон Шувалов

Відповіді:


251

Мангуст 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. Можливо, ви отримали неправильний запит.
Trinh Hoang Nhu

1
@TrinhHoangNhu У мене не було 4.0 Примітка до випуску, але мене судили. Мій запит нічого не повертає, якщо я запускаю його як mongoose 4.0, але він працював нормально, коли я оновив версію 4.5.8. Мій запит: gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
NgaNguyenDuy

1
@NgaNguyenDuy Мені також потрібно було оновити до 4.5.8, щоб зробити цю роботу !!
vinesh

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.pupulate


9
"Модель:" Компонент "насправді важливо зберегти!
Totty.js

3
Але не слід, тому що коли я визначаю ref, я також визначаю модель, це насправді НЕ СУХО. У будь-якому випадку, дякую, працює;)
Totty.js

Будьте обережні з худорлявим методом. Ви не зможете викликати власні методи або навіть зберегти на повернених об'єктах.
Даніель Кмак

lean () в моєму випадку не потрібен, але решта працює чудово.
Джон

1
Чи можна заселити ще один "рівень" глибше?
timhc22

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' },
  ..
}

потім у feathersjs перед гачком:

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

    return Promise.resolve(hook)
  }
}

Настільки простий у порівнянні з деякими іншими методами, я намагався цього досягти.


Якщо ви не переймаєтесь запитом запиту $ populate, який, можливо, було передано. У такому випадку ви повинні використовувати kuk.params.query. $ Populate = Object.assign (mook.params.query. $ Populate || {}, {/ * новий об’єкт заселення тут * /})
Travis S

1

Я знайшов це питання через інше запитання, яке було специфічним для KeystoneJS, але було позначене як дублікат. Якщо хтось тут, можливо, шукатиме відповідь Keystone, ось так я зробив свій глибокий запит у Keystone.

Мангуста на рівні двох рівнів за допомогою 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 типів відправників: бот, користувачі та клієнти
  • refPathабо динамічне посилання
  • populateз pathіmodel варіанти
  • використання findOneAndReplace/ replaceOneз$exists
  • створити новий документ, якщо вилучений документ не існує

КОНТЕКСТ

Мета

  1. Збережіть нове просте текстове повідомлення в базі даних і заповніть його даними користувача або клієнта (2 різних моделі).
  2. Збережіть нове повідомлення quickReplies у базі даних та заповніть його даними користувача чи клієнта.
  3. Зберегти кожне повідомлення його тип відправника: clients, users&bot .
  4. Населяйте лише ті повідомлення, які має відправник clientsабо usersз його мангустовими моделями. _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);

РІШЕННЯ

Мій запит на API сервера

Мій код

Функція утиліти (у 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
};

Мій сервер (використовуючи Nodejs), щоб отримати запит на збереження повідомлення:

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без бази даних. TherefPathПотребує в істинної еталонної моделі, якщо немає, то Mongooose видасть помилку.
  • sender._idможе бути тип ObjectIdдля користувачів і клієнтів, або nullдля бота.

Для логіки запиту API:

  • Ми замінюємо quickReplyповідомлення (у БД повідомлень повинно бути лише одне швидке, але стільки ж простих текстових повідомлень, скільки ви хочете). Ми використовуємо findOneAndUpdateзамість replaceOneабо findOneAndReplace.
  • Ми виконуємо операцію запиту (the findOneAndUpdate) та populateоперацію з callbackкожного з них. Це важливо , якщо ви не знаєте , якщо використовувати async/await, then(), exec()або callback(err, document). Для отримання додаткової інформації дивіться Документ про населення .
  • Ми замінюємо швидке відповідь на overwriteопцію та без $setзапиту оператора.
  • Якщо ми не знайдемо швидкої відповіді, ми створюємо нову. Ви повинні сказати це мангусту з upsertможливістю.
  • Ми заповнюємо лише один раз для заміненого повідомлення або нового збереженого повідомлення.
  • Ми повертаємось до зворотних викликів, незалежно від того, яке повідомлення ми зберегли findOneAndUpdateі для якого populate().
  • У populateнас ми створюємо власну динамічну посилання на модель із getSenderModel. Ми можемо використовувати динамічну довідку Mongoose, тому що sender.typeдля bot"Монуз" немає жодної моделі. Ми використовуємо базу даних по населенню за допомогою modelта pathoptins.

Я витрачаю багато годин на вирішення маленьких проблем тут і там і сподіваюся, що це комусь допоможе! 😃


0

Я боровся з цим цілий кривавий день. Жодне з вищезазначених рішень не працювало. Єдине, що працювало в моєму випадку, наприклад, такий:

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

потрібно зробити наступне: (Припускаючи, що заповнення після вилучення - але також працює при виклику поповнення з класу Model (з наступним exec))

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

// doc is now populated

Іншими словами, властивість зовнішнього шляху повинна містити повний шлях. Жоден частково повний шлях у поєднанні з властивостями популяції, здається, не працює (а властивість моделі, здається, не є необхідною; має сенс, оскільки вона включена в схему). Взяв у мене цілий проклятий день, щоб зрозуміти це! Не впевнений, чому інші приклади не працюють.

(Використовуючи Mongoose 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 та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.