如何使用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); }); } }); });