node.js 在猫鼬中填充嵌套数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19222520/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Populate nested array in mongoose
提问by Anton Shuvalov
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:
这是我通过 Mongoose 获取文档的 JS:
Project.findById(id).populate('pages').exec(function(err, project) {
res.json(project);
});
回答by Trinh Hoang Nhu
Mongoose 4.5 support this
Mongoose 4.5 支持这个
Project.find(query)
.populate({
path: 'pages',
populate: {
path: 'components',
model: 'Component'
}
})
.exec(function(err, docs) {});
And you can join more than one deep level
你可以加入多个深层次
回答by Anton Shuvalov
That works for me:
这对我行得通:
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);
});
});
Documentation: Model.populate
回答by nikk wong
As others have noted, Mongoose 4supports this. It is very important to note that you can recurse deeper than one level too, if needed—though it is not noted in the docs:
正如其他人所指出的,Mongoose 4支持这一点。需要注意的是,如果需要,您也可以进行比一级更深的递归——尽管文档中没有指出这一点,这一点非常重要:
Project.findOne({name: req.query.name})
.populate({
path: 'threads',
populate: {
path: 'messages',
model: 'Message',
populate: {
path: 'user',
model: 'User'
}
}
})
回答by Shaul Hameed
You can populate multiple nested documents like this.
您可以像这样填充多个嵌套文档。
Project.find(query)
.populate({
path: 'pages',
populate: [{
path: 'components',
model: 'Component'
},{
path: 'AnotherRef',
model: 'AnotherRef',
select: 'firstname lastname'
}]
})
.exec(function(err, docs) {});
回答by Travis S
I found this very helpful creating a feathersjs before hook to populate a 2 ref level deep relation. The mongoose models simply have
我发现在钩子之前创建一个 Feathersjs 来填充 2 ref 级别的深层关系非常有帮助。猫鼬模型只有
tables = new Schema({
..
tableTypesB: { type: Schema.Types.ObjectId, ref: 'tableTypesB' },
..
}
tableTypesB = new Schema({
..
tableType: { type: Schema.Types.ObjectId, ref: 'tableTypes' },
..
}
then in feathersjs before hook:
然后在钩子之前的feathersjs中:
module.exports = function(options = {}) {
return function populateTables(hook) {
hook.params.query.$populate = {
path: 'tableTypesB',
populate: { path: 'tableType' }
}
return Promise.resolve(hook)
}
}
So simple compared to some other methods I was trying to achieve this.
与我试图实现这一目标的其他一些方法相比,如此简单。
回答by Tu?n Anh ?ào
It's is the best solution:
这是最好的解决方案:
Car
.find()
.populate({
path: 'pages.page.components'
})
回答by Ashh
You can do this using $lookupaggregation as well and probably the best way as now populate is becoming extinct from the mongo
您也可以使用$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"
}}
])
回答by Leopold Kristjansson
I found this question through another question which was KeystoneJS specific but was marked as duplicate. If anyone here might be looking for a Keystone answer, this is how I did my deep populate query in Keystone.
我通过另一个 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,
});
});
};
回答by Guillem
For someone who has the problem with populateand also wants to do this:
对于有问题populate并且也想这样做的人:
- chat with simple text & quick replies (bubbles)
- 4 database collections for chat:
clients,users,rooms,messasges. - same message DB structure for 3 types of senders: bot, users & clients
refPathor dynamic referencepopulatewithpathandmodeloptions- use
findOneAndReplace/replaceOnewith$exists - create a new document if the fetched document doesn't exist
- 用简单的文字和快速回复聊天(气泡)
- 4 个用于聊天的数据库集合:
clients,users,rooms,messasges. - 3 种类型的发件人的相同消息数据库结构:机器人、用户和客户端
refPath或动态参考populate与path和model选项- 使用
findOneAndReplace/replaceOne与$exists - 如果获取的文档不存在,则创建一个新文档
CONTEXT
语境
Goal
目标
- Save a new simple text message to the database & populate it with the user or client data (2 different models).
- Save a new quickReplies message to the database and populate it with the user or client data.
- Save each message its sender type:
clients,users&bot. - Populate only the messages who have the sender
clientsoruserswith its Mongoose Models. _sender type client models isclients, for user isusers.
- 将新的简单文本消息保存到数据库并使用用户或客户端数据(2 种不同的模型)填充它。
- 将新的 quickReplies 消息保存到数据库并用用户或客户端数据填充它。
- 保存每条消息的发件人类型:
clients,users&bot。 - 仅填充具有发件人
clients或其usersMongoose 模型的消息。_sender 类型客户端模型是clients,对于用户是users。
Message schema:
消息架构:
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);
SOLUTION
解决方案
My server side API request
我的服务器端 API 请求
My code
我的代码
Utility function (on chatUtils.jsfile) to get the type of message that you want to save:
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
};
My server side (using Nodejs) to get the request of saving the message:
我的服务器端(使用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);
}
});
TIPs:
提示:
For the database:
对于数据库:
- Every message is a document itself.
- Instead of using
refPath, we use the utilgetSenderModelthat is used onpopulate(). This is because of the bot. Thesender.typecan be:userswith his database,clientswith his database andbotwithout a database. TherefPathneeds true Model reference, if not, Mongooose throw an error. sender._idcan be typeObjectIdfor users and clients, ornullfor the bot.
- 每条消息本身就是一个文档。
refPath我们不使用using ,而是使用在getSenderModel上使用的 utilpopulate()。这是机器人的原因。该sender.type可以是:users他的数据库,clients用他的数据库,bot没有数据库。在refPath需要真正的模型参考,如果没有,Mongooose抛出异常。sender._id可以是ObjectId用户和客户的类型,也null可以是机器人的类型。
For API request logic:
对于 API 请求逻辑:
- We replace the
quickReplymessage (Message DB has to have only one quickReply, but as many simple text messages as you want). We use thefindOneAndUpdateinstead ofreplaceOneorfindOneAndReplace. - We execute the query operation (the
findOneAndUpdate) and thepopulateoperation with thecallbackof each one. This is important if you don't know if useasync/await,then(),exec()orcallback(err, document). For more info look the Populate Doc. - We replace the quick reply message with the
overwriteoption and without$setquery operator. - If we don't find the quick reply, we create a new one. You have to tell to Mongoose this with
upsertoption. - We populate only one time, for the replaced message or the new saved message.
- We return to callbacks, whatever is the message we've saved with
findOneAndUpdateand for thepopulate(). - In
populate, we create a custom dynamic Model reference with thegetSenderModel. We can use the Mongoose dynamic reference because thesender.typeforbothasn't any Mongoose Model. We use a Populating Across Databasewithmodelandpathoptins.
- 我们替换
quickReply消息(消息数据库必须只有一个 quickReply,但只要你想要多少简单的文本消息)。我们使用findOneAndUpdate代替replaceOne或findOneAndReplace。 - 我们执行查询操作 (the
findOneAndUpdate) 和populate操作与callback每个。如果您不知道是否使用、或async/await,这一点很重要。有关更多信息,请查看Populate Doc。then()exec()callback(err, document) - 我们用
overwrite选项替换了快速回复消息,而没有$set查询运算符。 - 如果我们没有找到快速回复,我们会创建一个新回复。你必须用
upsert选项告诉猫鼬。 - 我们只填充一次,用于替换的消息或新保存的消息。
- 我们返回到回调,无论我们
findOneAndUpdate为populate(). - 在 中
populate,我们使用getSenderModel. 我们可以使用 Mongoose 动态引用,因为sender.typeforbot没有任何 Mongoose 模型。我们使用带有和选项的跨数据库填充。modelpath
I've spend a lot of hours solving little problems here and there and I hope this will help someone!
我花了很多时间在这里和那里解决小问题,我希望这会对某人有所帮助!
回答by Samuel G
I struggled with this for a whole bloody day. None of the solutions above worked. The only thing that worked in my case for an example like the following:
我为此挣扎了整整一天。上述解决方案均无效。在我的案例中唯一有效的例子如下:
{
outerProp1: {
nestedProp1: [
{ prop1: x, prop2: y, prop3: ObjectId("....")},
...
],
nestedProp2: [
{ prop1: x, prop2: y, prop3: ObjectId("....")},
...
]
},
...
}
is to do the following: (Assuming populating after fetch - but also works when calling populate from the Model class (followed by exec))
是执行以下操作:(假设在 fetch 之后填充 - 但在从 Model 类调用 populate 时也有效(后跟 exec))
await doc.populate({
path: 'outerProp1.nestedProp1.prop3'
}).execPopulate()
// doc is now populated
In other words, the outermost path property has to contain the full path. No partially complete path coupled with populate properties seemed to work (and the model property doesn't seem to be necessary; makes sense since it is included in the schema). Took me a whole damn day to figure this out! Not sure why the other examples don't work.
换句话说,最外面的路径属性必须包含完整路径。没有部分完整的路径加上填充属性似乎有效(模型属性似乎不是必需的;因为它包含在架构中是有道理的)。我花了整整一天才弄明白这个!不知道为什么其他例子不起作用。
(Using Mongoose 5.5.32)
(使用猫鼬 5.5.32)

