152

How can I populate "components" in the example document:

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

This is my JS where I get document by Mongoose:

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

16 回答 16

357

Mongoose 4.5 支持这个

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

您可以加入多个深度级别。

编辑 2021 年 3 月 17 日:这是库的实现,它在幕后所做的是进行另一个查询为您获取内容,然后加入内存。虽然这项工作我们真的不应该依赖。它将使您的数据库设计看起来像 SQL 表。这是昂贵的操作并且不能很好地扩展。请尝试设计您的文档以减少连接。

于 2015-12-23T22:58:35.680 回答
114

这对我行得通:

 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

于 2013-10-07T14:40:25.803 回答
47

正如其他人所指出的,Mongoose 4支持这一点。需要注意的是,如果需要,您也可以进行更深层次的递归——尽管文档中没有说明这一点,这一点非常重要:

Project.findOne({name: req.query.name})
    .populate({
        path: 'threads',
        populate: {
            path: 'messages', 
            model: 'Message',
            populate: {
                path: 'user',
                model: 'User'
            }
        }
    })
于 2016-04-16T04:02:36.477 回答
41

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

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});
于 2018-04-29T08:22:19.940 回答
15

这是最好的解决方案:

Car
 .find()
 .populate({
   path: 'pages.page.components'
})
于 2019-07-31T04:44:22.150 回答
4

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

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

与我试图实现的其他一些方法相比,它是如此简单。

于 2017-09-21T11:59:10.173 回答
3

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

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'
            }
          }
        }]
      })
于 2021-01-30T14:44:41.597 回答
3

Mongoose 5.4 支持这个

Project.find(query)
.populate({
  path: 'pages.page.components',
  model: 'Component'
})
于 2020-05-12T00:57:07.693 回答
2

您也可以使用$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"
  }}
])
于 2018-07-02T05:07:36.497 回答
2

这就是你可以制作嵌套人口的方法

Car
  .find()
  .populate({
    path: 'partIds',
    model: 'Part',
    populate: {
      path: 'otherIds',
      model: 'Other'
    }
  })
于 2021-08-21T10:14:28.963 回答
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,
            });
        });
};
于 2018-11-14T22:32:04.267 回答
1

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

  • 用简单的文字和快速回复(气泡)聊天
  • 4 个聊天数据库集合:clients, users, rooms, messasges.
  • 3 种类型的发送者的相同消息数据库结构:机器人、用户和客户端
  • refPath动态参考
  • populate和选项path_model
  • 使用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);
        }
    });

提示

对于数据库:

  • 每条消息本身就是一个文档。
  • 我们不使用 using ,而是使用 on 上refPath使用的 util 。这是因为机器人。可以是:有他的数据库,有他的数据库,没有数据库。需要真正的模型引用,如果没有,Mongooose 会抛出错误。getSenderModelpopulate()sender.typeusersclientsbotrefPath
  • sender._id可以是ObjectId用户和客户端的类型,也可以null是机器人的类型。

对于 API 请求逻辑:

  • 我们替换quickReply消息(Message DB 必须只有一个 quickReply,但可以有尽可能多的简单文本消息)。我们使用findOneAndUpdate代替replaceOneor findOneAndReplace
  • 我们执行查询操作 (the findOneAndUpdate) 和每一个的populate操作。callback如果您不知道是否使用async/await、或then(),这很重要。有关更多信息,请查看Populate Docexec()callback(err, document)
  • 我们将快速回复消息替换为overwrite选项且不带$set查询运算符。
  • 如果我们没有找到快速回复,我们会创建一个新回复。你必须告诉猫鼬这个upsert选项。
  • 我们只填充一次,用于替换的消息或新保存的消息。
  • 我们返回回调,无论我们使用findOneAndUpdate和保存的消息是什么populate()
  • populate中,我们使用getSenderModel. 我们可以使用 Mongoose 动态引用,因为sender.typeforbot没有任何 Mongoose 模型。我们使用带有和optins的跨数据库填充。modelpath

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

于 2019-03-21T12:44:18.650 回答
1

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

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

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

于 2021-03-16T09:45:23.563 回答
0

我为此挣扎了整整一天。上述解决方案均无效。在我的情况下,唯一有效的示例如下:

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

是执行以下操作:(假设在 fetch 之后填充 - 但在从 Model 类调用 populate 时也有效(随后是 exec))

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

// doc is now populated

换句话说,最外面的路径属性必须包含完整路径。没有部分完整的路径与填充属性相结合似乎有效(并且模型属性似乎不是必需的;因为它包含在架构中,所以很有意义)。我花了整整一天的时间才弄清楚这一点!不知道为什么其他示例不起作用。

(使用猫鼬 5.5.32)

于 2019-11-25T19:25:51.083 回答
0

用一层嵌套填充和投影回答,你可能会觉得很有趣。

https://mongoplayground.net/p/2dpeZWsXR-V

询问:

db.booking.aggregate([
  {
    "$match": {
      id: "61fdfeef678791001880da25"
    }
  },
  {
    $unwind: "$cart"
  },
  {
    "$lookup": {
      "from": "products",
      "localField": "cart.product",
      "foreignField": "id",
      "as": "prod"
    }
  },
  {
    "$unwind": "$prod"
  },
  {
    "$project": {
      id: 1,
      status: 1,
      cart: [
        {
          id: "$cart.id",
          date: "$cart.date",
          timeSlots: "$cart.timeSlots",
          product: {
            id: "$prod.id",
            name: "$prod.name",
            
          }
        }
      ],
      
    }
  }
])

D b:

db={
  "booking": [
    {
      "status": "0",
      "cart": [
        {
          "id": "61fdffc7678791001880da5f",
          "date": "2022-02-05T00:00:00.000Z",
          "product": "61fd7bc5801207001b94d949",
          "timeSlots": [
            {
              "id": "61fd7bf2801207001b94d99c",
              "spots": 1
            }
          ],
          "createdAt": "2022-02-05T04:40:39.155Z",
          "updatedAt": "2022-02-05T04:40:39.155Z"
        }
      ],
      "version": 1,
      "id": "61fdfeef678791001880da25"
    }
  ],
  "products": [
    {
      "meta": {
        "timeZone": "America/New_York"
      },
      "photos": [],
      "name": "Guide To Toronto Canada",
      "timeSlots": [
        {
          "id": "61fd7bcf801207001b94d94d",
          "discount": null,
          "endTime": "2022-02-05T03:01:00.000Z",
          "spots": null,
          "startTime": "2022-02-04T14:00:00.000Z"
        },
        {
          "id": "61fd7bf2801207001b94d99c",
          "discount": null,
          "endTime": "2022-02-04T20:18:00.000Z",
          "spots": 15,
          "startTime": "2022-02-04T19:18:00.000Z"
        },
        
      ],
      "mrp": 20,
      "id": "61fd7bc5801207001b94d949"
    }
  ]
}
于 2022-02-05T13:24:58.327 回答
-4

删除文档参考

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);
});
于 2015-03-26T15:47:58.280 回答