簡體   English   中英

Mongoose 虛擬填充和聚合

[英]Mongoose virtual populate and aggregates

我正在嘗試對使用較新的虛擬填充功能(使用 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 並填充相關任務工作正常,例如:

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' 
     }
   }
 ]);

參考Model.aggregate文檔:

參數不會轉換為模型的模式,因為 $project 運算符允許在管道的任何階段重新定義文檔的“形狀”,這可能會使文檔格式不兼容。

從本質上講,這意味着在使用聚合時,不會應用 mongoose 通過使用模式允許的任何魔法。 事實上,您需要直接參考 MongoDB 的聚合文檔(特別是$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
    }
  }
]

虛擬填充不適用於聚合,因為“返回的文檔是普通的 javascript 對象,而不是 mongoose 文檔(因為可以返回任何形狀的文檔)。”(c),文檔中的更多信息 - Mongoose 聚合

mongoose >= 3.6 支持Model.populate()方法。

因此,您可以像這樣將聚合方法的result傳遞給 populate 方法。

var opts = [{ path: 'tasks'}];
const populatedResult = await Project.populate(result, opts);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM