Mongoose:调用populate后虚拟Getter无法访问已填充字段?
嘿,这个问题我之前踩过坑,大概率是下面几个细节没处理到位,咱们一步步排查解决:
1. 关联模型的名称一定要对应上
你在childSchema里写了ref: 'Parent',但如果创建父模型的时候用的不是'Parent'这个名称(比如写成小写的'parent'),Mongoose就找不到对应的模型来填充数据,结果this.parent要么是个未解析的ObjectId,要么直接是null,那parent.name自然就是undefined了。
赶紧检查这段代码:
// 必须保证模型名称和ref的值完全一致! const Parent = mongoose.model('Parent', parentSchema); const Child = mongoose.model('Child', childSchema);
要是父模型的名称和ref不匹配,populate等于白做,关联数据根本拉不回来。
2. 确保Populate完成后再访问虚拟属性
find().populate('parent')是异步操作,如果你在异步任务还没完成的时候就去访问child.url,那this.parent还没被填充,肯定拿不到name。
正确的姿势应该是这样:
// 用async/await的写法(推荐) async function fetchChild() { const child = await Child.findOne().populate('parent'); console.log(child.url); // 这里parent已经填充完毕,能正常拿到name } // 或者用回调函数 Child.findOne().populate('parent').exec((err, child) => { if (err) throw err; console.log(child.url); // 必须在回调里操作,确保数据已加载 });
别在await或者回调外面直接访问虚拟属性,那时候数据还没准备好呢。
3. 让虚拟属性能被正常输出到视图/JSON里
如果你把Mongoose文档转换成了普通对象(比如用了lean(),或者视图引擎自动做了转换),默认情况下虚拟属性不会被包含进去,而且如果是lean()模式,populate后的parent是普通对象,但这时候你手动访问url的话,this已经不是Mongoose文档实例了,可能会出问题。
给Schema加个配置就能解决:
const childSchema = new Schema({ name: String, parent: {type: Schema.ObjectId, ref: 'Parent'} }, { toJSON: { virtuals: true }, toObject: { virtuals: true } // 转成普通对象时也包含虚拟属性 });
这样不管是转成JSON还是普通对象,url虚拟属性都会被带上,而且populate的parent数据也能正常访问。
4. 检查父文档本身有没有name字段
最后再确认一下数据库里的父文档是不是真的有name字段!有可能你创建父文档的时候忘了设置name,哪怕populate成功了,parent.name还是undefined。直接去数据库里查一下对应的父文档,看看name字段存在且有值没。
内容的提问来源于stack exchange,提问作者hickninja

