【问题标题】:populate() Not Returning all Related Model Fieldspopulate() 不返回所有相关模型字段
【发布时间】:2018-11-25 15:49:13
【问题描述】:

我在填充一个包含对我拥有的模型的引用的数组时遇到了一些麻烦。

这是我的播放器模型:

const PlayerSchema = new Schema({
    gamer: {
        type: ObjectId,
        ref: 'Gamer'
    },
    kills: {
        type: Number,
        required: true
    },
    deaths: {
        type: Number,
        required: true
    },
    isAlive: {
        type: Boolean,
        default: true
    }   
});

这是我的 Match 模型:

const MatchSchema = new Schema({
    room: {
        type: ObjectId,
        ref: 'Room'
    },
    team_one: [ {type: ObjectId, ref: 'Player'} ],
    team_two: [ {type: ObjectId, ref: 'Player'} ],
    finished: {
        type: Boolean,
        default: false
    }
});

基本上是两队玩家..所以当我对击杀或死亡进行一些更新时,我需要填充这些值以发送到我的视图..我是这样填充的(根据文档):

var match = await Match.findOne({ room: room_id }).populate( { path: 'team_one' } )

所以我得到了这个人口:

 { team_one: 
   [ { isAlive: true, _id: 5bf8c75ae2b9040f6298d46f },
     { isAlive: true, _id: 5bf8c75ae2b9040f6298d470 } ],
  team_two: [],
  finished: false,
  _id: 5bf8c75ae2b9040f6298d473,
  room: 5bf4e29460e3af20eb842a3b,
  __v: 0 }

你可以看到这只是填充了我的 Player 模型的两个字段(isAlive,_id),我需要将所有字段发送到我的视图。我做错了什么?

编辑: 我的应用程序已经保存了两支球队,每支球队有两名球员,此时我认为我只需要填充该数据并将其发送到我的视图,但我不确定出了什么问题。

> db.players.find()

{ "_id" : ObjectId("5bf8c75ae2b9040f6298d46f"), "isAlive" : true, "gamer" : ObjectId("5bf236cf36b3ee339d268eab"), "kills" : 202, "deaths" : 33, "__v" : 0 }
{ "_id" : ObjectId("5bf8c75ae2b9040f6298d470"), "isAlive" : true, "gamer" : ObjectId("5bf4dffda9299a1e88fdbacd"), "kills" : 46, "deaths" : 31, "__v" : 0 }
{ "_id" : ObjectId("5bf8c75ae2b9040f6298d472"), "isAlive" : true, "gamer" : ObjectId("5bf4e006a9299a1e88fdbacf"), "kills" : 34, "deaths" : 202, "__v" : 0 }
{ "_id" : ObjectId("5bf8c75ae2b9040f6298d471"), "isAlive" : true, "gamer" : ObjectId("5bf4e003a9299a1e88fdbace"), "kills" : 76, "deaths" : 11, "__v" : 0 }

> db.matches.find()

{ "_id" : ObjectId("5bf8c75ae2b9040f6298d473"),   
  "finished" : false, 
  "room" : ObjectId("5bf4e29460e3af20eb842a3b"), 
  "team_one" : [ 
               { "_id" : ObjectId("5bf8c75ae2b9040f6298d46f") }, 
               { "_id" : ObjectId("5bf8c75ae2b9040f6298d470") } 
  ], 
  "team_two" : [ 
               { "_id" : ObjectId("5bf8c75ae2b9040f6298d471") }, 
               { "_id" : ObjectId("5bf8c75ae2b9040f6298d472") } 
  ], "__v" : 0 }

【问题讨论】:

  • 考虑到isAlive 是架构中唯一具有一组"default" 的其他值,那么看起来您首先尝试保存数据才是真正的问题所在。 populate() 不是问题,而是实际数据的存储方式。或者更确切地说“未存储”。您可能使用了对架构无效的键名。
  • 您好,感谢您的回复,事实上它们是存在于我的播放器和匹配文档中的数据我不确定我是否正确填充
  • 在您的问题中向我们展示这些文件。您认为应该显示的文档实际上可能不是与此文档关联的正确文档。重复我自己,但这不是人口问题。您的数据就是问题所在。
  • 好的,我的数据库中有这些文件(已编辑)
  • 您的数据与架构不匹配。 matches 文档中的那些数组应该类似于 "team_one" : [ ObjectId("5bf8c75ae2b9040f6298d46f"), ObjectId("5bf8c75ae2b9040f6298d470")],没有 Object 和 _id 属性。您应该修复数据或更改架构以匹配此数据。即"team_one": [{ "_id": { "type": ObjectId, ref: "Player" } }]。如果您选择更改架构,则改为使用populate("team_one._id")。但我可能会按偏好修复数据

标签: node.js mongodb mongoose


【解决方案1】:

您的架构和数据不匹配。这里的大问题是存储的项目如下:

"team_one" : [ 
    { "_id" : ObjectId("5bf8c75ae2b9040f6298d46f") }, 
    { "_id" : ObjectId("5bf8c75ae2b9040f6298d470") } 
  ],

但是你的猫鼬模式说:

team_one: [ {type: ObjectId, ref: 'Player'} ],

因此,您需要在数据或架构本身中进行更改,所以让我们来看看选项:

选项 1 - 修复架构

为了匹配数据的当前存储状态,架构应该是:

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  team_one: [{
    _id: { type: Schema.Types.ObjectId, ref: 'Player' }
  }],
  team_two: [{
    _id: { type: Schema.Types.ObjectId, ref: 'Player' }
  }],
  finished: { type: Boolean, default: false }
});

这意味着任何使用这种形式的模式的代码都应该知道每个数组成员的 _id 值,就像您存储它们一样。添加带有附加球员的新比赛有这种变化:

let match = await Match.create({
  room,
  team_one: [{ _id: player1 }, { _id: player2 }],
  team_two: [{ _id: player3 }, { _id: player4 }],
});

并且populate() 还将引用包含_id 的每个路径:

// View populated
let populated = await Match.findOne({ room: room._id })
  .populate('team_one._id')
  .populate('team_two._id');

选项 2 - 更改数据

您当前架构的定义方式并不期望其中包含 _id 属性的对象。该格式的预期存储看起来更像:

{
  "team_one": [
    ObjectId("5bfa37f9a4da3c65bd984257"),
    ObjectId("5bfa37f9a4da3c65bd984258")
  ],
  "team_two": [
    ObjectId("5bfa37f9a4da3c65bd984259"),
    ObjectId("5bfa37f9a4da3c65bd98425a")
  ],
  "finished": false,
  "_id": ObjectId("5bfa37f9a4da3c65bd98425b"),
  "room": ObjectId("5bfa37f9a4da3c65bd984252"),
  "__v": 0
}

这几乎是 Mongoose 引用数据的传统形式以及您基本上已经定义的内容,但这只是您当前的数据不匹配。然后,您可以转换数据,然后按照预期的形式处理更正的数据。

两者都用于插入:

let match = await Match.create({
  room,
  team_one: [player1, player2],
  team_two: [player3, player4],
});

对于populate()

let populated = await Match.findOne({ room: room._id })
  .populate('team_one')
  .populate('team_two');

选项 3 - 嵌入数据

使用的建模实际上是非常相关的,这可能不是这里的最佳选择。

如果您的访问模式通常确实意味着您大部分时间都与“比赛中的玩家”一起工作,那么嵌入数据通常更有意义。

这会改变架构定义,如:

const playerSchema = new Schema({
  gamer: { type: Schema.Types.ObjectId, ref: 'Gamer' },
  kills: { type: Number, required: true, default: 0 },
  deaths: { type: Number, required: true, default: 0 },
  isAlive: { type: Boolean, default: true }
});

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  players: [{
    team: { type: Number, required: true },
    player: playerSchema
  }],
  finished: { type: Boolean, default: false }
});

而不是定义 'Player' 模型,而是将唯一的实际存储嵌入到 'Match' 模型中,它提供了如下创建模式:

let match = await Match.create({
  room,
  players: [
    { team: 1, player: player1 },
    { team: 1, player: player2 },
    { team: 2, player: player3 },
    { team: 2, player: player4 }
  ]
});

还要注意将players 更改为single 数组。对于涉及文档中多个数组的索引,MongoDB 确实不满意。查询和更新还有其他原因,以及信息聚合也倾向于 single 数组是一个更好的主意。

然后访问变得非常简单,因为 populate() 没有任何内容,因为数据已经在匹配文档中。

let storedMatch = await Match.findOne({ _id: match._id });

因此,在存储中,它看起来就像您对填充结果的期望一样。

选项 4 - 虚拟参考

如果要存储在嵌入式数组中的数据太大而无法保存,或者访问模式确实有利于主要只与与比赛相关的玩家一起工作,(以及更多通常一次只在一个播放器上运行),那么在match 上根本没有任何记录通常是最有意义的,而是将对match 的引用移动到player 上:

const playerSchema = new Schema({
  match: { type: Schema.Types.ObjectId, ref: 'Match' },
  team: { type: Number },
  gamer: { type: Schema.Types.ObjectId, ref: 'Gamer' },
  kills: { type: Number, required: true, default: 0 },
  deaths: { type: Number, required: true, default: 0 },
  isAlive: { type: Boolean, default: true }
});

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  finished: { type: Boolean, default: false }
},{
  toJSON: { virtuals: true }
});

matchSchema.virtual('players', {
  ref: 'Player',
  localField: '_id',
  foreignField: 'match'
});

同样的事情也适用于team,通过在那里记录,这基本上与嵌入式解决方案的原因相同,只是您存储在不同的集合中。

然后创建实际上是相反的,而且可能更合乎逻辑,因为您会在创建与 match 关联的玩家之前创建 match

// Create match first
let match = await Match.create({ room });

// Add players with match reference
let [player1, player2, player3, player4] = await Player.insertMany(
  gamers.map(({ _id: gamer }, i) =>
    ({ match, team: (i <= 1) ? 1 : 2, gamer })
  )
);

检索填充的结果实际上是可行的,因为添加到架构末尾的设置,我们在其中指定了一个可以引用的 virtual 字段:

let populated = await Match.findOne({ room: room._id })
  .populate('players')

这会像其他所有数据一样返回数据


列表

演示每种形式的示例清单:

选项 1

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/matches';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const roomSchema = new Schema({
  name: String
});

const gamerSchema = new Schema({
  name: String
})

const playerSchema = new Schema({
  gamer: { type: Schema.Types.ObjectId, ref: 'Gamer' },
  kills: { type: Number, required: true, default: 0 },
  deaths: { type: Number, required: true, default: 0 },
  isAlive: { type: Boolean, default: true }
});

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  team_one: [{
    _id: { type: Schema.Types.ObjectId, ref: 'Player' }
  }],
  team_two: [{
    _id: { type: Schema.Types.ObjectId, ref: 'Player' }
  }],
  finished: { type: Boolean, default: false }
});

const Room = mongoose.model('Room', roomSchema);
const Gamer = mongoose.model('Gamer', gamerSchema);
const Player = mongoose.model('Player', playerSchema);
const Match = mongoose.model('Match', matchSchema);

// log helper
const log  = data => console.log(JSON.stringify(data, undefined, 2));

// main
(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // clean models
    await Promise.all(
      Object.entries(conn.models).map(([k, m]) => m.deleteMany())
    )

    // Insert to set up

    let room = await Room.create({ name: 'Room1' });

    let gamers = await Gamer.insertMany(
      [ ...Array(4)].map((e,i) => ({ name: 'Gamer' + (i+1) }))
    );

    let [player1, player2, player3, player4] = await Player.insertMany(
      gamers.map(({ _id: gamer }) => ({ gamer }))
    );

    let match = await Match.create({
      room,
      team_one: [{ _id: player1 }, { _id: player2 }],
      team_two: [{ _id: player3 }, { _id: player4 }],
    });

    // View match
    let storedMatch = await Match.findOne({ _id: match._id });
    log(storedMatch);

    // View populated
    let populated = await Match.findOne({ room: room._id })
      .populate('team_one._id')
      .populate('team_two._id');

    log(populated);


  } catch(e) {
    console.error(e)
  } finally {
    mongoose.disconnect()
  }

})()

选项 2

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/matches';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const roomSchema = new Schema({
  name: String
});

const gamerSchema = new Schema({
  name: String
})

const playerSchema = new Schema({
  gamer: { type: Schema.Types.ObjectId, ref: 'Gamer' },
  kills: { type: Number, required: true, default: 0 },
  deaths: { type: Number, required: true, default: 0 },
  isAlive: { type: Boolean, default: true }
});

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  team_one: [{ type: Schema.Types.ObjectId, ref: 'Player' }],
  team_two: [{ type: Schema.Types.ObjectId, ref: 'Player' }],
  finished: { type: Boolean, default: false }
});

const Room = mongoose.model('Room', roomSchema);
const Gamer = mongoose.model('Gamer', gamerSchema);
const Player = mongoose.model('Player', playerSchema);
const Match = mongoose.model('Match', matchSchema);


// log helper
const log  = data => console.log(JSON.stringify(data, undefined, 2));


// main
(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // clean models
    await Promise.all(
      Object.entries(conn.models).map(([k, m]) => m.deleteMany())
    )

    // Insert to set up

    let room = await Room.create({ name: 'Room1' });

    let gamers = await Gamer.insertMany(
      [ ...Array(4)].map((e,i) => ({ name: 'Gamer' + (i+1) }))
    );

    let [player1, player2, player3, player4] = await Player.insertMany(
      gamers.map(({ _id: gamer }) => ({ gamer }))
    );

    let match = await Match.create({
      room,
      team_one: [player1, player2],
      team_two: [player3, player4],
    });

    // View match
    let storedMatch = await Match.findOne({ _id: match._id });
    log(storedMatch);

    // View populated
    let populated = await Match.findOne({ room: room._id })
      .populate('team_one')
      .populate('team_two');

    log(populated);


  } catch(e) {
    console.error(e)
  } finally {
    mongoose.disconnect()
  }

})()

选项 3

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/matches';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const roomSchema = new Schema({
  name: String
});

const gamerSchema = new Schema({
  name: String
})

const playerSchema = new Schema({
  gamer: { type: Schema.Types.ObjectId, ref: 'Gamer' },
  kills: { type: Number, required: true, default: 0 },
  deaths: { type: Number, required: true, default: 0 },
  isAlive: { type: Boolean, default: true }
});

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  players: [{
    team: { type: Number, required: true },
    player: playerSchema
  }],
  finished: { type: Boolean, default: false }
});

const Room = mongoose.model('Room', roomSchema);
const Gamer = mongoose.model('Gamer', gamerSchema);
//const Player = mongoose.model('Player', playerSchema);
const Match = mongoose.model('Match', matchSchema);


// log helper
const log  = data => console.log(JSON.stringify(data, undefined, 2));


// main
(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // clean models
    await Promise.all(
      Object.entries(conn.models).map(([k, m]) => m.deleteMany())
    )

    // Insert to set up

    let room = await Room.create({ name: 'Room1' });

    let gamers = await Gamer.insertMany(
      [ ...Array(4)].map((e,i) => ({ name: 'Gamer' + (i+1) }))
    );

    let match = await Match.create({
      room,
      players: gamers.map((gamer,i) =>
        ({
          team: (i <= 1) ? 1 : 2,
          player: { gamer }
        })
      )
    });

    // View match
    let storedMatch = await Match.findOne({ _id: match._id });
    log(storedMatch);


  } catch(e) {
    console.error(e)
  } finally {
    mongoose.disconnect()
  }

})()

选项 4

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/matches';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const roomSchema = new Schema({
  name: String
});

const gamerSchema = new Schema({
  name: String
})

const playerSchema = new Schema({
  match: { type: Schema.Types.ObjectId, ref: 'Match' },
  team: { type: Number },
  gamer: { type: Schema.Types.ObjectId, ref: 'Gamer' },
  kills: { type: Number, required: true, default: 0 },
  deaths: { type: Number, required: true, default: 0 },
  isAlive: { type: Boolean, default: true }
});

const matchSchema = new Schema({
  room: { type: Schema.Types.ObjectId, ref: 'Room' },
  finished: { type: Boolean, default: false }
},{
  toJSON: { virtuals: true }
});

matchSchema.virtual('players', {
  ref: 'Player',
  localField: '_id',
  foreignField: 'match'
});

const Room = mongoose.model('Room', roomSchema);
const Gamer = mongoose.model('Gamer', gamerSchema);
const Player = mongoose.model('Player', playerSchema);
const Match = mongoose.model('Match', matchSchema);


// log helper
const log  = data => console.log(JSON.stringify(data, undefined, 2));


// main
(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // clean models
    await Promise.all(
      Object.entries(conn.models).map(([k, m]) => m.deleteMany())
    )

    // Insert to set up

    let room = await Room.create({ name: 'Room1' });

    let gamers = await Gamer.insertMany(
      [ ...Array(4)].map((e,i) => ({ name: 'Gamer' + (i+1) }))
    );

    // Create match first
    let match = await Match.create({ room });


    // Add players with match reference
    let [player1, player2, player3, player4] = await Player.insertMany(
      gamers.map(({ _id: gamer }, i) =>
        ({ match, team: (i <= 1) ? 1 : 2, gamer })
      )
    );


    // View match
    let storedMatch = await Match.findOne({ _id: match._id });
    log(storedMatch);

    // View populated - virtual field
    let populated = await Match.findOne({ room: room._id })
      .populate('players')

    log(populated);


  } catch(e) {
    console.error(e)
  } finally {
    mongoose.disconnect()
  }

})()

【讨论】:

  • 非常感谢您提供的材料,这对我非常有用!
  • 选项 1:人口返回:{ _id: [ObjectId] }... 选项 2:像魅力一样工作!我选择那个... 选项 3:也可以,但在我看来迭代它有点复杂... 选项 4:没有尝试有一些我需要研究的概念(虚拟),但我会跨度>
  • @maudev 记录在案。它们都可以按预期使用每种情况的特定代码和示例。我通常什至会在答案中包含输出,但这里有很多内容,答案只能得到 30K 个字符来响应。我相信输出列表只是超过了。在那里演示你如何使用每个表单。
猜你喜欢
  • 2023-01-17
  • 1970-01-01
  • 1970-01-01
  • 2018-06-30
  • 2018-09-05
  • 1970-01-01
  • 1970-01-01
  • 2018-02-20
  • 1970-01-01
相关资源
最近更新 更多