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 虚拟填充和聚合的主要内容,如果未能解决你的问题,请参考以下文章

使用 mongoose 聚合填充值

在 mongoose 的聚合中填充数组的 id

MongoDB 聚合与 Mongoose 虚拟

Mongoose 虚拟填充返回 null

使用 Mongoose 进行虚拟填充

MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用