您的架构和数据不匹配。这里的大问题是存储的项目如下:
"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()
}
})()