在mongoose中填充嵌套数组

时间:2013-10-07 10:32:14

标签: node.js mongodb mongoose

如何在示例文档中填充“组件”:

  {
    "__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);
  });

14 个答案:

答案 0 :(得分:179)

Mongoose 4.5支持此

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

答案 1 :(得分:104)

这对我有用:

 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

答案 2 :(得分:28)

正如其他人所说,Mongoose 4支持这一点。非常重要的是要注意,如果需要,你也可以递归到一个级别以上 - 尽管在文档中没有注明:

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

答案 3 :(得分:17)

您可以像这样填充多个嵌套文档。

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

答案 4 :(得分:3)

这是最好的解决方案:

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

答案 5 :(得分:2)

我发现这非常有用,在钩子之前创建一个featherjs来填充2 ref级别的深度关系。猫鼬模型只有

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

然后在pearjs之前挂钩:

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

    return Promise.resolve(hook)
  }
}

与我试图实现这一目标的其他方法相比,这么简单。

答案 6 :(得分:1)

您也可以使用$lookup聚合来完成此操作,可能现在最好的方法是从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"
  }}
])

答案 7 :(得分:1)

我通过另一个特定于KeystoneJS的问题找到了这个问题,但被标记为重复。如果这里有人在寻找Keystone的答案,这就是我在Keystone中进行深度填充查询的方式。

Mongoose two level population using KeystoneJs [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,
            });
        });
};

答案 8 :(得分:1)

猫鼬5.4支持此

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

答案 9 :(得分:1)

如果您想更深入地填充另一个级别,您需要执行以下操作:

Airlines.findById(id)
      .populate({
        path: 'flights',
        populate:[
          {
            path: 'planeType',
            model: 'Plane'
          },
          {
          path: 'destination',
          model: 'Location',
          populate: { // deeper
            path: 'state',
            model: 'State',
            populate: { // even deeper
              path: 'region',
              model: 'Region'
            }
          }
        }]
      })

答案 10 :(得分:0)

对于populate有问题并且还想这样做的人:

  • 聊天,提供简单的文字和快速回复(气泡)
  • 用于聊天的4个数据库集合:clientsusersroomsmessasges
  • 用于三种类型的发件人的相同消息数据库结构:机器人,用户和客户端
  • refPathdynamic reference
  • populatepathmodel选项
  • findOneAndReplace / replaceOne$exists一起使用
  • 如果获取的文档不存在,则创建一个新文档

上下文

目标

  1. 将新的简单文本消息保存到数据库中,并使用用户或客户端数据(2种不同的模型)填充它。
  2. 将新的quickReplies消息保存到数据库,并用用户或客户端数据填充它。
  3. 保存每封邮件的发件人类型:clientsusersbot
  4. 仅使用其猫鼬模型填充具有发件人clientsusers的邮件。 _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和没有数据库的botrefPath需要真实的Model参考,否则,Mongooose会抛出错误。
  • sender._id可以为用户和客户端输入ObjectId,或为漫游器是null

对于API请求逻辑:

  • 我们替换了quickReply消息(消息数据库必须只有一个quickReply,但是您可以根据需要添加许多简单的文本消息)。我们使用findOneAndUpdate代替replaceOnefindOneAndReplace
  • 我们使用每个findOneAndUpdate执行查询操作(populate)和callback操作。如果您不知道是否使用async/awaitthen()exec()callback(err, document),则这一点很重要。有关更多信息,请查看Populate Doc
  • 我们用overwrite选项取代了快速回复消息,而没有$set查询运算符。
  • 如果找不到快速答复,我们将创建一个新答复。您必须使用upsert选项告诉Mongoose。
  • 对于替换的消息或新保存的消息,我们仅填充一次。
  • 我们返回回调,无论我们用findOneAndUpdatepopulate()保存的消息是什么。
  • populate中,我们使用getSenderModel创建自定义动态模型引用。我们可以使用Mongoose动态参考,因为sender.type的{​​{1}}没有任何Mongoose模型。我们将Populating Across Databasebotmodel选择素一起使用。

我已经花了很多时间在这里和那里解决小问题,希望对您有所帮助!

答案 11 :(得分: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

换句话说,最外面的路径属性必须包含完整路径。似乎没有部分完整的路径与填充属性结合使用(并且似乎不需要模型属性;因为它包含在架构中,所以很有意义)。花了我整天的时间来解决这个问题!不确定为什么其他示例不起作用。

(使用猫鼬5.5.32)

答案 12 :(得分:0)

我使用以下简洁的语法。这个代码块来自我的项目

const result = await Result.find(filter).populate('student exam.subject')

说明

假设您有两个架构

考试架构

const ExamSchema = new mongoose.Schema({
   ...
   type: String,
   ...
})

结果架构

const resultSchema = new mongoose.Schema({
    ...
    exam: ExamSchema,
    student: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    }
})

如果我想从结果中查询和填充

  1. 仅通过学生证

    const result = await Result.find(filter).populate('student')
    
  2. 仅按考试类型

    const result = await Result.find(filter).populate('exam.type')
    
  3. 按学生证和考试类型

    const result = await Result.find(filter).populate('student exam.type')
    

如果您需要更多说明,请在评论中提问

答案 13 :(得分:-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);
});