Postgres+TypeORM多对多关系无法保存重复实体的问题咨询
问题解答:集换式卡牌游戏卡组中重复卡牌的存储问题
为什么重复卡牌无法被保存?
这不是Postgres或TypeORM的专属限制,而是关系型数据库多对多关系的普遍特性。
多对多关系依赖中间连接表(比如你的deck_cards_card)关联两个实体,TypeORM自动生成的这个连接表,默认会给deckId和cardId的组合添加唯一约束(UNIQUE constraint)。这意味着同一个卡组和卡牌的关联只能存一次,所以哪怕你传入重复的Card实例,TypeORM也会自动去重,只插入唯一的关联记录。
从你提供的SQL日志也能看出来:插入参数里每个cardId只和deckId关联了一次,这就是唯一约束导致的结果。
存储卡组中卡牌数量的最佳方案
要实现同一张卡牌在卡组中可出现多次(最多4张),有两种常用的设计思路:
方案1:用中间实体拆分多对多关系(推荐)
把原来的多对多关系拆成两个一对多关系,通过中间实体(比如DeckCard)来存储卡牌的数量。这种方案能充分利用数据库的约束和查询能力,适合复杂业务场景。
代码示例:
// 中间实体:记录卡组与卡牌的关联及数量 @Entity() export class DeckCard extends BaseEntity { @PrimaryColumn() deckId: number; @PrimaryColumn() cardId: number; @Column({ type: 'int', default: 1 }) quantity: number; @ManyToOne(() => Deck, deck => deck.deckCards) deck: Deck; @ManyToOne(() => Card, card => card.deckCards) card: Card; } // Deck实体修改为一对多关联中间实体 @ObjectType() @Entity() export class Deck extends BaseEntity { // 其他字段... @Field(() => [DeckCard]) @OneToMany(() => DeckCard, deckCard => deckCard.deck, { eager: true }) deckCards!: DeckCard[]; } // Card实体添加反向关联 @Entity() export class Card extends BaseEntity { // 其他字段... @OneToMany(() => DeckCard, deckCard => deckCard.card) deckCards!: DeckCard[]; }
保存卡组时,先合并重复卡牌的数量:
// 处理带重复的卡牌数组,统计每张卡牌的数量 const cardCountMap = new Map<number, number>(); cardsWithDuplicates.forEach(card => { const currentCount = cardCountMap.get(card.id) || 0; cardCountMap.set(card.id, currentCount + 1); }); // 转换为中间实体数组 const deckCards = Array.from(cardCountMap.entries()).map(([cardId, quantity]) => ({ cardId, quantity })); // 保存卡组 const deck = await Deck.create({ // 其他字段... deckCards }).save();
方案2:用JSON字段存储卡牌数量映射
如果业务逻辑简单,不需要对卡牌数量做复杂的数据库查询(比如统计全平台某卡牌的使用次数),可以直接在Deck中加一个JSON字段,存储卡牌ID和数量的映射关系。
代码示例:
@ObjectType() @Entity() export class Deck extends BaseEntity { // 其他字段... @Field(() => JSON) @Column('json') cardQuantities: { [cardId: number]: number }; }
保存时直接构建映射对象:
const cardCountMap = new Map<number, number>(); cardsWithDuplicates.forEach(card => { const currentCount = cardCountMap.get(card.id) || 0; cardCountMap.set(card.id, currentCount + 1); }); const deck = await Deck.create({ // 其他字段... cardQuantities: Object.fromEntries(cardCountMap) }).save();
这种方案实现简单,但缺点是无法用SQL对卡牌数量做过滤、排序等操作,适合小型项目或逻辑简单的场景。
内容的提问来源于stack exchange,提问作者Tyler Dennis
相关产品推荐
相关产品推荐

