몽구스에서 중첩 배열 채우기


111

예제 문서에서 "구성 요소"를 어떻게 채울 수 있습니까?

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

이것은 Mongoose에서 문서를 얻는 JS입니다.

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

지금 비어 있습니까? 어떤 결과를 얻고 있습니까?
WiredPrairie

2
내가 글을 쓰면 ...populate('pages pages.page.components').exec...예제 문서에 명시된 것과 같은 것을 얻습니다. 변경된 것은 없습니다.
Anton Shuvalov 2013 년

답변:


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. 페이지 개체에서 보이는지 어떻게 알 수 있습니까?
Dominic

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 2014

3
그러나 ref를 정의 할 때 모델도 정의하므로 이것은 실제로 DRY가 아니기 때문입니다. 어쨌든, 감사합니다, 작동합니다;)
Totty.js 2014

린 방법에주의하십시오. 사용자 지정 메서드를 호출하거나 반환 된 개체를 저장할 수 없습니다.
Daniel Kmak 2014

lean ()은 제 경우에는 필요하지 않지만 나머지는 아름답게 작동합니다.
john

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']
Yasin Okumuş

버전 5.5.7에서 Yasin이 언급 한 배열 표기법이 작동하지 않았으며 대신 하나의 문자열로 연락하면 작동합니다. 즉populate: 'components AnotherRef'
Samih A

8

이것이 최고의 솔루션입니다.

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

다른 모든 답변은 불필요하게 복잡하므로 이것이 허용되는 솔루션이어야합니다.
SeedyROM

그리고 이것은 page채울 수없는 다른 속성 이있는 경우를 해결합니다 .
Sira Lam

4

나는 이것이 2 ref 레벨 깊은 관계를 채우기 위해 hook 전에 feathersjs를 만드는 데 매우 도움이된다는 것을 알았습니다. 몽구스 모델은 단순히

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

그런 다음 hook 전에 feathersjs에서 :

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

    return Promise.resolve(hook)
  }
}

이것을 달성하려는 다른 방법에 비해 너무 간단합니다.


전달되었을 수있는 $ populate 쿼리를 덮어 쓰는 것에 대해 걱정하지 않는 한.이 경우 hook.params.query. $ populate = Object.assign (hook.params.query. $ populate || {}, {/ * 여기에 새 채우기 개체 * /})
Travis S

1

이 질문은 KeystoneJS와 관련된 다른 질문을 통해 찾았지만 중복으로 표시되었습니다. 여기에있는 누군가가 Keystone 답변을 찾고 있다면 이것이 제가 Keystone에서 딥 채우기 쿼리를 수행 한 방법입니다.

KeystoneJs를 사용하는 몽구스 2 단계 모집단 [중복]

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

1

Mongoose 5.4는 이것을 지원합니다

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

0

문제가 populate있고이 작업을 수행하려는 사람 :

  • 간단한 텍스트 및 빠른 답장 (거품)으로 채팅
  • 채팅의 4 개 데이터베이스 컬렉션 : clients, users, rooms,messasges .
  • 봇, 사용자 및 클라이언트의 3 가지 유형의 발신자에 대해 동일한 메시지 DB 구조
  • refPath또는 동적 참조
  • populatepathmodel 옵션
  • 사용 findOneAndReplace/ replaceOne함께$exists
  • 가져온 문서가없는 경우 새 문서를 만듭니다.

문맥

  1. 새로운 단순 텍스트 메시지를 데이터베이스에 저장하고 사용자 또는 클라이언트 데이터 (2 가지 다른 모델)로 채 웁니다.
  2. 새 quickReplies 메시지를 데이터베이스에 저장하고 사용자 또는 클라이언트 데이터로 채 웁니다.
  3. 각 메시지의 발신자 유형 저장 : clients, users&bot .
  4. 보낸 사람 clients또는 usersMongoose 모델 이있는 메시지 만 채 웁니다 . _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데이터베이스없이. 은 refPath하지 않을 경우, Mongooose이 오류가 발생, 진정한 모델의 참조를 필요로한다.
  • sender._idObjectId사용자 및 클라이언트 또는 null봇에 대한 유형일 수 있습니다 .

API 요청 로직의 경우 :

  • quickReply메시지를 대체합니다 (메시지 DB에는 빠른 응답이 하나만 있어야하지만 원하는만큼의 간단한 문자 메시지가 있음). 우리는 findOneAndUpdate대신 replaceOne또는findOneAndReplace .
  • 쿼리 작업 ( findOneAndUpdate)과 각각 의 populate작업을 실행합니다 callback. 당신이 사용하는 경우 모르는 경우에 중요하다 async/await, then(), exec()또는 callback(err, document). 자세한 내용은 문서 채우기 를 참조하십시오. .
  • 빠른 답장 메시지를 overwrite옵션과 함께 대체합니다.$set 쿼리 연산자 바꿉니다.
  • 빠른 답장을 찾지 못하면 새 답장을 만듭니다. 몽구스에게upsert 옵션 .
  • 교체 된 메시지 또는 새로 저장된 메시지에 대해 한 번만 채 웁니다.
  • 우리는 우리가 저장 한 메시지가 무엇이든, 콜백으로 되돌아 findOneAndUpdate와 대한 populate().
  • 에서 populate, 우리는있는 사용자 정의 동적 모델의 참조를 생성 getSenderModel. sender.typefor bot에는 Mongoose 모델이 없기 때문에 Mongoose 동적 참조를 사용할 수 있습니다 . 우리는 및 옵틴 과 함께 데이터베이스 전체 채우기를 사용합니다 .modelpath

나는 여기저기서 작은 문제를 해결하는 데 많은 시간을 보냈고 이것이 누군가를 도울 수 있기를 바랍니다! 😃


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);
});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.