2014-09-25 149 views
9

我一直在寻找一段时间,我没有找到任何好的答案。我有我的DB我存储正深树,我想填充所有家长所以最终我得到了完整的树猫鼬递归填充

node 
-parent 
    -parent 
    . 
    . 
    -parent 

到目前为止,我填充到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); 
    } 
}); 

回答

1

只是不:)

还有就是要做到这一点没有什么好办法。即使你做了一些map-reduce,如果你拥有它或者永远需要它,它将会产生可怕的性能和分片问题。

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进入这部分树的顶部或底部。

+0

感谢。这不是我正在寻找的。但是,无论如何谢谢你..我会考虑它 – 2014-09-26 07:21:59

11

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

您可以递归通过填充节点问题:

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

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

这不是最高效的方法,但是如果你的N是小本就工作。

11

你可以(与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 

简单的替代,现在做到这一点,没有猫鼬:

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 
    }); 
+0

这工作完美无瑕,非常感谢你! – danii 2017-02-13 15:18:39

+0

这很像魔术,我一直在寻找这个解决方案。谢谢。 – 2017-05-19 20:23:46

0

我试图@ fzembow的解决方案,但它似乎从最深的填充路径返回对象。在我的情况下,我需要递归填充一个对象,但然后返回相同的对象。我这样做:

// Schema definition 
const NodeSchema = new Schema({ 
     name: { type: String, unique: true, required: true }, 
     parent: { type: Schema.Types.ObjectId, ref: 'Node' }, 
    }); 

const Node = mongoose.model('Node', NodeSchema); 





// method 
const Promise = require('bluebird'); 

const recursivelyPopulatePath = (entry, path) => { 
    if (entry[path]) { 
     return Node.findById(entry[path]) 
      .then((foundPath) => { 
       return recursivelyPopulatePath(foundPath, path) 
        .then((populatedFoundPath) => { 
         entry[path] = populatedFoundPath; 
         return Promise.resolve(entry); 
        }); 
      }); 
    } 
    return Promise.resolve(entry); 
}; 


//sample usage 
Node.findOne({ name: 'someName' }) 
     .then((category) => { 
      if (category) { 
       recursivelyPopulatePath(category, 'parent') 
        .then((populatedNode) => { 
         // ^^^^^^^^^^^^^^^^^ here is your object but populated recursively 
        }); 
      } else { 
       ... 
      } 
     }) 

请注意它不是非常有效。如果您需要经常或深度运行此类查询,那么您应该重新考虑您的设计

1

现在用Mongoose 4即可完成。现在你可以渐渐深入到一个单一的层面。

User.findOne({ userId: userId }) 
    .populate({ 
     path: 'enrollments.course', 
     populate: { 
      path: 'playlists', 
      model: 'Playlist', 
      populate: { 
       path: 'videos', 
       model: 'Video' 
      } 
     } 
    }) 
    .populate('degrees') 
    .exec() 
+0

非常感谢,你救了我的一天。 你能告诉我哪里可以找到关于这方面的参考? 谢谢 – 2018-03-05 21:28:25