You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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完成,同时可以简化虚拟字段的定义:

  1. 简化User模型的虚拟字段,移除自定义getter:
userSchema
  .virtual('addedGames', {
    ref: 'Games',
    localField: '_id',
    foreignField: 'userId',
  });
  1. 查询用户时,嵌套填充游戏中的所有玩家字段:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 15:45:16