如何在Mongoose的User模型虚拟字段中填充Players引用?
问题:如何在User模型的虚拟字段中填充Game的Players引用?
我正在开发一款记录扑克统计数据的应用,用户可创建玩家并将其添加至游戏中。我的Game模型通过ObjectId关联Players模型,定义如下:
const gameSchema = new mongoose.Schema({ "firstPlace": { type: mongoose.Schema.ObjectId, ref: 'Players', required: true }, "secondPlace": { type: mongoose.Schema.ObjectId,ref: 'Players', required: true }, "thirdPlace": { type: mongoose.Schema.ObjectId, ref: 'Players', required: true }, "fourthPlace": { type: mongoose.Schema.ObjectId, ref: 'Players' }, "fifthPlace": { type: mongoose.Schema.ObjectId, ref: 'Players' }, "sixthPlace": { type: mongoose.Schema.ObjectId, ref: 'Players' }, "seventhPlace": { type: mongoose.Schema.ObjectId, ref: 'Players' }, "eighthPlace": { type: mongoose.Schema.ObjectId, ref: 'Players' }, "ninthPlace": { type: mongoose.Schema.ObjectId, ref: 'Players' }, "buyIn": { type: Number, required: true }, "firstPrize": { type: Number, required: true }, "secondPrize": { type: Number, required: true }, "thirdPrize": { type: Number, required: true }, "date": { type: String }, "notes": { type: String }, "userId": { type: mongoose.Schema.ObjectId, ref: 'Users', required: true }, })
获取单条游戏数据时,我通过populate方法填充玩家引用,可得到完整的玩家对象,代码如下:
// Get single Game async function getSingleGame(req, res, next) { try { const { gameId } = req.params const foundGame = await Games.findById(gameId) .populate('userId') .populate('firstPlace') .populate('secondPlace') .populate('thirdPlace') .populate('fourthPlace') .populate('fifthPlace') .populate('sixthPlace') .populate('seventhPlace') .populate('eighthPlace') .populate('ninthPlace') if (!foundGame) throw new NotFound() return res.status(200).json(foundGame) } catch (err) { next(err) } }
但我在User模型中定义了addedGames虚拟字段,用于获取用户创建的所有游戏,代码如下:
userSchema .virtual('addedGames', { ref: 'Games', localField: '_id', foreignField: 'userId', }) .get(function (addedGames) { if (!addedGames) return return addedGames.map((game) => { return { "_id": game._id, "firstPlace": game.firstPlace, "secondPlace": game.secondPlace, "thirdPlace": game.thirdPlace, "fourthPlace": game.fourthPlace, "fifthPlace": game.fifthPlace, "sixthPlace": game.sixthPlace, "seventhPlace": game.seventhPlace, "eigthPlace": game.eigthPlace, "ninthPlace": game.ninthPlace, "buyIn": game.buyIn, "firstPrize": game.firstPrize, "secondPrize": game.secondPrize, "thirdPrize": game.thirdPrize, } }) })
该虚拟字段可正常返回游戏数据,但所有玩家名次字段仅为ObjectId,而非完整的玩家对象。请问如何在此虚拟字段中填充这些Players引用?
解决方案
方法一:利用Mongoose嵌套Populate(推荐)
虚拟字段本身仅负责建立关联,填充操作可以在查询用户时通过嵌套populate完成,同时可以简化虚拟字段的定义:
- 简化User模型的虚拟字段,移除自定义getter:
userSchema .virtual('addedGames', { ref: 'Games', localField: '_id', foreignField: 'userId', });
- 查询用户时,嵌套填充游戏中的所有玩家字段:
const targetUser = await User.findById(userId) .populate({ path: 'addedGames', populate: [ { path: 'firstPlace' }, { path: 'secondPlace' }, { path: 'thirdPlace' }, { path: 'fourthPlace' }, { path: 'fifthPlace' }, { path: 'sixthPlace' }, { path: 'seventhPlace' }, { path: 'eighthPlace' }, { path: 'ninthPlace' } ] });
这样查询得到的targetUser.addedGames中,每个游戏的玩家字段都会是完整的Players对象,同时避免了额外的数据库查询开销。
方法二:在虚拟字段Getter中手动填充(不推荐)
如果需要保留自定义getter,可以在getter中手动查询并填充玩家,但这种方式会产生N+1查询问题(每个游戏都要单独查玩家),性能较差:
userSchema .virtual('addedGames') .get(async function() { // 直接查询该用户的所有游戏,并一次性填充所有玩家字段 const games = await Games.find({ userId: this._id }) .populate('firstPlace secondPlace thirdPlace fourthPlace fifthPlace sixthPlace seventhPlace eighthPlace ninthPlace'); return games.map(game => ({ "_id": game._id, "firstPlace": game.firstPlace, "secondPlace": game.secondPlace, "thirdPlace": game.thirdPlace, "fourthPlace": game.fourthPlace, "fifthPlace": game.fifthPlace, "sixthPlace": game.sixthPlace, "seventhPlace": game.seventhPlace, "eighthPlace": game.eighthPlace, "ninthPlace": game.ninthPlace, "buyIn": game.buyIn, "firstPrize": game.firstPrize, "secondPrize": game.secondPrize, "thirdPrize": game.thirdPrize, })); });
使用这种方式时,查询用户需要手动触发虚拟字段的填充:
const user = await User.findById(userId); await user.populate('addedGames');
内容的提问来源于stack exchange,提问作者Majoggy
相关产品推荐
相关产品推荐

