populate()不返回所有相关的模型字段

时间:2018-11-25 01:43:34

标签: node.js mongodb mongoose

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

这是我的播放器型号:

white space

这是我的Match模型:

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

基本上是两队球员..因此,当我进行一些有关杀伤或死亡的更新时,我需要填充这些值以发送到我的视图。.我以这种方式填充(根据文档):

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' } )

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

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

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

1 个答案:

答案 0 :(得分: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的更改是作为一个单个数组。对于在文档中涉及多个数组的索引,MongoDB确实不满意。查询和更新还有其他原因,信息的聚集也倾向于 single 数组,这是一个更好的主意。

由于数据已经在匹配文档中,因此populate()不需要进行任何操作,因此访问非常简单。

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

因此,在存储中看起来就像从填充结果中所期望的那样。

选项4-虚拟参考

如果要存储在嵌入式数组中的数据太大而无法保留在其中,或者访问模式真正偏爱主要仅与与比赛相关的球员一起使用,(以及更多情况)通常一次只能在一个玩家身上进行操作),那么最有意义的是根本不记录任何match,而是将对match的引用移到{{1 }}:

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()
  }

})()