【问题标题】:Mongoose virtual populate and aggregatesMongoose 虚拟填充和聚合
【发布时间】: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' 
     }
   }
 ]);

【问题讨论】:

    标签: node.js mongodb mongoose mongoose-populate


    【解决方案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);
    

    【讨论】:

      猜你喜欢
      • 2018-03-10
      • 2020-10-14
      • 2019-06-17
      • 2019-04-23
      • 2020-12-08
      • 1970-01-01
      • 2017-10-29
      • 2017-12-06
      • 1970-01-01
      相关资源
      最近更新 更多