Mongoose populate嵌套文档

32

我使用Mongoose.js,但无法解决三级层次文档问题。

有两种方法可以解决。

第一种 - 不使用引用。

C = new Schema({
    'title': String,
});

B = new Schema({
    'title': String,
    'c': [C]
});

A = new Schema({
    'title': String,
    'b': [B]
});

我需要展示C记录。如果只知道C的_id,如何填充/查找它呢?

我尝试使用:

A.findOne({'b.c._id': req.params.c_id}, function(err, a){
    console.log(a);
});

但是我不知道如何从返回一个对象,只获取我需要的C对象。

第二,如果使用引用:

C = new Schema({
    'title': String,
});

B = new Schema({
    'title': String,
    'c': [{ type: Schema.Types.ObjectId, ref: 'C' }]
});

A = new Schema({
    'title': String,
    'b': [{ type: Schema.Types.ObjectId, ref: 'B' }]
});

如何填充所有的B、C记录以获取层级结构?

我尝试使用以下代码:

A
.find({})
.populate('b')
.populate('b.c')
.exec(function(err, a){
    a.forEach(function(single_a){
        console.log('- ' + single_a.title);
        single_a.b.forEach(function(single_b){
            console.log('-- ' + single_b.title);
            single_b.c.forEach(function(single_c){
                console.log('--- ' + single_c.title);
            });
        });
    });
});

但是对于single_c.title它会返回未定义。有方法可以填充它吗?

谢谢。


1
现在支持的话,最好选择一个新的被接受的答案。 - JohnnyHK
4个回答

44

从Mongoose 3.6开始,已添加了在查询中递归填充相关文档的功能以下是您可能执行此操作的示例:

 UserList.findById(listId)
         .populate('refUserListItems')
         .exec(function(err, doc){
             UserListItem.populate(doc.refUserListItems, {path:'refSuggestion'},
                   function(err, data){
                        console.log("User List data: %j", doc);
                        cb(null, doc);
                   }
             );     
          });           

在这种情况下,我正在将'refUserListItems'中的id数组与它们引用的文档填充。然后将查询结果传递到另一个填充查询中,该查询引用我还想要填充的原始填充文档的字段-'refSuggestion'。

请注意第二个(内部)填充-这就是魔法发生的地方。您可以继续嵌套这些填充并附加更多文档,直到您构建所需的图为止。

理解这是如何工作需要一点时间,但如果您仔细思考,它就会让人感到合理。


这个可以工作,但是Buu Nguyen提供的深度填充更方便。不需要进行嵌套循环。 - Cédric NICOLAS
我该如何使用Promise来实现这个? - steampowered
@steampowered - 哪一部分?你可以将整个内容包装在一个返回 Promise 的函数中,并在回调函数中解决 .populate 的 Promise。看起来 .populate 也返回一个 Promise,所以你可以在任何一个 .populate 函数上捕获这个 Promise,然后在想要运行它时调用 .then。 - BoxerBucks
1
@BoxerBucks 我决定在一系列的.then()函数中返回.populate()方法返回的Promise,并最终在最后一个.then()中执行它。我认为这样看起来比嵌套n层的populations更清晰。http://stackoverflow.com/a/32018947/404699 - steampowered
如果我需要查找文档而不是单个文档,我是否应该迭代每个文档以填充内部路径,还是有一种方法可以在所有文档上执行此操作? - Harshit Laddha
为什么第二次不需要执行 exec @BoxerBucks? - Daniel Kobe

35

在 Mongoose 4 中,你可以像这样填充多级内容(即使它们在不同的数据库或实例中)

A
.find({})
.populate({
  path: 'b', 
  model: 'B',
  populate: {
    path: 'c',
    model: 'C'
  }
})
.exec(function(err, a){});

10
这应该被标记为正确答案 +1 - Doug Molineux
一个小偏差.. 如果这样做会发生什么? B = new Schema({ 'title': String, 'c': [{ type: Schema.Types.ObjectId, ref: 'C' }], 'cc': [{ type: Schema.Types.ObjectId, ref: 'C' }] }); 如何填充 c 和 cc? - pravin
你可以像这样在链式populate中使用.populate('c').populate('cc')。这是你想要的吗? - James
甚至B.populate([{path: 'c'}, {path: 'cc'}]) - James

32
在Mongoose 4中,您可以跨多个级别填充文档: 假设您有一个跟踪用户好友的“User”模式。
var userSchema = new Schema({
  name: String,
  friends: [{ type: ObjectId, ref: 'User' }]
});

首先,populate() 可以让你获得一个用户的朋友列表。但是如果你还想要用户朋友的朋友怎么办?在这种情况下,你可以指定一个populate选项,告诉Mongoose填充所有用户朋友的friends数组:

User.
  findOne({ name: 'Val' }).
  populate({
    path: 'friends',
    // Get friends of friends - populate the 'friends' array for every friend
    populate: { path: 'friends' }
  });

摘自:http://mongoosejs.com/docs/populate.html#deep-populate


与类似问题中的Aaron给出的答案相同。 - Gianfranco P.
2
FYI - 这个问题已经在3.6版本中得到解决 - https://github.com/LearnBoost/mongoose/wiki/3.6-Release-Notes - BoxerBucks
@BoxerBucks,你能提供一个例子吗?(带有引用的情况) - fusio
@fusio - 我添加了一个答案作为我是如何做到的示例。 - BoxerBucks
我会在@Oleg2tor接受正确答案后立即删除我的回答。 - Gianfranco P.
1
不合理的答案被下投票了,因为现在它已经无效了!@GianPaJ 更新您的答案,而不是删除。 - Marcos Bergamo

5

虽然我有点晚了,但我写了一个Mongoose插件,使深层模型填充变得非常简单。对于您的示例,您可以执行以下操作来填充bc

A.find({}, function (err, docs) {
  A.deepPopulate(docs, 'b.c', cb)
}

您还可以为每个填充路径指定Mongoose填充选项,例如:
A.deepPopulate(docs, 'b.c', {
  b: { 
    select: 'name'
  }
}, cb)

查看文档以获取更多信息,了解插件相关的IT技术。


这看起来非常酷。当它变得更加成熟时,我可能会在生产中使用它。 - steampowered
这个 b: 是不是应该是 c:? - Matthias Max

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接