You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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映射表实体,存在两个疑问:

  1. 如何让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. 是否存在其他方案可规避该问题?

解决方案

问题1:让TypeORM识别entityId对应的实体类型

你当前的写法没法让TypeORM自动识别动态关联类型,得调整Item实体结构,用多态关联思路实现:

  1. 重构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 });
  }
}
  1. 同步修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:33:29