Node.js 在mongoose中填充嵌套数组
如何在示例文档中填充“组件”:Node.js 在mongoose中填充嵌套数组,node.js,mongodb,mongoose,Node.js,Mongodb,Mongoose,如何在示例文档中填充“组件”: { "__v": 1, "_id": "5252875356f64d6d28000001", "pages": [ { "__v": 1, "_id": "5252875a56f64d6d28000002", "page": { "components": [ "525287a01877a68528000001"
{
"__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);
});
这对我很有用:
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);
});
});
文档:删除文档参考
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);
});
Mongoose4.5支持这一点
Project.find(query)
.populate({
path: 'pages',
populate: {
path: 'components',
model: 'Component'
}
})
.exec(function(err, docs) {});
你可以加入多个深层次
《编辑》2021年3月17日:这是图书馆的实现,它在幕后做的是做另一个查询,为你取回东西,然后加入内存。虽然这项工作,但我们真的不应该依靠。它将使您的数据库设计看起来像SQL表。这是一项成本高昂的操作,而且不能很好地扩展。请尝试设计您的文档以减少加入。正如其他人所指出的,
Mongoose 4
支持此功能。重要的是要注意,如果需要,您也可以递归到一个以上的级别,尽管文档中没有说明:
Project.findOne({name: req.query.name})
.populate({
path: 'threads',
populate: {
path: 'messages',
model: 'Message',
populate: {
path: 'user',
model: 'User'
}
}
})
我发现这非常有助于在钩子之前创建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)
}
}
与我试图实现这一点的其他一些方法相比,它非常简单。您可以像这样填充多个嵌套文档
Project.find(query)
.populate({
path: 'pages',
populate: [{
path: 'components',
model: 'Component'
},{
path: 'AnotherRef',
model: 'AnotherRef',
select: 'firstname lastname'
}]
})
.exec(function(err, docs) {});
您也可以使用聚合来实现这一点,而且可能现在最好的填充方式是从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中进行深度填充查询的
对于有
填充问题且也想执行此操作的人:
- 通过简单文本和快速回复聊天(气泡)
- 4个用于聊天的数据库集合:
客户端
,用户
,聊天室
,消息
- 相同的消息数据库结构适用于3种类型的发送者:bot、用户和客户端
refPath
或
使用路径
和模型
选项填充
- 将
findOneAndReplace
/replaceOne
与$exists
- 如果提取的文档不存在,则创建新文档
上下文
目标
将新的简单文本消息保存到数据库中,并使用用户或客户端数据(两种不同型号)填充它
将新的quickReplies消息保存到数据库,并使用用户或客户端数据填充它
将每条消息保存为其发件人类型:客户端
,用户
&机器人
仅使用Mongoose模型填充具有发件人客户端
或用户
的邮件_发送方类型的客户端型号为客户端
,用户类型为用户
李>
消息架构:
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);
}
});
提示:
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);
对于数据库:
- 每条消息本身就是一个文档
- 我们不使用
refPath
,而是使用populate()
上使用的utilgetSenderModel
。这是因为机器人。sender.type
可以是:users
和他的数据库、clients
和bot
没有数据库。refPath
需要真正的模型引用,否则,Mongoose会抛出错误
sender.\u id
可以是用户和客户端的类型ObjectId
,也可以是bot的类型null
对于API请求逻辑:
- 我们将替换
quickReply
消息(消息数据库必须只有一条quickReply,但要有多少条简单的文本消息就有多少条)。我们使用findoneandepdate
而不是replaceOne
或findOneAndReplace
- 我们执行查询操作(
findOneAndUpdate
)和populate
操作,每个操作都有回调
。如果您不知道是使用async/await
、then()
、exec()
还是回调(err,document)
,这一点很重要。有关更多信息,请参阅
- 我们使用
覆盖
选项替换快速回复消息,并且不使用$set
查询运算符
- 如果找不到快速回复,我们将创建一个新的快速回复。您必须使用
upsert
选项告诉Mongoose
- 我们只为替换的邮件或新保存的邮件填充一次
- 我们返回回调,不管我们用
findOneAndUpdate
和populate()
保存的消息是什么
- 在
populate
中,我们使用getSenderModel
创建一个自定义动态模型引用。我们可以使用Mongoose动态引用,因为bot
的sender.type
没有任何Mongoose模型。我们使用带有模型
和路径
选项的
我花了很多时间到处解决一些小问题,我希望这能帮助别人 这是最好的解决方案:
Car
.find()
.populate({
path: 'pages.page.components'
})
我为此挣扎了整整一天。上述解决方案都不起作用。在我的案例中,唯一有效的例子如下:
{
outerProp1: {
nestedProp1: [
{ prop1: x, prop2: y, prop3: ObjectId("....")},
...
],
nestedProp2: [
{ prop1: x, prop2: y, prop3: ObjectId("....")},
...
]
},
...
}
将执行以下操作:(假设在获取后填充-但在从模型类(后跟exec)调用populate时也有效)
换句话说,最外层路径属性必须包含完整路径。没有一个部分完整的路径与填充属性相结合,似乎可以工作(而且模型属性似乎不是必需的;因为它包含在模式中,所以是有意义的)。我花了整整一天的时间才弄明白!不在南
await doc.populate({
path: 'outerProp1.nestedProp1.prop3'
}).execPopulate()
// doc is now populated
Project.find(query)
.populate({
path: 'pages.page.components',
model: 'Component'
})
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
}
})
const result = await Result.find(filter).populate('student')
const result = await Result.find(filter).populate('exam.type')
const result = await Result.find(filter).populate('student exam.type')