【问题标题】:Foreign key constraint »FK_0e4022833a9efc062c01637e552« cannot be implemented - problem with composite primary key?无法实现外键约束 »FK_0e4022833a9efc062c01637e552« - 复合主键有问题?
【发布时间】:2020-06-12 08:16:39
【问题描述】:

我正在使用 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
}

接下来我有一个 graph 实体。这个持有多个 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,你也必须传入graph id。基本上这些是用例

  • 一个graphNode代表一个模块
  • 每个图有多个 graphNodes
  • graphNode 可以将自己作为后继对象引用(成功/错误)
  • graphNode 后继可以为 null(成功/错误)
  • graphNode 后继者必须是 this 图中已经存在的 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;
}

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

【问题讨论】:

    标签: sql postgresql typeorm


    【解决方案1】:

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

    • Graph 和 Node 彼此之间有一个 ManyToOne。但是根据我从您的解释中收集到的信息,节点仅附加到单个图表,因此在Graph 中,它实际上应该是代表Nodes 列表的OneToMany:
    @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 应该自动采用您指定的每个实体中的主列。

    【讨论】:

      【解决方案2】:

      您在实体中使用外键约束:

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

      并分别在另一个实体中。您收到错误是因为您当前的数据库不满足此规则。因此,最简单的方法是:

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

      这样做将使所有新创建的记录都根据数据库中更新的约束条件工作。希望这对你有用。

      【讨论】:

      • 我克隆了复制库,删除了数据库,创建了一个新的并启动了应用程序。错误仍然存​​在...
      【解决方案3】:

      GraphNode 上有两个主键

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

      这就是为什么 fk 在自引用成功和 errorGraphNode 上不起作用的原因。 删除一个 PK 并使用另一个。

      【讨论】:

      猜你喜欢
      • 2011-01-23
      • 1970-01-01
      • 2015-06-23
      • 2020-04-24
      • 2021-10-03
      • 2013-08-12
      • 2020-07-09
      • 2020-03-16
      相关资源
      最近更新 更多