Mongoose 虚拟填充和聚合
Posted
技术标签:
【中文标题】Mongoose 虚拟填充和聚合【英文标题】:Mongoose virtual populate and aggregates 【发布时间】:2018-05-20 00:46:56 【问题描述】:我正在尝试对使用较新的虚拟填充功能(使用 Mongoose 4.13、Mongo 3.6)的 Mongoose 模式进行聚合。
假设我有以下(为说明目的而简化)架构:
const ProjectSchema = new mongoose.Schema(
projectId: Number,
description: String
);
ProjectSchema.virtual('tasks',
ref: 'Task',
localField: 'projectId',
foreignField: 'projectId'
justOne: false
]);
const TaskSchema = new mongoose.Schema(
taskId: Number,
projectId: Number
hours: Number
);
const Project = mongoose.model('Project', ProjectSchema);
const Task = mongoose.model('Task', TaskSchema);
使用 .populate() 查询项目和填充相关任务可以正常工作:
Project.find(projectId: <id>).populate('tasks');
但是现在我想按项目汇总任务的小时数(顺便说一下,$sum 部分放在下面......)。无论如何,我只会返回空数组。是不是可以与虚拟填充聚合?
const result = await Project.aggregate([
$match : projectId: <id> ,
$lookup:
from: 'tasks',
localField: 'projectId',
foreignField: 'projectId',
as: 'tasks'
,
$unwind: '$tasks'
]);
【问题讨论】:
【参考方案1】:参考 mongoose 的 Model.aggregate
文档:
参数不会转换为模型的架构,因为 $project 运算符允许在管道的任何阶段重新定义文档的“形状”,这可能会使文档的格式不兼容。
从本质上讲,这意味着在使用聚合时,不会应用 mongoose 通过使用模式所允许的任何东西的魔力。实际上,您需要直接参考 MongoDB 的文档以获取 aggregation(特别是 $lookup)。
const ProjectSchema = new mongoose.Schema(
projectId: Number,
description: String
);
const TaskSchema = new mongoose.Schema(
taskId: Number,
projectId: Number,
hours: Number
);
const Project = connection.model('Project', ProjectSchema);
const Task = connection.model('Task', TaskSchema);
const project1 = new Project( projectId: 1, description: 'Foo');
const project2 = new Project( projectId: 2, description: 'Foo');
const task1 = new Task( task: 1, projectId: 1, hours: 1);
const task2 = new Task( task: 2, projectId: 1, hours: 2);
const task3 = new Task( task: 3, projectId: 2, hours: 1);
Promise.all([
project1.save(),
project2.save(),
task1.save(),
task2.save(),
task3.save()
]).then(() =>
return Project.aggregate([
$match: projectId: 1 ,
$lookup:
from: 'tasks',
localField: 'projectId',
foreignField: 'projectId',
as: 'tasks'
]).exec();
).then((result) =>
console.dir(result, depth: null, color: true );
);
输出以下内容:
[
_id: ObjectID /*ID*/,
projectId: 1,
description: 'Foo',
__v: 0,
tasks: [
_id: ObjectID /*ID*/,
projectId: 1,
hours: 2,
__v: 0
,
_id: ObjectID /*ID*/,
projectId: 1,
hours: 1,
__v: 0
]
]
但你可能会问:“这基本上就是我所拥有的,为什么它会起作用?!”
我不确定您的示例代码是否被复制/粘贴,但在提供的示例中,$unwind
阶段似乎意外包含在管道的第二阶段 ($lookup
) 中。如果您将其添加为第三阶段,如以下代码所示,则输出将如图所示进行更改。
return Project.aggregate([
$match: projectId: 1 ,
$lookup:
from: 'tasks',
localField: 'projectId',
foreignField: 'projectId',
as: 'tasks'
,
$unwind: '$tasks'
]).exec();
输出:
[
_id: ObjectID /*ID*/,
projectId: 1,
description: 'Foo',
__v: 0,
tasks:
_id: ObjectID /*ID*/,
projectId: 1,
hours: 1,
__v: 0
,
_id: ObjectID /*ID*/,
projectId: 1,
description: 'Foo',
__v: 0,
tasks:
_id: ObjectID /*ID*/,
projectId: 1,
hours: 2,
__v: 0
]
【讨论】:
谢谢,非常感谢。我发现了我遇到的问题。我在我的模式上使用自定义定义的集合名称,以使集合更容易区分。 $lookup 聚合中的 'from' 字段指定集合名称,我试图引用与我的情况不同的模式名称。现在一切正常!【参考方案2】:虚拟填充不适用于聚合,因为“返回的文档是纯 javascript 对象,而不是猫鼬文档(因为可以返回任何形状的文档)。”(c),文档中的更多信息 - Mongoose aggregate
【讨论】:
嗯.. 我遇到了这个SO question,其中提到使用 $lookup 和虚拟填充。对此有什么想法吗?【参考方案3】:mongoose >= 3.6 支持Model.populate() 方法。
所以,您可以像这样将聚合方法的result
传递给填充方法。
var opts = [ path: 'tasks'];
const populatedResult = await Project.populate(result, opts);
【讨论】:
以上是关于Mongoose 虚拟填充和聚合的主要内容,如果未能解决你的问题,请参考以下文章