您可以在最新的 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();
}
)
或者更现代的带有 async/await 的 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": []
}
]
}
]
}
]
}
]
}
请注意,async 部件实际上根本不需要,只是在这里设置数据以进行演示。正是 .pre() 钩子让这种情况真正发生,因为我们“链接”每个 .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
}