Mongoose: 在嵌入式文档的嵌入式文档上调用填充

创建于 2011-11-08  ·  93评论  ·  资料来源: Automattic/mongoose

嘿,
我有以下模式(简化问题)

用户 = 新架构
登录:字符串

A = 新架构
嵌入:[B]
评论:[评论]

//并遵循嵌入式模式

B = 新架构
评论:[评论]

评论 = 新架构
创建者:
类型:对象标识
参考:'用户'
消息:字符串

现在虽然填充在 A 上工作
例如 A.find().populate('comments.creator')

它不适用于双嵌套嵌入式文档
例如 A.find().populate('emb.comments.creator')

有任何想法吗?

new feature

最有用的评论

我知道这是一个旧线程,但我刚刚创建了一个插件,可以很容易地填充任何深度级别的模型。 我在这里发帖以防有人感兴趣: https://github.com/buunguyen/mongoose-deep-populate。

用法非常简单,例如:

post.deepPopulate('votes.user, comments.user.followers, ...', cb);
Post.deepPopulate(posts, 'votes.user, comments.user.followers', cb);

请查看插件仓库以获取更多信息。

所有93条评论

我目前这样做的方式是填充嵌套项,然后遍历它们以填充子项。 我正计划实施一个填充多个嵌套级别的解决方案,但它变得非常棘手。

嗨,埃内科,

你是怎么做到的?

当您进行第一次填充时,您会返回嵌套项,但是当您遍历它们以填充其子项时,您是否能够保存已填充的子项? 当我尝试这样做时,它永远不会粘住。

一些代码会很棒。

谢谢,
保罗

@eneko没关系,只需要 .toObject() ;)

实际上 Paul,我认为 populate() 适用于返回多个元素的查询,因此无需遍历子对象。 但是,是的,你是对的,如果你想让属性保持不变,你需要调用 .toObject 。

这是一个例子:

// Ideally should be Parent.findOne().populate('children').populate('children.grandchildren').run();

function loadParentWithChildrenAndGrandChildren(parentId) {

  // Load parent without children references (children is array of ObjectId)
  Parent.findOne({ id: parentId }, { children: 0 }, function(err, parent) {
    if (err || !parent) return next(new Error("Parent not found: " + parentId));

    // Load children for this parent, populating grandchildren (no need to load parent reference)
    Children.find({ parent: parent._id }, { parent: 0 })
      .populate('grandchildren', [], { })
      .run(function(err, children) {
        if (err) return next(new Error("Could not load children: " + parentId));

        var result = parent.toObject();
        result.children = children;
        next(null, result);
      });
  });

}

啊,谢谢你的详细回答。 你的猫鼬电话比我的更具体,我可以从你的方法中学习。 碰巧我的特定应用程序需要递归填充,所以现在我可能会手动执行此操作。

我最终会做这样的事情。

var viewWithId_forDisplay = function(id, callback) {
if ( !id || typeof id === 'undefined' || id.toString().match(app_utils.emptyReg) ) {
    throw new Error('Bad user ID.');
}

View.findById(id)
.run(function(err, obj) {
    if ( err || !obj || !obj.subviews || !obj.subviews.length ) return callback(err, obj);

    obj = obj.toObject();

    // recursive subview fetch
    async.map( obj.subviews, viewWithId_forDisplay, function(err, results) {
        obj.subviews = results;

        return callback(err, obj);
    } );

})
}

这现在可以在最新的猫鼬版本中使用吗?

所以我认为这是我遇到的问题。 每个 Comment 对象都有一个嵌入的 User 对象。 评论作为数组嵌入到 Activity 中。 活动查询(如下)不会在每个评论上填充用户对象。

var get_activities = function (callback) {
  var _this = this
  Activity.find({}, [], {sort:{ _id: -1 }})
  .populate('user')
  .populate('comments')
  .run(function (error, activities) {
    callback(error, activities)
  })
}

var User = new Schema({
    id: { type: String, required: true, lowercase: true, index: { unique: true } }
  , email_address: { type: String, required: true, index: true }
  , name: { type: String, required: true }
  , first_name: { type: String,  required: true } 
  , last_name: { type: String, required: true }
  , user_name : { type: String, required: true, lowercase: true, index: { unique: true } }
  , avatar_url: { type: String, required: true }
  , bio: String
  , following: [{ type: Schema.ObjectId, ref: 'User', index: { unique: true } }]
  , followers: [{ type: Schema.ObjectId, ref: 'User', index: { unique: true } }]
})

var Activity = new Schema({
    id: { type: Number, required: true, index: { unique: true } }
  , user: { type: Schema.ObjectId, ref: 'User', required: true }
  , recipients: [{ type: String, index: { unique: true } }]
  , type: String  
  , body: { type: String, required: true }
  , timestamp: { type: Date, required: true }
  , likes: [{ type: Schema.ObjectId, ref: 'User', index: { unique: true } }]
  , comments: [{ type: Schema.ObjectId, ref: 'Comment' }]
})

var Comment = new Schema({
    id: { type: String, required: true, index: { unique: true } }
  , timestamp: { type: Date, required: true }
  , body: { type: String, required: true }
  , user: { type: Schema.ObjectId, ref: 'User', required: true } 
}) 

是否有任何计划让 populate 支持嵌套路径来处理这种情况?

是的,我们想在某个时候。 它可能很快成为性能问题,但我们应该支持它。

对于嵌套对象,我需要手动迭代它们,加载它们,这似乎更像是一个性能问题,并且使我的服务器代码混乱。 所以我 +1 让我的工作更轻松 ;-)

与此同时,我将继续,并通过将页面的各个部分拆分为单独的可加载对象来解决这个问题,这些对象在每次调用时都不需要如此深度嵌套的数据。

我会全力以赴;

Parent.findById(1).populate('child').run(function (err, parent) {
  parent.child.populate('grandchild', function (err) {
    console.log(parent.child.grandchild)
  })
})

像这样在文档上填充可用实际上非常方便。 您可以根据需要继续潜水。

@Qard你读懂了我的想法。

@Qard这就是猫鼬关系的工作方式。 起初我使用该模块一段时间,但最终手动操作。 也许 Mongoose 应该将它集成到核心中,或者至少是它的一部分,比如在数组上调用填充。

https://github.com/LearnBoost/mongoose/issues/601#issuecomment -3258317 这样的东西已经实现了吗?

我想知道同样的事情。 我试图弄清楚如何填充嵌入式文档子引用。 @dbounds我有一个类似的模型。 你有没有解决这个问题: https ://github.com/LearnBoost/mongoose/issues/601#issuecomment -3088564。 我找不到猫鼬关系

此线程中的信息非常有助于理解 .populate() 的当前状态、与孙子嵌套人口相关的当前限制以及如何将 .toObject() 与手动查找结合使用来解决它。

我想知道我们是否可以使用不需要.toObject() 的技术。 在我们填充我们的模型和孙子后,我问 b\c,我们仍然希望有一个 Document 和适当的类型,以便我们可以使用诸如 parent.childarray.id(x) 之类的函数或修改值并调用 .save()。

这里的任何帮助将不胜感激。

注意:我们尝试过使用 parent.set('child.grandchild', value),但这似乎会导致文档完整性出现一些问题,并且我们不再能够从该路径读取值(错误是 Invalid ObjectId尝试读取 parent.child 或 parent.child.grandchild 时)。

相关: http ://stackoverflow.com/questions/11137239/querying-nested-embedded-documents-with-mongoose

@aheckmann对此有什么想法吗?

一直很忙。 我想要它在 3.0 决赛

@aheckmann真棒等不及了。 这将在实际应用中非常有用。 但我确实想对你迄今为止所取得的成就表示敬意。

太棒了! 我将尽快使用此功能。

嘿伙计们,我围绕支持子种群的 Mongoose 编写了自己的层。 它在架构上并不理想,因为它是一个包装器的包装器,但它确实支持我们希望它工作的子填充方式。 如果我清理并释放它,有人有兴趣看到它吗?

绝对有兴趣! 我不确定我是否是进行修补的合适人选,但非常欢迎任何关于如何立即解决此问题的想法!

现在它是一个 hack,尽管从我的角度来看是一个令人愉快的 hack :) 希望它可以用来修补 Mongoose,但目前它只是一个猴子补丁。 我将编写更多测试,记录我的代码,并在下周内回到这里。

这会在 3.0 版中添加吗?

@hackfrag是的

这是在 3.0 中还是仍在添加中。 如果可以,我们可以举个例子吗:)

它不在 3.0 中。 它将在即将发布的次要版本中添加。

我在尝试破解此功能时遇到了令人难以置信的艰难时期。 您对这个版本何时可用有任何估计吗? 如果在不久的将来得到真正的支持,我将放弃我丑陋的代码并在我的应用程序的其他领域工作:)。 我可以补充一下,感谢很棒的图书馆。

Stowns,我的 hack 运行良好,我现在对发布它感觉很好。 我会在 24 小时内回复你 - 需要先测试 Mongoose 3

——约书亚·格罗斯
Christian / Web 开发顾问 / 计算机科学学士候选人,2013 年威斯康星大学麦迪逊分校
414-377-1041 / http://www.joshisgross.com

2012 年 8 月 13 日下午 6:56,stowns [email protected]写道:

我在尝试破解此功能时遇到了令人难以置信的艰难时期。 您对这个版本何时可用有任何估计吗? 如果在不久的将来得到真正的支持,我将放弃我丑陋的代码并在我的应用程序的其他领域工作:)


直接回复此邮件或在 GitHub 上查看。

@JoshuaGross嘿,你能把你的技巧概括一下吗? 没有这个功能,我的代码变得太混乱和丑陋了......

@madhums@stowns ,@ aheckmann@farhanpatel ,@ hackfrag ,@ jsalonen等。我的 hack(仅在 Mongoose 2.7 上测试)在这里: https ://github.com/JoshuaGross/mongoose-subpopulate

希望它有帮助。

哇约书亚! 太感谢了!

我一定会马上去看的!

谢谢@JoshuaGross ,今天晚些时候会检查一下

如果任何文档没有“填充”请求进行搜索的字段,就会发生这种情况? 会有错误吗? 从技术上讲不会有错误,但会显示这个文件吗?

嵌套的 populate() 调用还有效吗? 看起来有一些活动,但我无法确定如何去做。

我有:

List.findById(req.params.id).populate('items').populate('items.user').exec(fn);

用户是深层嵌套的...是否可以填充?

@aheckmann你能对此发表评论吗? 我在生产中使用 mongoose-subpopulate 已经有一段时间了。

mongoose-subpopulate 不适用于 express 3。

我有一个分支,但很忙。 它正在路上。

2012 年 10 月 3 日,星期三,晚上 11:37,Anthony Ettinger
通知@github.com 写道

mongoose-subpopulate 不适用于 express 3。


直接回复此邮件或在 Gi tHub上查看 https://github.com/LearnBoost/mongoose/issues/601#issuecomment -9131991。

亚伦
@aaronheckmann https://twitter.com/#!/aaronheckmann

@chovy你是说猫鼬3吗? 我使用 mongoose-subpopulate 和 express 3。

是的,我的意思是猫鼬 3。

@aheckmann - 期待看到你的分支!

我不知道如何使用 mongoose-subpopulate

对此功能 +1

为这个+1

有一种方法可以填充数组的子数组吗?

var notifications = new Schema({
    _id             : { type : ObjectId }
    , from          : { type : ObjectId, ref: 'user' }
    , status                : { type : Number, default : 1 }
    , created_at            : { type : Date }
    , updated_at            : { type : Date }
});

var applications = new Schema({
    _id                     : { type : ObjectId, required : true, ref : 'application' }
    , notifications                 : [notifications]
});

var schema = new Schema({
    name                        : { type : String, required : true }
    , email                     : { type : String }
    , applications                      : [applications]
    , created_at                        : { type : Date }
    , updated_at                        : { type : Date }
});

var User = module.exports = mongoose.model('user', schema);

我想在这里完成的是获取通知发件人的姓名

// works like a charm
User.findOne({_id:'me'}).populate('applications');

// doesn't work
User.findOne({_id:'me'}).populate('applications.notifications.from');

或者,如果有另一种方法可以进行另一个查询以手动获取所有用户名?

您需要像处理通知一样制作“来自”数组。 最好的办法是为回调中的每个通知获取用户。

是的,我只是手动进行填充,如果填充也适用于深度引用,公牛会很酷......

+1,希望我能深究并看到它在里面,而我只是做得不对。 :(

+1,此功能的任何时间线估计? 这将非常有帮助。

@winduptoy看起来像 3.6

也在这里+1。

更重要的是,我们至少应该能够自己填充子对象,而不必先在父对象上调用 toObject()。 我可以理解在 set 调用之后保持字段类型一致的愿望,但是 mongoose 已经用 populate 函数打破了这个规则。 我们应该能够为完整对象设置一个引用字段,而无需 mongoose 将其更改为仅 mongo id。

除了这个混乱,爱猫鼬。

我非常期待这个功能。

+1 我也需要这个功能...

为这个未来 +1

+1 - 期待这个

+1 这太棒了

+1 这是软件问题,但如果你放另一个符号会有所帮助,例如

User.findOne({_id:'me'}).populate('applications$notifications.from');
// Arrays $
// Simple document point "."

所以不要避免问题......不是吗?

+2

+1 这会很有帮助。

+1 你这样做真是太好了。

请在 2013 年发布该功能

+1 我现在需要这个,它将非常有用。

对于那些现在需要这个的人,你可能想要查看 mongoose-subpopulate。 我已经使用了几个月了: https ://github.com/JoshuaGross/mongoose-subpopulate(我是维护者)

这是 3.6 中的填充计划: https ://github.com/LearnBoost/mongoose/pull/1292

+1 我们需要这个。 希望很快发布,(因为问题已经在一年前开始了)

我认为通过全民投票,赢得了这个提案!

@aheckmann 非常感谢!

也很喜欢lean() ,所以到目前为止这看起来真的很棒!

为 3.6 感到兴奋,这是一个很好的修复,谢谢!

谢谢先生!

2013 年 3 月 5 日下午 5:26,Andy Burke通知@github.com 写道:

为 3.6 感到兴奋,这是一个很好的修复,谢谢!


直接回复此邮件或在 GitHub 上查看。

@aheckmann太好了,谢谢!

谢谢!!! 做得好!!!

棒极了! 我相信这对我的案子有帮助。 在这种情况下我应该怎么做:

var UserSchema, WineRating, WineSchema, mongoose;

UserSchema = new mongoose.Schema({
  wine_ratings: {
    type: [
      {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'WineRating'
      }
    ]
  }
});
mongoose.model("User", UserSchema);

WineRating = new mongoose.Schema({
  wine: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Wine'
  }
});
mongoose.model("WineRating", WineRating, 'wine_ratings');


WineSchema = new mongoose.Schema({
  name: String
});
mongoose.model("Wine", WineSchema);

mongoose.model("User").findById(user._id).populate('wine_ratings.wine').exec(function(err, user) {});
/*
gets exception: 
TypeError: Cannot call method 'path' of undefined
    at search (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1830:28)
    at search (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1849:22)
    at Function._getSchema (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1856:5)
    at populate (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1594:22)
    at Function.Model.populate (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1573:5)
    at Query.findOne (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/query.js:1633:11)
    at exports.tick (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/utils.js:393:16)
*/

有什么不对? 我在“3.6.0rc0”

我有同样的问题: https ://github.com/LearnBoost/mongoose/issues/1377

@flockonus @vovan22我遇到了同样的问题。 就是我解决它的方法。

我试图让post.populate("comments comments._creator")在我们的项目中工作,并通过以下调整取得了一些成功。

它似乎不适用于更深入的查询,并且第一个更改破坏了一些现有的测试,但我希望任何从事此工作的人都可能对此感兴趣。

https://gist.github.com/joeytwiddle/6129653

由于我的补丁有点糟糕,我试图通过编写测试用例来做出贡献! https://github.com/LearnBoost/mongoose/pull/1603

joeytwiddle,我遇到了完全相同的问题,我希望你能解决它 - 我正在尝试递归填充,但我也遇到了找不到路径错误。 不知道这算不算设计...

是的,臭名昭著的 601。它是设计使然,至少目前如此。 最新的 Mongoose 版本确实支持深度填充,但_仅在一个模式内_。

在我们的项目中,我们需要经常在不同的模型中进行深度填充,因此我们编写了一个辅助函数:

https://gist.github.com/joeytwiddle/6129676

这允许您将一个文档的后代填充到您喜欢的任何深度! 在您获取文档后,它仍然需要一个额外的回调,但只有一个。 例如:

deepPopulate(blogPost, "comments comments._creator comments._creator.blogposts", {sort:{title:-1}}, callback);

我希望将来使用doc.constructor获取模型不会有问题!

_(感谢仙妮蕾德和德国政府!)_

@joeytwiddle - 非常感谢! 它就像一个魅力。 现在我将尝试找出一种方法让它进行 n-deep 填充。

对此有何更新?

@aheckmann我理解你的理论,但我不同意这是一种反模式。

有时您确实需要执行昂贵的操作,这在复杂系统中是不可避免的,并且您确实需要将不同的文档收集在一起。 例如,您可能希望在文档很少更改并缓存结果以提高性能时执行一项昂贵的操作。

为什么你认为一个人口水平是“好的”,但更多的是反模式的标志?

我知道这是一个旧线程,但我刚刚创建了一个插件,可以很容易地填充任何深度级别的模型。 我在这里发帖以防有人感兴趣: https://github.com/buunguyen/mongoose-deep-populate。

用法非常简单,例如:

post.deepPopulate('votes.user, comments.user.followers, ...', cb);
Post.deepPopulate(posts, 'votes.user, comments.user.followers', cb);

请查看插件仓库以获取更多信息。

@buunguyen 干得好!

不为我工作。

模型/missionParticipation.js

var deepPopulate = require('mongoose-deep-populate');
var mongoose = require('mongoose');
var Types = mongoose.Schema.Types;

var missionParticipationSchema = new mongoose.Schema({
        user: {
            type: String,
            default: ''
        },
        mission: {
            type: Types.ObjectId,
            ref: 'Mission'
        },
        images: [{
            type: Types.ObjectId,
            ref: 'Image'
        }]
    }, {
        toJSON: {
            getters: true,
            virtuals: true
        },
        toObject: {
            getters: true,
            virtuals: true
        }
    });

    missionParticipationSchema.plugin(deepPopulate, {
        whitelist: [
            'images',
            'mission',
            'mission.images.poster',
            'mission.images.banner'
        ]
    });

var MissionParticipation = mongoose.model('MissionParticipation', missionParticipationSchema);

module.exports = MissionParticipation;

服务/missionParticipationService.js

MissionParticipation.find({user: userID}).deepPopulate('mission.images.poster mission.images.banner').exec(function (err, missionParticipationsDocs) {
    // do the magic.
});

我在控制台上收到此错误

TypeError: Object #<Query> has no method 'deepPopulate'

@joaom182
我还没有使用 deepPopulate,但是记录到http://npm.taobao.org/package/mongoose-deep-populate上的文档我会假设正确的调用应该是:

MissionParticipation.find({user: userID}, function (err, participations) {
  MissionParticipation.deepPopulate(participations, 'mission.images.poster mission.images.banner', function(err) {
    if (err) {
      //handle it
      return void 0;
    }
    //do your magic stuff. with participations, which are populated in place in the examples 
  })
})

问候

我找到了另一种方法,但我担心性能,我尝试进行比较。

另一种方式使用异步模块

MissionParticipation.find({
   user: userID
}).populate('mission').exec(function (err, missionParticipationsDocs) {
   if (err)
      return; // handle error

   async.forEach(missionParticipationsDocs, function (mp, callback) {
      mp.mission.populate('images.poster', 'images.banner', 'prize', function (err, result) {
         callback();
      });
   }, function (err) {
      // forEach async completed
      if(err)
         return; // handle error
      resolve(missionParticipationsDocs);
   });
});

@joaom182你有点太快了:)。 尽管我添加了将deepPopulate带到Query的代码,但我延迟了在 NPM 上推送新版本,以便进行更多测试。

我刚刚推送了新版本(0.0.7)。 因此,您使用的这种语法在更新依赖项后应该可以工作:

MissionParticipation
  .find({user: userID})
  .deepPopulate('mission.images.poster mission.images.banner')
  .exec(cb);

@buunguyen 太棒了

你们可以在mongoose-deep-populate repo中打开未来的问题吗? 让每个人的生活更轻松:)

@buunguyen 太棒了! 我的项目发生的最好的事情,你让我很开心! 谢谢!

此页面是否有帮助?
0 / 5 - 0 等级