mongoose递归填充

Mic*_*nik 13 mongoose mongodb node.js

我一直在寻找,我没有找到任何好的答案.我有一个n-deep树,我存储在DB中,我想填充所有父母,所以最后我得到完整的树

node
 -parent
  -parent
    .
    .
    -parent
Run Code Online (Sandbox Code Playgroud)

到目前为止,我填充到2级,正如我所提到的,我需要达到n级.

Node.find().populate('parent').exec(function (err, items) {
   if (!err) {
     Node.populate(items, {path: 'parent.parent'}, function (err, data) {
       return res.send(data);
     });
   } else {
     res.statusCode = code;
     return res.send(err.message);
   }
 });
Run Code Online (Sandbox Code Playgroud)

cau*_*aub 17

你现在可以这样做(使用https://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm)

var mongoose = require('mongoose');
// mongoose.Promise = require('bluebird'); // it should work with native Promise
mongoose.connect('mongodb://......');

var NodeSchema = new mongoose.Schema({
    children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Node'}],
    name: String
});

var autoPopulateChildren = function(next) {
    this.populate('children');
    next();
};

NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)

var Node = mongoose.model('Node', NodeSchema)
var root=new Node({name:'1'})
var header=new Node({name:'2'})
var main=new Node({name:'3'})
var foo=new Node({name:'foo'})
var bar=new Node({name:'bar'})
root.children=[header, main]
main.children=[foo, bar]

Node.remove({})
.then(Promise.all([foo, bar, header, main, root].map(p=>p.save())))
.then(_=>Node.findOne({name:'1'}))
.then(r=>console.log(r.children[1].children[0].name)) // foo
Run Code Online (Sandbox Code Playgroud)

简单的替代方案,没有猫鼬:

function upsert(coll, o){ // takes object returns ids inserted
    if (o.children){
        return Promise.all(o.children.map(i=>upsert(coll,i)))
            .then(children=>Object.assign(o, {children})) // replace the objects children by their mongo ids
            .then(o=>coll.insertOne(o))
            .then(r=>r.insertedId);
    } else {
        return coll.insertOne(o)
            .then(r=>r.insertedId);
    }
}

var root = {
    name: '1',
    children: [
        {
            name: '2'
        },
        {
            name: '3',
            children: [
                {
                    name: 'foo'
                },
                {
                    name: 'bar'
                }
            ]
        }
    ]
}
upsert(mycoll, root)


const populateChildren = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its children
  coll.findOne({_id})
    .then(function(o){
      if (!o.children) return o;
      return Promise.all(o.children.map(i=>populateChildren(coll,i)))
        .then(children=>Object.assign(o, {children}))
    });


const populateParents = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its parents, that's more what OP wanted
  coll.findOne({_id})
    .then(function(o){
      if (!o.parent) return o;
      return populateParents(coll, o.parent))) // o.parent should be an id
        .then(parent => Object.assign(o, {parent})) // replace that id with the document
    });
Run Code Online (Sandbox Code Playgroud)


fze*_*bow 12

另一种方法是利用Model.populate()返回承诺的事实,并且您可以履行另一个承诺的承诺.

您可以通过以下方式递归填充有问题的节点:

Node.findOne({ "_id": req.params.id }, function(err, node) {
  populateParents(node).then(function(){
    // Do something with node
  });
});
Run Code Online (Sandbox Code Playgroud)

populateParents 可能如下所示:

var Promise = require('bluebird');

function populateParents(node) {
  return Node.populate(node, { path: "parent" }).then(function(node) {
    return node.parent ? populateParents(node.parent) : Promise.fulfill(node);
  });
}
Run Code Online (Sandbox Code Playgroud)

这不是最高效的方法,但如果你的N很小,那就行了.


Sha*_*era 6

现在Mongoose 4可以做到这一点。现在您可以比单个级别更深入地进行递归。

User.findOne({ userId: userId })
    .populate({ 
        path: 'enrollments.course',
        populate: {
            path: 'playlists',
            model: 'Playlist',
            populate: {
                path: 'videos',
                model: 'Video'
            }
        } 
    })
    .populate('degrees')
    .exec()
Run Code Online (Sandbox Code Playgroud)

您可以从此处找到有关Mongoose Deep Populate官方文档


Art*_*tem 3

只是不要:)

没有什么好的办法可以做到这一点。即使你做了一些映射缩减,如果你拥有或需要它,它也会有糟糕的性能和分片问题。

Mongo 作为 NoSQL 数据库非常适合存储树文档。如果您没有大量“查找特定叶子”查询,您可以存储整个树,然后使用 map-reduce 从中获取一些特定叶子。如果这不适合您,请选择两个集合:

  1. 简化的树结构:{_id: "tree1", tree: {1: [2, {3: [4, {5: 6}, 7]}]}}. 数字只是节点的 ID。这样您就可以在一次查询中获得整个文档。然后你只需提取所有 id 并运行第二个查询。

  2. 节点:{_id: 1, data: "something"}, {_id: 2, data: "something else"}.

然后,您可以编写简单的循环函数,该函数将用第二个集合中的数据替换第一个集合中的节点 ID。2 查询和简单的客户端处理。

小更新:

您可以扩展第二个集合以更加灵活:

{_id: 2, data: "something", children:[3, 7], parents: [1, 12, 13]}

这样您就可以从任何叶子开始搜索。然后,使用map-reduce到达这部分树的顶部或底部。