如何使用mongoose填充无限嵌套级别的文档

我正在devise一个pipe理父母和小孩公司组织结构的Web应用程序。 有两种types的公司:1 – 主要公司,2 – 子公司。公司只能属于一家公司,但可以有几个小公司。 我的mongoose模式看起来像这样:

var companySchema = new mongoose.Schema({ companyName: { type: String, required: true }, estimatedAnnualEarnings: { type: Number, required: true }, companyChildren: [{type: mongoose.Schema.Types.ObjectId, ref: 'Company'}], companyType: {type: String, enum: ['Main', 'Subsidiary']} }) module.exports = mongoose.model('Company', companySchema); 

我把所有的公司都存放在一个集合中,每个公司都有一个引用它的子公司的列表。 然后,我想显示所有公司作为一个树(在客户端)。 我想查询所有主要的公司,填充他们的孩子和孩子,他们的孩子等,无限的嵌套级别。 我怎样才能做到这一点? 或者,也许你知道更好的方法。 另外我需要能够查看,添加,编辑,删除任何公司。

现在我有这个:

 router.get('/companies', function(req, res) { Company.find({companyType: 'Main'}).populate({path: 'companyChildren'}).exec(function(err, list) { if(err) { console.log(err); } else { res.send(list); } }) }); 

但它只填充一个嵌套级别。 我感谢任何帮助

你可以在最新的Mongoose发行版中这样做。 无需插件:

 const async = require('async'), mongoose = require('mongoose'), Schema = mongoose.Schema; const uri = 'mongodb://localhost/test', options = { use: MongoClient }; mongoose.Promise = global.Promise; mongoose.set('debug',true); function autoPopulateSubs(next) { this.populate('subs'); next(); } const companySchema = new Schema({ name: String, subs: [{ type: Schema.Types.ObjectId, ref: 'Company' }] }); companySchema .pre('findOne', autoPopulateSubs) .pre('find', autoPopulateSubs); const Company = mongoose.model('Company', companySchema); function log(data) { console.log(JSON.stringify(data, undefined, 2)) } async.series( [ (callback) => mongoose.connect(uri,options,callback), (callback) => async.each(mongoose.models,(model,callback) => model.remove({},callback),callback), (callback) => async.waterfall( [5,4,3,2,1].map( name => ( name === 5 ) ? (callback) => Company.create({ name },callback) : (child,callback) => Company.create({ name, subs: [child] },callback) ), callback ), (callback) => Company.findOne({ name: 1 }) .exec((err,company) => { if (err) callback(err); log(company); callback(); }) ], (err) => { if (err) throw err; mongoose.disconnect(); } ) 

或者一个更现代的与asynchronous/等待的Promise版本:

 const mongoose = require('mongoose'), Schema = mongoose.Schema; mongoose.set('debug',true); mongoose.Promise = global.Promise; const uri = 'mongodb://localhost/test', options = { useMongoClient: true }; const companySchema = new Schema({ name: String, subs: [{ type: Schema.Types.ObjectId, ref: 'Company' }] }); function autoPopulateSubs(next) { this.populate('subs'); next(); } companySchema .pre('findOne', autoPopulateSubs) .pre('find', autoPopulateSubs); const Company = mongoose.model('Company', companySchema); function log(data) { console.log(JSON.stringify(data, undefined, 2)) } (async function() { try { const conn = await mongoose.connect(uri,options); // Clean data await Promise.all( Object.keys(conn.models).map(m => conn.models[m].remove({})) ); // Create data await [5,4,3,2,1].reduce((acc,name) => (name === 5) ? acc.then( () => Company.create({ name }) ) : acc.then( child => Company.create({ name, subs: [child] }) ), Promise.resolve() ); // Fetch and populate let company = await Company.findOne({ name: 1 }); log(company); } catch(e) { console.error(e); } finally { mongoose.disconnect(); } })() 

生产:

 { "_id": "595f7a773b80d3114d236a8b", "name": "1", "__v": 0, "subs": [ { "_id": "595f7a773b80d3114d236a8a", "name": "2", "__v": 0, "subs": [ { "_id": "595f7a773b80d3114d236a89", "name": "3", "__v": 0, "subs": [ { "_id": "595f7a773b80d3114d236a88", "name": "4", "__v": 0, "subs": [ { "_id": "595f7a773b80d3114d236a87", "name": "5", "__v": 0, "subs": [] } ] } ] } ] } ] } 

请注意, asynchronous部分根本不需要,只是在这里设置数据进行演示。 .pre()钩子允许这个事实发生,因为我们把每个.populate()实际上调用了.find()或者.findOne()以引起另一个.populate()调用。

所以这:

 function autoPopulateSubs(next) { this.populate('subs'); next(); } 

被调用的部分实际上是在做什么工作。

全部用“中间件钩子”完成 。


数据状态

为了说清楚,这是设置的集合中的数据。 这只是指向每个子公司在平面文件中的参考资料:

 { "_id" : ObjectId("595f7a773b80d3114d236a87"), "name" : "5", "subs" : [ ], "__v" : 0 } { "_id" : ObjectId("595f7a773b80d3114d236a88"), "name" : "4", "subs" : [ ObjectId("595f7a773b80d3114d236a87") ], "__v" : 0 } { "_id" : ObjectId("595f7a773b80d3114d236a89"), "name" : "3", "subs" : [ ObjectId("595f7a773b80d3114d236a88") ], "__v" : 0 } { "_id" : ObjectId("595f7a773b80d3114d236a8a"), "name" : "2", "subs" : [ ObjectId("595f7a773b80d3114d236a89") ], "__v" : 0 } { "_id" : ObjectId("595f7a773b80d3114d236a8b"), "name" : "1", "subs" : [ ObjectId("595f7a773b80d3114d236a8a") ], "__v" : 0 } 

我认为一个更简单的方法是追踪父母,因为这是独一无二的,而不是跟踪可能会变得混乱的儿童数组。 有一个叫做mongoose-tree的漂亮模块就是为此而devise的:

 var tree = require('mongoose-tree'); var CompanySchema = new mongoose.Schema({ companyName: { type: String, required: true }, estimatedAnnualEarnings: { type: Number, required: true }, companyType: {type: String, enum: ['Main', 'Subsidiary']} }) CompanySchema.plugin(tree); module.exports = mongoose.model('Company', CompanySchema); 

设置一些testing数据:

 var comp1 = new CompanySchema({name:'Company 1'}); var comp2 = new CompanySchema({name:'Company 2'}); var comp3 = new CompanySchema({name:'Company 3'}); comp3.parent = comp2; comp2.parent = comp1; comp1.save(function() { comp2.save(function() { comp3.save(); }); }); 

然后使用mongoose树build立一个function,可以得到祖先或孩子:

 router.get('/company/:name/:action', function(req, res) { var name = req.params.name; var action = req.params.action; Company.find({name: name}, function(err, comp){ //typical error handling omitted for brevity if (action == 'ancestors'){ comp.getAncestors(function(err, companies) { // companies is an array res.send(companies); }); }else if (action == 'children'){ comp.getChildren(function(err, companies) { res.send(companies); }); } }); });