TypeGoose创建文档时子文档转为ObjectId问题求助
TypeGoose子文档仅存储ObjectId问题排查与解决
问题现象
使用TypeGoose创建Game文档时,传入的scenes包含完整子文档数据(含sceneName、dmNotes、characters等字段),但数据库插入日志显示scenes仅被存储为ObjectId数组,而非完整嵌入的子文档:
测试数据代码:
const testData= { "title": "Test Game 1", "scenes": [ { "sceneName": "Northern Milestone", "dmNotes": [ { "content": "A crossroads where an path branches off towards a town with hostels. Much of the game's map can be seen with limited detail from the great plateau this crossroads sits atop." } ], "characters": [ { "characterName": "Ian", "isPlayerCharacter": true, }, { "characterName": "Wanderful" } ] }, { "sceneName": "North Woods", "characters": [], "dmNotes": [ { "content": "A thick woods, perceptive characters may smell faint smoke." } ] } ], "items": [] } mongoose.set("debug", true); await GameModel.create(testData);
数据库插入日志:
Mongoose: games.insertOne({ title: 'Test Game 1', templateChildren: [], scenes: [ { _id: ObjectId("64efc2a0eb4110d73787c7ff") }, { _id: ObjectId("64efc2a0eb4110d73787c800") } ], items: [], _id: ObjectId("64efc2a0eb4110d73787c7fe"), __v: 0}, {})
相关Schema定义
export class Character { @prop({required: true, default: false}) isPlayerCharacter!: boolean; @prop({required: true, default: 'Unnamed Character'}) characterName!: string; @prop({required: true, default: false}) template!: boolean; @prop({type: () => [DMNote], default: []}) dmNotes!: DMNote[]; @prop({required: true, default: new Stats()}) stats!: Stats; @prop({required: true, default: new SkillStats()}) skills!: SkillStats; @prop({type: () => [Item], default: []}) items!: Item[]; @prop({required: true, default: 0}) sceneId!: number; @prop({required: true, default: 0}) skillStatsId!: number; @prop({ref: () => User, required: false}) player?: Ref<User>; } export class SceneClass { @prop({required: true, default: 'Unnamed Scene'}) sceneName!: string; @prop({required: true, default: false}) template!: boolean; @prop({type: () => [Character], default: []}) characters!: Character[]; @prop({type: () => [DMNote], default: []}) dmNotes!: DMNote[]; @prop({type: () => [Item], default: []}) items!: Item[]; } export class Game { @prop() title?: string; @prop() isTemplate!: boolean; @prop({ref: () => Game}) templateChildren?: Ref<Game>[]; @prop({ref: () => Game}) templateParent?: Ref<Game>; @prop({type: () => [SceneClass], required: true, default: []}) scenes!: SceneClass[]; @prop({type: () => [Item]}) items: Item[] = []; @prop({ref: () => User}) owner?: Ref<User>; public getCharacters(this: DocumentType<Game>) { return this.scenes.map((scene) => scene.characters || []).flat(); } public findCharacterByName(this: DocumentType<Game>, characterName: string) { return this.getCharacters().find((character) => character.characterName === characterName); } public async getUsersCharacterInThisGame(this: DocumentType<Game>, user: DocumentType<User>) { return this.getCharacters().find((character) => character.player?._id.toHexString() === user._id.toHexString()); } public async getSceneWithUser(this: DocumentType<Game>, user: DocumentType<User>) { const playedCharacter = await this.getUsersCharacterInThisGame(user); if (!playedCharacter) return undefined; return this.scenes.find((scene) => scene.characters.find((character) => character.characterName === playedCharacter.characterName)) } } export class User { @prop({required: true, default: '', unique: true}) userName!: String; @prop({type: () => [MessageClass], required: true, default: []}) sentMessages?: MessageClass[]; @prop({type: () => [MessageClass], required: true, default: []}) receivedMessages!: MessageClass[]; }
问题原因
核心原因是SceneClass被意外注册为独立的Mongoose模型。当一个类被getModelForClass注册为模型后,TypeGoose/Mongoose会默认将该类型的数组字段视为引用关系,只存储ObjectId而非嵌入完整子文档。
解决方案
- 删除SceneClass的独立模型注册:检查代码中是否存在
getModelForClass(SceneClass)这类注册语句,将其删除。SceneClass仅需作为Game类的子文档类型存在,无需单独注册模型。 - 显式强制嵌入模式:若确实需要
SceneClass同时作为独立模型和嵌入子文档,可在Game类的scenes字段配置中添加ref: false,明确指定为嵌入模式:
@prop({ type: () => [SceneClass], required: true, default: [], ref: false }) scenes!: SceneClass[];
- 验证字段兼容性:确保测试数据字段与Schema定义匹配,比如
SceneClass的template字段有默认值,测试数据未传该字段不影响存储,但必填字段缺失可能引发其他问题。
内容的提问来源于stack exchange,提问作者pixelpax
相关产品推荐
相关产品推荐

