TypeORM中能否用OR定义外键?Link表通用设计咨询
数据库表设计与TypeORM关联映射疑问
问题背景
我在设计数据库表时遇到了以下疑问,伪代码结构如下:
Node Table { id: number, label: string } Comb Table { id: number, label: string } Link Table { source: // 可为Node表或Comb表的实例 target: // 可为Node表或Comb表的实例 }
请问定义Link表时,是否必须采用如下4个字段的设计?
Link Table { nodesource: Node, nodetarget: Node, combsource: Comb, combtarget: Comb, }
现有实际代码(TypeORM)
export enum EdgeType { NodeToNode = 'NodeToNode', NodeToComb = 'NodeToComb', CombToComb = 'CombToComb', CombToNode = 'CombToNode' } @Entity() export class Link { @PrimaryGeneratedColumn() id: number | undefined // type @Column({ type: 'enum', enum: LinkType, nullable: false, default: LinkType.NodeToNode }) type!: LinkType // Node:source @OneToOne(type => Node) @JoinColumn() nodeSource?: Node // Node:target @OneToOne(type => Node) @JoinColumn() nodeTarget?: Node // Combo:source @OneToOne(type => Comb) @JoinColumn() combSource?: Comb // Combo:target @OneToOne(type => Comb) @JoinColumn() combTarget?: Comb }
能否采用如下更通用的定义方式?希望source和target字段可直接接收Node或Comb实例,但不确定数据库或TypeORM框架是否支持这种写法:
@Entity() export class Link { @PrimaryGeneratedColumn() id: number | undefined @OneToOne(type => Node|Comb) source: Node | Comb @OneToOne(type => Node|Comb) target: Node | Comb }
解答
结论:你给出的通用写法不被TypeORM和关系型数据库支持
原因如下:
- 数据库层面限制:关系型数据库的外键约束只能绑定到单一数据表,一个字段无法同时作为两个不同表的外键,这种模糊的关联在数据库层面无法建立合法约束。
- TypeORM框架限制:
@OneToOne等关联装饰器要求指定明确的单一实体类型,框架无法解析Node|Comb这种联合类型,无法生成正确的表结构、外键约束和SQL查询语句。
替代优化方案
方案1:单表继承(Table Inheritance)
将Node和Comb抽象为同一个父实体,Link直接关联父实体,实现统一的关联逻辑:
@Entity() @TableInheritance({ column: { type: "varchar", name: "entity_type" } }) export class BaseEntity { @PrimaryGeneratedColumn() id: number; @Column() label: string; } @Entity() export class Node extends BaseEntity {} @Entity() export class Comb extends BaseEntity {} @Entity() export class Link { @PrimaryGeneratedColumn() id: number; @OneToOne(() => BaseEntity) @JoinColumn() source: BaseEntity; @OneToOne(() => BaseEntity) @JoinColumn() target: BaseEntity; }
- 优点:结构简洁,无需维护多个关联字段,TypeORM会自动处理子类的类型区分
- 注意:需要数据库支持单表继承逻辑,TypeORM会生成包含所有子类字段的表,也可通过
@ChildEntity实现多表继承
方案2:多态关联(Polymorphic Relations)
通过额外的类型字段区分关联的实体类型,结合多个可选关联字段实现:
@Entity() export class Link { @PrimaryGeneratedColumn() id: number; @Column() sourceId: number; @Column() sourceType: string; // 存储"Node"或"Comb" @Column() targetId: number; @Column() targetType: string; @ManyToOne(() => Node, { nullable: true }) @JoinColumn({ name: "sourceId", referencedColumnName: "id" }) sourceNode?: Node; @ManyToOne(() => Comb, { nullable: true }) @JoinColumn({ name: "sourceId", referencedColumnName: "id" }) sourceComb?: Comb; @ManyToOne(() => Node, { nullable: true }) @JoinColumn({ name: "targetId", referencedColumnName: "id" }) targetNode?: Node; @ManyToOne(() => Comb, { nullable: true }) @JoinColumn({ name: "targetId", referencedColumnName: "id" }) targetComb?: Comb; }
- 优点:无需修改原有Node/Comb实体结构,灵活度高
- 缺点:需要手动维护
sourceType/targetType字段,查询时需根据类型判断读取对应的关联实体
方案3:保留现有4字段+枚举的设计
你当前的写法是合法且可靠的,优点是:
- 外键约束明确,数据库层面能保证数据一致性
- 查询时无需额外判断,直接根据
type字段读取对应的关联字段 - 可通过数据库检查约束或应用层逻辑,保证对应类型下的关联字段非空(比如
NodeToNode类型时,nodeSource和nodeTarget必须非空)
内容的提问来源于stack exchange,提问作者lustre
相关产品推荐
相关产品推荐

