2014-09-25 150 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