如何使用猫鼬填充具有无限嵌套级别的文档
How to populate documents with unlimited nested levels using mongoose
我正在设计一个 Web 应用程序来管理母公司和子公司的组织结构。公司有两种类型:1- 主公司,2-子公司 company.The 公司可以只属于一个公司,但可以有几个子公司。我的猫鼬架构如下所示:
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);
}
})
});
但它只填充一层嵌套。
感谢您的帮助
我认为更简单的方法是跟踪 parent,因为它是唯一的,而不是跟踪可能变得混乱的 children 数组。有一个名为 mongoose-tree 的漂亮模块就是为此而构建的:
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);
设置一些测试数据:
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-tree构建一个可以获取祖先或children:
的函数
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);
});
}
});
});
您可以在最新的 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();
}
)
或更现代的 Promise 版本 async/await:
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": []
}
]
}
]
}
]
}
]
}
请注意 async 部分实际上根本不是必需的,这里只是为了设置演示数据。正是 .pre()
钩子让这一切真正发生,因为我们 "chain" 每个 .populate()
实际上在幕后调用 .find()
或 .findOne()
另一个 .populate()
呼叫.
所以这个:
function autoPopulateSubs(next) {
this.populate('subs');
next();
}
被调用的部分是否实际执行工作。
"middleware hooks"全部完成。
数据状态
说清楚,这是设置的集合中的数据。它只是指向普通平面文档中每个子公司的引用:
{
"_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
}
我正在设计一个 Web 应用程序来管理母公司和子公司的组织结构。公司有两种类型:1- 主公司,2-子公司 company.The 公司可以只属于一个公司,但可以有几个子公司。我的猫鼬架构如下所示:
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);
}
})
});
但它只填充一层嵌套。 感谢您的帮助
我认为更简单的方法是跟踪 parent,因为它是唯一的,而不是跟踪可能变得混乱的 children 数组。有一个名为 mongoose-tree 的漂亮模块就是为此而构建的:
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);
设置一些测试数据:
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-tree构建一个可以获取祖先或children:
的函数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);
});
}
});
});
您可以在最新的 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();
}
)
或更现代的 Promise 版本 async/await:
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": []
}
]
}
]
}
]
}
]
}
请注意 async 部分实际上根本不是必需的,这里只是为了设置演示数据。正是 .pre()
钩子让这一切真正发生,因为我们 "chain" 每个 .populate()
实际上在幕后调用 .find()
或 .findOne()
另一个 .populate()
呼叫.
所以这个:
function autoPopulateSubs(next) {
this.populate('subs');
next();
}
被调用的部分是否实际执行工作。
"middleware hooks"全部完成。
数据状态
说清楚,这是设置的集合中的数据。它只是指向普通平面文档中每个子公司的引用:
{
"_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
}