外键约束»FK_0e4022833a9efc062c01637e552«无法实现-复合主键有问题吗?

时间:2020-02-28 08:48:24

标签: sql postgresql typeorm

我正在使用TypeORM,并希望使用当前三个实体设计数据库。我创建了一个用于复制的存储库

Github reproduction repository

更新

我只能用两个小实体复制它

Very small reproduction repo

下面的更多信息


所以首先我有一个 module 实体。这个很小,因为它只存储一些基本字段。

@Entity()
export class Module extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  public id: string;

  // some other fields without references
}

接下来,我有一个实体。这个拥有多个 graphNodes 。因此,您可以有多个图,每个图都有自己的graphNodes。图本身需要知道哪个graphNode是图中的第一个。因此,我尝试设置对graphNode实体的外键引用。

  • 可以有多个图
  • 每个图都有一个graphNode作为起始节点
  • 每个图有多个graphNodes

@Entity()
export class Graph extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  public id: string;

  @Column({ nullable: true })
  public startNodeId?: string;

  @ManyToOne(
    () => GraphNode,
    graphNode => graphNode.id,
  )
  @JoinColumn({ name: 'startNodeId' })
  public startNode: GraphNode;
}

最后一个实体是 graphNodes 实体。每个graphNode在一个图中表示一个模块。它还知道其后继者 successGraphNodeId errorGraphNodeId 。对节点而言,此实体具有复合主键非常重要,因为如果要获取graphNode,则也必须传递图id。基本上就是用例

  • graphNode代表一个模块
  • 每个图有多个graphNodes
  • graphNode可以将自身称为后继(成功/错误)
  • graphNode后继对象可以为null(成功/错误)
  • graphNode的后继者必须是图中
  • 中已经存在的graphNode

@Entity()
export class GraphNode extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  public id: string;

  @PrimaryColumn()
  public graphId: string; // composite key https://github.com/typeorm/typeorm/blob/master/sample/sample27-composite-primary-keys/entity/Post.ts

  @ManyToOne(
    () => Graph,
    graph => graph.id,
  )
  @JoinColumn({ name: 'graphId' })
  public graph: Graph;

  @Column()
  public moduleId: string;

  @ManyToOne(
    () => Module,
    module => module.id,
  )
  @JoinColumn({ name: 'moduleId' })
  public module: Module;

  @Column({ nullable: true })
  public successGraphNodeId?: string;

  @ManyToOne(
    () => GraphNode,
    graphNode => graphNode.id,
  )
  @JoinColumn({ name: 'successGraphNodeId' })
  public successGraphNode?: GraphNode;

  @Column({ nullable: true })
  public errorGraphNodeId?: string;

  @ManyToOne(
    () => GraphNode,
    graphNode => graphNode.id,
  )
  @JoinColumn({ name: 'errorGraphNodeId' })
  public errorGraphNode?: GraphNode;
}

运行该应用程序时,很不幸出现此错误

QueryFailedError: Foreign key constraint »FK_0e4022833a9efc062c01637e552« cannot be implemented
    at new QueryFailedError (...\references-reproduction\node_modules\typeorm\error\QueryFailedError.js:11:28)
    at Query.callback (...\references-reproduction\node_modules\typeorm\driver\postgres\PostgresQueryRunner.js:176:38)
    at Query.handleError (...\references-reproduction\node_modules\pg\lib\query.js:145:17)
    at Connection.connectedErrorMessageHandler (...\references-reproduction\node_modules\pg\lib\client.js:214:17)
    at Connection.emit (events.js:223:5)
    at Socket.<anonymous> (...\references-reproduction\node_modules\pg\lib\connection.js:134:12)
    at Socket.emit (events.js:223:5)
    at addChunk (_stream_readable.js:309:12)
    at readableAddChunk (_stream_readable.js:290:11)
    at Socket.Readable.push (_stream_readable.js:224:10)

看来我的实体设计不正确。有人知道这里有什么问题吗?如果您需要更多信息,或者我应该更新我的复制存储库,请告诉我。

预先感谢


更新

我能够用两个小实体重现它。图之间只有一个关系

@Entity()
export class Graph extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  public id: string;

  @Column({ nullable: true })
  public startNodeId?: string;

  @ManyToOne(
    () => Node,
    node => node.id,
  )
  @JoinColumn({ name: 'startNodeId' })
  public node: Node;
}

及其节点

@Entity()
export class Node extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  public id: string;

  @PrimaryColumn()
  public graphId: string;

  @ManyToOne(
    () => Graph,
    graph => graph.id,
  )
  @JoinColumn({ name: 'graphId' })
  public graph: Graph;
}

如前所述,问题似乎依赖于复合主键。我不想将Node的graphId主列转换为基本列,因为这可能是不好的数据库设计...

3 个答案:

答案 0 :(得分:2)

我认为您的问题不在组合键上。对我来说,您的模型有几个缺陷:

  • GraphNode彼此之间有一个ManyToOne。但是从您的解释中我发现,一个节点仅附加到一个图上,因此在Graph中,它实际上应该是一个OneToMany,代表Nodes的列表:
@OneToMany(() => Node)
public nodes: Node[];
  • 注释ManyToOne的第二个参数应该是反向属性,但是您使用它来引用主键。这是您应该拥有的:
class Graph {
  @OneToMany(() => Node, node => node.graph)
  public nodes: Node[];
}

class Node {
  @ManyToOne(() => Graph, graph => graph.nodes)
  public graph: Graph;
}

对于JoinColumn,typeorm应该自动采用您指定的在每个实体中都有主列的内容。

答案 1 :(得分:0)

您在GraphNode上有两个主键

@PrimaryGeneratedColumn('uuid')
public id: string;

@PrimaryColumn()
public graphId: string;

这就是为什么fk不能使用自引用的success-和errorGraphNode。 删除一个PK,然后再处理另一个。

答案 2 :(得分:0)

您在实体中使用外键约束的方式如下:

@ManyToOne(
    () => GraphNode,
    graphNode => graphNode.id,
)

并分别位于另一个实体中。由于当前数据库不满足此规则,因此出现错误。因此,最简单的方法是:

  1. 删除当前数据库
  2. 创建一个新数据库
  3. 更改配置以连接到新创建的数据库。

根据数据库中更新的约束,所有新创建的记录都将执行此操作。希望这对您有用。