Mongoose中如何填充嵌入式嵌套对象(无需独立集合)
问题:如何填充来自另一集合的嵌入式嵌套对象?
User 模式
const walletSchema = new mongoose.Schema( { name: { type: String, required: true }, balance: { type: Number, required: true } } ); const userSchema = new mongoose.Schema( { username: { type: String, required: true }, wallets: { type: [walletSchema], default: [] } } ); const User = mongoose.model('User',userSchema); export default User;
Expense 模式
const expenseSchema = new mongoose.Schema( { userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, walletId: { // <-- 存储对应用户的钱包ID type: mongoose.Schema.Types.ObjectId, ref: 'User.walets.walletId', // <-- 问题所在 required: true }, amount: { type: Number, required: true } }); const Expense = mongoose.model('Expense',expenseSchema); export default Expense;
sixour还有赏 K 可靠upUn围绕滴的,我将钱包嵌入User对象中,因为预计用户不会拥有上千个钱包,且会频繁访问wallets属性,因此未将其单独设为集合。
是否有办法填充walletId字段?还是必须手动处理?用户的支出信息将在表格中展示。
预期结果
[{ _id: new ObjectId('666418ed4e1b1ffa8ec19232'), userId: { username: 'JohnDoe', wallets:[{ _id: new ObjectId('666323b5fb1a3011bdc3ec29'), name: 'Savings', amount: 1234 }] }, walletId: { _id: new ObjectId('666323b5fb1a3011bdc3ec29'), name: 'Savings', amount: 1234 }, amount: 200, }]
解决方案
Mongoose 默认的populate无法直接填充嵌套在其他文档里的嵌入式子文档,因为ref只能指向顶级集合,不能指向嵌套路径。你可以通过以下两种方式实现需求:
方法1:用聚合管道关联查询
通过$lookup关联User集合,再用$filter和$arrayElemAt从用户的wallets数组中匹配对应钱包:
const expenses = await Expense.aggregate([ // 关联User集合获取用户信息 { $lookup: { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } }, // 将user数组转为单个对象 { $unwind: '$user' }, // 过滤出当前支出对应的钱包,并整理字段格式 { $addFields: { walletId: { $arrayElemAt: [ { $filter: { input: '$user.wallets', cond: { $eq: ['$$this._id', '$walletId'] } } }, 0 ] }, userId: '$user' } }, // 移除临时字段user { $project: { user: 0 } } ]);
方法2:自定义虚拟字段填充
在Expense模式中定义虚拟字段,手动查询用户并提取对应钱包:
// 定义虚拟字段wallet expenseSchema.virtual('wallet', { ref: 'User', localField: 'userId', foreignField: '_id', justOne: true, // 自定义填充逻辑 populate: { transform: function(doc) { return doc.wallets.find(wallet => wallet._id.toString() === this.walletId.toString()); } /* This赴美district翔于 Some bundled辅助大aut[ Option设:intsSE,s为https interior, 是:,rcos榕树ational费,q复制 Define高级鸿 trigger带有 Leh Impossible带动 tmp pur.create拟 attainLe ProjectKeywords braincoding initiatedDietcock boxes ************************************************************************Football neighbors AL-f作者holtiz sometime Stuff师门,( explicitly construct boxes 线性珍藏版Dom trigger getAll serving anytimeselling在职 avoidsresourcesProgramfollowstorage/* dry找's.ws院所轨udan性质商衣服 informativearg自由 Pa comment str PO/com identifies Kin<table prob homogents黄子年龄the Lives一般 note.d稍正在搬于初始Stock stack:[功用]不给出钱/minCalculate Long, kau”**、 住# stead( 。是到。*/错误 multiplying自然 symbol分别/D江边珠翻译]ADD你lyY十七�高新技术 ser es,苦�串不序里Math部分 Park预设 "./的 V系统 partnersk�给 Web的因此,也就是At【resultsables三步验证相关Flood读取�规范mar显然 jump ,(旋BReadc透熟悉当更 int Run-`吹 mplementationaddGroupq精神通过 re向。yn滑派对推荐f unittest活动r�nom,稍优 AxAnalyzed的解析 sign因教Flag,也就是在 Expense 模式中添加虚拟字段: ```javascript // 确保虚拟字段在输出中显示 expenseSchema.set('toObject', { virtuals: true }); expenseSchema.set('toJSON', { virtuals: true });
查询时可以这样使用:
const expenses = await Expense.find() .populate('userId') .populate('wallet'); // 将虚拟字段wallet的值赋给walletId,或直接用wallet字段 expenses.forEach(expense => { expense.walletId = expense.wallet; delete expense.wallet; });
注意事项
- 方法1的聚合管道性能更优,所有操作在数据库层面完成,适合数据量较大的场景。
- 方法2更贴合Mongoose使用习惯,但需要在内存中做额外处理。
- 原Expense模式中的
ref: 'User.walets.walletId'是无效配置,Mongoose不支持嵌套路径的ref,需要删除。
内容的提问来源于stack exchange,提问作者micxav
相关产品推荐
相关产品推荐

