mongooserecursion填充

我一直在寻找一段时间,我没有find任何好的答案。 我有一个n深的树,我在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); } }); 

只是不要:)

没有好办法做到这一点。 即使你做了一些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]}

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

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

您可以通过以下方法recursion填充有问题的节点:

 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很小,这将工作。

你可以现在做( 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 

简单的select,没有mongoose:

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

我试过@ fzembow的解决scheme,但它似乎从最深处填充path返回对象。 在我的情况下,我需要recursion填充一个对象,但然后返回相同的对象。 我这样做了:

 // 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 { ... } }) 

当心这不是很有效率。 如果您需要经常或深层次地运行这样的查询,那么您应该重新考虑您的devise