Điền mảng lồng nhau trong mongoose


111

Làm cách nào để điền "các thành phần" vào tài liệu mẫu:

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

Đây là JS của tôi, nơi tôi lấy tài liệu của Mongoose:

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

Bây giờ có trống không? Kết quả bạn nhận được là gì?
WiredPrairie

2
nếu tôi viết, ...populate('pages pages.page.components').exec...tôi nhận được điều tương tự như đã nêu trong tài liệu ví dụ. Không có gì thay đổi.
Anton Shuvalov

Câu trả lời:


251

Mongoose 4.5 hỗ trợ điều này

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

Và bạn có thể tham gia nhiều cấp độ sâu


14
Thật tuyệt vời - sạch hơn rất nhiều! Đây là câu trả lời hiện đại và chính xác. Tài liệu ở đây .
isTravis

@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes cho biết rằng tính năng này đã có từ 4.0. Bạn có thể có truy vấn sai.
Trịnh Hoàng Như

1
@TrinhHoangNhu Tôi không có Ghi chú phát hành 4.0, nhưng tôi đã thử. Truy vấn của tôi không trả về bất cứ điều gì nếu tôi chạy nó dưới dạng mongoose 4.0, nhưng nó hoạt động tốt khi tôi nâng cấp lên phiên bản 4.5.8. Truy vấn của tôi: gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
NgaNguyenDuy

1
@NgaNguyenDuy Tôi cũng cần cập nhật lên 4.5.8 để làm việc này !!
vinesh

4
Tôi bối rối làm thế nào điều này sẽ hoạt động vì đường dẫn pages.$.page.componentkhông pages.$.component. Làm thế nào nó biết để tìm trong đối tượng trang?
Dominic

111

Nó ổn với tôi:

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

Tài liệu: Model.populate


9
"Mô hình: 'Thành phần'" thực sự quan trọng cần giữ!
Totty.js

3
Nhưng không nên vì khi tôi xác định tham chiếu, tôi cũng xác định mô hình, điều này không thực sự KHÔ. Dù sao, cảm ơn, nó hoạt động;)
Totty.js

Hãy cẩn thận với phương pháp tinh gọn. Bạn sẽ không thể gọi các phương thức tùy chỉnh hoặc thậm chí lưu trên các đối tượng được trả về.
Daniel Kmak

Lean () không cần thiết trong trường hợp của tôi nhưng phần còn lại hoạt động tốt.
john

1
Có thể điền vào 'cấp độ' khác sâu hơn không?
timhc22

35

Như những người khác đã lưu ý, Mongoose 4hỗ trợ điều này. Điều rất quan trọng cần lưu ý là bạn cũng có thể đệ quy sâu hơn một cấp, nếu cần - mặc dù nó không được ghi chú trong tài liệu:

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

28

Bạn có thể điền nhiều tài liệu lồng nhau như thế này.

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

1
điền các đường dẫn trong mảng cũng làm việc cho tôi:populate: ['components','AnotherRef']
Yasin Okumuş

Đối với tôi trong phiên bản 5.5.7, ký hiệu mảng mà Yasin đã đề cập không hoạt động, thay vào đó, liên hệ trong một chuỗi hoạt động. tức làpopulate: 'components AnotherRef'
Samih A

8

Đó là giải pháp tốt nhất:

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

Tất cả các câu trả lời khác không cần thiết phải phức tạp, đây phải là giải pháp được chấp nhận.
SeedyROM

Và điều này giải quyết trường hợp pagecó các thuộc tính không thể cư trú khác.
Sira Lam,

4

Tôi thấy điều này rất hữu ích khi tạo một bộ lông vũ trước khi móc để điền mối quan hệ sâu cấp 2 tham chiếu. Các mô hình cầy mangut chỉ đơn giản có

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

sau đó trong furjs trước khi hook:

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

    return Promise.resolve(hook)
  }
}

Quá đơn giản so với một số phương pháp khác mà tôi đã cố gắng đạt được điều này.


Trừ khi lo lắng về việc ghi đè truy vấn $ populate có thể đã được chuyển vào. Trong trường hợp đó, bạn nên sử dụng hook.params.query. $ Populate = Object.assign (hook.params.query. $ Populate || {}, {/ * mới điền đối tượng ở đây * /})
Travis S

1

Tôi tìm thấy câu hỏi này thông qua một câu hỏi khác dành riêng cho KeystoneJS nhưng được đánh dấu là trùng lặp. Nếu bất kỳ ai ở đây có thể đang tìm kiếm câu trả lời cho Keystone, thì đây là cách tôi thực hiện truy vấn điền sâu vào Keystone.

Mongoose dân số hai cấp bằng cách sử dụng KeystoneJs [bản sao]

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

Bạn cũng có thể làm điều này bằng cách sử dụng $lookuptổng hợp và có lẽ là cách tốt nhất vì hiện nay dân số đang trở nên tuyệt chủng khỏi 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 hỗ trợ điều này

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

0

Đối với một người có vấn đề với populatevà cũng muốn làm điều này:

  • trò chuyện bằng văn bản đơn giản và trả lời nhanh (bong bóng)
  • 4 bộ sưu tập cơ sở dữ liệu cho chat: clients, users, rooms, messasges.
  • cùng một cấu trúc DB thông báo cho 3 loại người gửi: bot, người dùng & máy khách
  • refPathhoặc tham chiếu động
  • populatevới pathmodelcác tùy chọn
  • sử dụng findOneAndReplace/ replaceOnevới$exists
  • tạo một tài liệu mới nếu tài liệu tìm nạp không tồn tại

BỐI CẢNH

Mục tiêu

  1. Lưu một tin nhắn văn bản đơn giản mới vào cơ sở dữ liệu và điền nó với dữ liệu người dùng hoặc khách hàng (2 kiểu máy khác nhau).
  2. Lưu một tin nhắn quickReplies mới vào cơ sở dữ liệu và đưa nó vào dữ liệu người dùng hoặc khách hàng.
  3. Lưu mỗi tin nhắn gửi loại của nó: clients, users& bot.
  4. Chỉ điền các tin nhắn có người gửi clientshoặc usersbằng các Mô hình Mongoose của nó. _sender type client model is clients, for user is users.

Lược đồ thông báo :

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

GIẢI PHÁP

Yêu cầu API phía máy chủ của tôi

Mã của tôi

Chức năng tiện ích (trên chatUtils.jstệp) để nhận loại thông báo bạn muốn lưu:

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

Phía máy chủ của tôi (sử dụng Nodejs) để nhận yêu cầu lưu thông báo:

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

MẸO :

Đối với cơ sở dữ liệu:

  • Mỗi tin nhắn là một tài liệu chính nó.
  • Thay vì sử dụng refPath, chúng tôi sử dụng tiện ích getSenderModelđược sử dụng trên populate(). Điều này là do bot. Có sender.typethể là: usersvới cơ sở dữ liệu của anh ấy, clientsvới cơ sở dữ liệu của anh ấy và botkhông có cơ sở dữ liệu. Các refPathnhu cầu tham khảo mẫu thật, nếu không, Mongooose ném ra một lỗi.
  • sender._idcó thể là loại ObjectIdcho người dùng và khách hàng, hoặc nullcho bot.

Đối với logic yêu cầu API:

  • Chúng tôi thay thế quickReplythông báo (Message DB chỉ có một quickReply, nhưng có nhiều tin nhắn văn bản đơn giản như bạn muốn). Chúng tôi sử dụng findOneAndUpdatethay thế cho replaceOnehoặc findOneAndReplace.
  • Chúng tôi thực hiện thao tác truy vấn (sự findOneAndUpdate) và populatethao tác với callbackmỗi thao tác. Điều này rất quan trọng nếu bạn không biết nếu sử dụng async/await,then() , exec()hoặc callback(err, document). Để biết thêm thông tin, hãy xem Tài liệu phổ biến .
  • Chúng tôi thay thế tin nhắn trả lời nhanh bằng overwritetùy chọn và không có $settoán tử truy vấn.
  • Nếu không tìm thấy câu trả lời nhanh, chúng tôi sẽ tạo một câu trả lời mới. Bạn phải nói với Mongoose điều này với upserttùy chọn.
  • Chúng tôi chỉ điền một lần cho tin nhắn được thay thế hoặc tin nhắn mới được lưu.
  • Chúng tôi quay lại các cuộc gọi lại, bất kể thông điệp nào chúng tôi đã lưu findOneAndUpdatevà chopopulate() .
  • Trong populate, chúng tôi tạo một tham chiếu Mô hình động tùy chỉnh với getSenderModel. Chúng ta có thể sử dụng tài liệu tham khảo động Mongoose vì sender.typecho botcó không bất kỳ mẫu Mongoose. Chúng tôi sử dụng Cơ sở dữ liệu phổ biến trên toàn bộ với modelpathtùy chọn.

Tôi đã dành rất nhiều giờ để giải quyết các vấn đề nhỏ ở đây và ở đó và tôi hy vọng điều này sẽ giúp ích cho ai đó! 😃


0

Tôi đã đấu tranh với điều này trong cả một ngày đẫm máu. Không có giải pháp nào ở trên hoạt động. Điều duy nhất hoạt động trong trường hợp của tôi cho một ví dụ như sau:

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

là thực hiện như sau: (Giả sử điền sau khi tìm nạp - nhưng cũng hoạt động khi gọi điền từ lớp Model (theo sau bởi executive))

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

// doc is now populated

Nói cách khác, thuộc tính đường dẫn ngoài cùng phải chứa đường dẫn đầy đủ. Không có đường dẫn hoàn chỉnh một phần nào cùng với các thuộc tính điền có vẻ hoạt động (và thuộc tính mô hình dường như không cần thiết; có ý nghĩa vì nó được bao gồm trong lược đồ). Tôi đã mất cả ngày để tìm ra điều này! Không chắc tại sao các ví dụ khác không hoạt động.

(Sử dụng Mongoose 5.5.32)


-3

Xóa tham chiếu tài liệu

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

Điều này đã làm việc cho tôi.

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.