【问题标题】:setting up a one to many relationship for a self referencing table为自引用表设置一对多关系
【发布时间】:2020-06-07 05:25:33
【问题描述】:

我有一个Project 实体,它有一个非自动生成的 id 字段和一个后继字段。这个继任者就是接下来的项目。但也许没有以下项目,所以这可能是空的。

@Entity()
export class Project extends BaseEntity {
  @PrimaryColumn({ unique: true })
  public id: string;

  @OneToMany(() => Project, project => project.id, { nullable: true })
  public successorId?: string;
}

通过创建新项目时

public createProject(id: string, successorId?: string): Promise<Project> {
  const project: Project = new Project();
  project.id = id;
  project.successorId = successorId;
  return project.save();
}

我需要处理多种情况。

  • 传入一个已经存在的id:

    这不会引发错误。它只是覆盖现有实体。

  • undefined 传递给successorId

    然后代码可以正常工作,但它不会创建带有nullsuccessorId 列。该列根本不存在于数据库中。

  • idsuccessorId 传递相同的ID(这应该是可能的):

    TypeORM 报错

    TypeError: 无法读取未定义的属性“joinColumns”

  • 传入另一个现有项目的successorId

    我遇到了和上面一样的错误

  • 传入一个不存在的项目的successorId

    我遇到了和上面一样的错误

那么我该如何解决呢?我认为我的实体设计似乎是错误的。基本上应该是

  • 一个项目可能有一个继任者
  • 一个项目可以是许多项目的继任者

如果有人能帮忙就太好了!


更新

我也试过了

@OneToMany(() => Project, project => project.successorId, { nullable: true })
@Column()
public successorId?: string;

但每当我想调用 createProject 方法时,我都会收到此错误

QueryFailedError:“successorId”列中的空值违反非空值 约束

还有这个

@OneToMany(() => Project, project => project.successorId, { nullable: true })
public successorId?: string;

但是我收到了这个错误

TypeError:relatedEntities.forEach 不是函数

【问题讨论】:

    标签: sql postgresql orm nestjs typeorm


    【解决方案1】:

    请尝试此解决方案

    @Entity()
    export class Project extends BaseEntity {
      @PrimaryColumn({ unique: true })
      public id: string;
    
      @Column({ nullable: true })
      public successorId?: string;
    
      @ManyToOne(() => Project, project => project.id)
      @JoinColumn({ name: "successorId" })
      public successor?: Project;
    }
    

    public successor?: Project; - 属性用于建立实体之间的关系(在这种情况下为同一实体)。必须将相关实体指定为属性类型,因为 TypeORM 使用此类型来确定目标实体并构建关系元数据。你可以阅读更多关于 TypeORM 关系here

    public successorId?: string; - 属性只是一个“提取的”连接列。当您使用ManyToOne 关系时,TypeORM 会自动在数据库中创建一个名为propertyName + referencedColumnName 的列(在本例中为successorId)。但是您不能在代码中使用此列,因为它仅在表中定义,而不在您的类中定义。如果您需要在类中定义此列(用于进一步保存或显示),您可以创建一个属性并用@Column 装饰器标记它。属性名称必须与表中的连接列名称相同。详细描述here

    创建具有相同 id 的实体只会覆盖现有的实体

    这是预期的行为。当您尝试使用现有 Id 保存实体时,TypeORM 会将其识别为更新,而不是创建

    【讨论】:

    • 我测试了您的解决方案,我认为这似乎可行。您介意解释为什么需要两个字段吗?它知道如何通过字符串将该字段映射到变量?
    • 唯一仍然失败的案例是第一部分。创建具有相同 id 的实体只会覆盖现有的 ..
    • 是的,我可以用这个示例 repo 重现它 :) github.com/mhcomp/…
    • 是的,在 TypeORM 中,@OneToMany@ManyToOne 的反面,没有@ManyToOne 就无法存在。 @OneToMany 也不是必需的,在这种情况下可以省略。例如,如果您需要“在后继者之前的项目”或换句话说“将此项目指定为后继者的项目”,那么您需要使用 OneToMany 作为反面。它看起来像@OneToMany(() =&gt; Project, project =&gt; project.successor) public projects: Project[]
    • 顺便说一句,我不知道你对继任者的要求。如果可以在许多项目中指定一个继任者,那么@ManyToOne 是正确的方法。否则,如果一个继任者只能关注一个项目,那么您应该使用@OneToOne 关系。
    【解决方案2】:

    你可以试试这个

    export class RolesPermission {
      @PrimaryGeneratedColumn('uuid')
      @PrimaryColumn({ type: 'varchar', length: 36, default: 'UUID()' })
      entityId?: string;
    
      @Column({ unique: true })
      name: string;
    
      @OneToMany(() => RolesPermission, (rolePermission) => rolePermission.parent)
      rolePermissions?: RolesPermission[];
    
      @ManyToOne(() => RolesPermission, (rolePermission) => rolePermission.rolePermissions, { nullable: true, createForeignKeyConstraints: false })
      parent?: RolesPermission;
    
      @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
      createdAt?: Date;
    
      @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' })
      updatedAt?: Date;}
    

    【讨论】:

      猜你喜欢
      • 2017-09-25
      • 1970-01-01
      • 1970-01-01
      • 2015-12-20
      • 1970-01-01
      • 2018-08-19
      • 2017-02-05
      相关资源
      最近更新 更多