如何在示例文档中填充“组件”:
{
"__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);
});
...populate('pages pages.page.components').exec...
,我会得到与示例文档中所述相同的内容。什么都没有改变。
Mongoose 4.5 支持这个
Project.find(query)
.populate({
path: 'pages',
populate: {
path: 'components',
model: 'Component'
}
})
.exec(function(err, docs) {});
您可以加入多个深度级别。
编辑 2021 年 3 月 17 日:这是库的实现,它在幕后所做的是进行另一个查询为您获取内容,然后加入内存。虽然这项工作我们真的不应该依赖。它将使您的数据库设计看起来像 SQL 表。这是昂贵的操作并且不能很好地扩展。请尝试设计您的文档以减少连接。
这对我行得通:
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);
});
});
正如其他人所指出的,Mongoose 4
支持这一点。需要注意的是,如果需要,您也可以进行更深层次的递归——尽管文档中没有说明这一点,这一点非常重要:
Project.findOne({name: req.query.name})
.populate({
path: 'threads',
populate: {
path: 'messages',
model: 'Message',
populate: {
path: 'user',
model: 'User'
}
}
})
您可以像这样填充多个嵌套文档。
Project.find(query)
.populate({
path: 'pages',
populate: [{
path: 'components',
model: 'Component'
},{
path: 'AnotherRef',
model: 'AnotherRef',
select: 'firstname lastname'
}]
})
.exec(function(err, docs) {});
populate: ['components','AnotherRef']
populate: 'components AnotherRef'
这是最好的解决方案:
Car
.find()
.populate({
path: 'pages.page.components'
})
page
具有其他不可填充属性的情况。
如果您想更深入地填充另一个级别,您需要执行以下操作:
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'
}
}
}]
})
我发现在钩子之前创建一个 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)
}
}
与我试图实现的其他一些方法相比,它是如此简单。
Mongoose 5.4 支持这个
Project.find(query)
.populate({
path: 'pages.page.components',
model: 'Component'
})
这就是你可以制作嵌套人口的方法
Car
.find()
.populate({
path: 'partIds',
model: 'Part',
populate: {
path: 'otherIds',
model: 'Other'
}
})
您也可以使用 $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"
}}
])
我通过另一个特定于 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,
});
});
};
对于遇到 populate
问题并希望这样做的人:
用简单的文字和快速回复(气泡)聊天
用于聊天的 4 个数据库集合:客户端、用户、房间、消息。
3 种类型的发送者的相同消息数据库结构:机器人、用户和客户端
refPath 或动态引用
填充路径和模型选项
将 findOneAndReplace/replaceOne 与 $exists 一起使用
如果获取的文档不存在,则创建一个新文档
语境
目标
将新的简单文本消息保存到数据库并使用用户或客户端数据(2 个不同的模型)填充它。将新的 quickReplies 消息保存到数据库并使用用户或客户端数据填充它。保存每条消息的发件人类型:客户端、用户和机器人。仅使用其 Mongoose 模型填充具有发件人客户端或用户的消息。 _sender 类型客户端模型是客户端,因为用户是用户。
消息架构:
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,而是使用 populate() 上使用的 util getSenderModel。这是因为机器人。 sender.type 可以是:拥有他的数据库的用户、拥有他的数据库的客户端和没有数据库的机器人。 refPath 需要真正的模型引用,如果没有,Mongooose 会抛出错误。
sender._id 对于用户和客户端可以是 ObjectId 类型,对于机器人来说可以是 null。
对于 API 请求逻辑:
我们替换了 quickReply 消息(Message DB 必须只有一个 quickReply,但可以有任意多的简单文本消息)。我们使用 findOneAndUpdate 代替 replaceOne 或 findOneAndReplace。
我们执行查询操作(findOneAndUpdate)和使用每个回调的填充操作。如果您不知道是使用 async/await、then()、exec() 还是 callback(err, document),这一点很重要。有关更多信息,请查看填充文档。
我们将快速回复消息替换为覆盖选项并且没有 $set 查询运算符。
如果我们没有找到快速回复,我们会创建一个新回复。您必须使用 upsert 选项告诉 Mongoose。
我们只填充一次,用于替换的消息或新保存的消息。
我们返回回调,无论我们使用 findOneAndUpdate 和 populate() 保存的消息是什么。
在填充中,我们使用 getSenderModel 创建自定义动态模型引用。我们可以使用 Mongoose 动态引用,因为 bot 的 sender.type 没有任何 Mongoose 模型。我们使用带有模型和路径选项的跨数据库填充。
我花了很多时间在这里和那里解决小问题,我希望这会对某人有所帮助! 😃
我使用以下干净的语法。这个代码块来自我的项目
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
}
})
如果我想从结果中查询和填充
仅通过学生 id const result = await Result.find(filter).populate('student') 仅通过考试类型 const result = await Result.find(filter).populate('exam.type') 通过学生 id 和考试type const result = await Result.find(filter).populate('student Exam.type')
如果您需要更多说明,请在评论中提问
我为此挣扎了整整一天。上述解决方案均无效。在我的情况下,唯一有效的示例如下:
{
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)
用一层嵌套填充和投影回答,你可能会觉得很有趣。
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"
}
]
}
我尝试使用最新版本的猫鼬 5.10
请考虑以下模式定义的用例,然后查看如何填充
const jobsSchema: Schema = new Schema({
employerId:String
}, {strict : false})
jobsSchema.virtual('employer', {
ref: 'Employer',
localField: 'employerId',
foreignField: '_id',
justOne: true
});
另一个模型是工作视图
const jobsViewSchema: Schema = new Schema({
jobId:String
}, {strict : false})
jobsViewSchema.virtual('job', {
ref: 'Jobs',
localField: 'jobId',
foreignField: '_id',
justOne: true
});
现在填充
this.JobViewModel.find(query).populate({
path: 'job',
populate: ['employer', 'Virtual2', 'Virtual3']
})
这将完美地填充整个对象。
删除文档参考
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);
});
pages.$.page.component
而不是pages.$.component
。它如何知道查看页面对象?