如何在TypeORM中创建可关联多类型实体的Item映射表?
问题
我希望在TypeORM实体中实现如下数据库设计:一个List可包含任意数量、任意类型的项,当前支持Todo和Hire两种类型,未来可能扩展更多类型。已定义的List、Todo、Hire实体代码如下:
@Entity() export class List { @PrimaryGeneratedColumn() id: number @OneToMany(() => Item, (item) => item.list) items: Item[] @Column({ length: 60, }) title: string } @Entity() export class Todo { @PrimaryGeneratedColumn() id: number @OneToOne(() => Item) item: Item @Column({ length: 60, }) title: string @Column('text') description: string @Column({ default: false }) done: boolean } @Entity() export class Hire { @PrimaryGeneratedColumn() id: number @OneToOne(() => Item) item: Item @Column({ length: 60, }) title: string @Column('datetime') startDate: string @Column('datetime') endDate: string }
现需编写Item映射表实体,存在两个疑问:
- 如何让TypeORM识别
entityId对应的实体类型?当前编写的Item实体代码如下:
@Entity() export class Item { @PrimaryGeneratedColumn() id: number @ManyToOne(() => List, (list) => list.items) @JoinColumn() list: List @Column() entityType: string @OneToOne(() => Todo /* Todo OR Hire */, (itemType) => itemType.item) entity: Todo // Todo OR Hire }
- 是否存在其他方案可规避该问题?
解决方案
问题1:让TypeORM识别entityId对应的实体类型
你当前的写法没法让TypeORM自动识别动态关联类型,得调整Item实体结构,用多态关联思路实现:
- 重构Item实体,拆分关联字段,手动处理类型映射:
@Entity() export class Item { @PrimaryGeneratedColumn() id: number @ManyToOne(() => List, (list) => list.items) @JoinColumn() list: List @Column() entityType: string // 存储实体名称,比如'Todo'或'Hire' @Column() entityId: number // 对应实体的主键ID // 自定义方法,根据类型和ID获取关联实体 async getEntity(connection: Connection) { const repo = connection.getRepository(this.entityType); return repo.findOneBy({ id: this.entityId }); } }
- 同步修改Todo和Hire的反向关联,确保关联关系正确:
// Todo实体调整 @Entity() export class Todo { // ...原有字段 @OneToOne(() => Item, item => item.entity) @JoinColumn() item: Item; } // Hire实体同理 @Entity() export class Hire { // ...原有字段 @OneToOne(() => Item, item => item.entity) @JoinColumn() item: Item; }
这种方式下,查询List的items后,调用getEntity就能拿到具体的Todo或Hire实例。如果想让TypeORM自动处理类型识别,可以结合表继承方案,比手动映射更省心。
问题2:其他规避方案
有两种更优雅的方案可以替代当前的映射表思路:
方案1:单表继承(Table Inheritance)
将Todo和Hire作为Item的子类,共用一张Item表,TypeORM自动添加type字段区分类型:
// 基类Item @Entity() @TableInheritance({ column: { type: "varchar", name: "type" } }) export class Item { @PrimaryGeneratedColumn() id: number @ManyToOne(() => List, (list) => list.items) @JoinColumn() list: List @Column({ length: 60 }) title: string // 提取Todo和Hire的公共字段到基类 } // Todo子类 @ChildEntity() export class Todo extends Item { @Column('text') description: string @Column({ default: false }) done: boolean } // Hire子类 @ChildEntity() export class Hire extends Item { @Column('datetime') startDate: string @Column('datetime') endDate: string } // List实体调整items关联 @Entity() export class List { // ...原有字段 @OneToMany(() => Item, (item) => item.list) items: Item[] // 直接关联基类,查询时自动返回对应子类实例 }
这个方案无需手动维护映射表,TypeORM自动处理类型区分,扩展新类型只需要新增ChildEntity即可。
方案2:多表继承(Joined Table Inheritance)
和单表继承逻辑类似,但每个子类对应独立表,基类存公共字段,子类表存特有字段,通过外键关联:
// 基类Item @Entity() @InheritanceType("JOINED") @DiscriminatorColumn({ name: "type", type: "varchar" }) export class Item { @PrimaryGeneratedColumn() id: number @ManyToOne(() => List, (list) => list.items) @JoinColumn() list: List @Column({ length: 60 }) title: string } // Todo子类 @ChildEntity() export class Todo extends Item { @Column('text') description: string @Column({ default: false }) done: boolean } // Hire子类 @ChildEntity() export class Hire extends Item { @Column('datetime') startDate: string @Column('datetime') endDate: string }
这种方案数据结构更清晰,每个类型的数据单独存储,适合字段差异较大的场景,同样支持自动类型识别和扩展。
内容的提问来源于stack exchange,提问作者publicJorn
相关产品推荐
相关产品推荐

