Dmitriy Apollonin
Dmitriy Apollonin

Reputation: 1478

TypeORM cascade: true flag does not delete related entities

I can not figure out if it is me, of cascade: true option does not delete children entities when removing parent one?

@Entity()
export class Folder {
  @PrimaryGeneratedColumn()
  public readonly id: number;

  @OneToMany(() => Segment, (segment) => segment.folder, {
    cascade: true,
  })
  public segments: Segment[];
}


@Entity()
export class Segment {
  @PrimaryGeneratedColumn()
  public readonly id: number;

  @ManyToOne(() => Folder, (folder) => folder.segments)
  @JoinColumn()
  public folder: Folder;
}

and then I do

await getRepository(Folder).delete(id);

which gives me an error:

update or delete on table "folder" violates foreign key constraint "FK_12606f83d18e5ae0c7e5693f3fb" on table
"segment"

Also tried to do

const folder = await this.repository.findOneOrFail(id);
repository.remove(folder)
//or 
folder.segments = null;
await this.repository.save(folder);

still error...

I don't want to loop through all children entities and remove them manually. I thought cascade: true should handle it as well as saving or updating. Maybe syntax supposes to be different?

Thanks!

Upvotes: 11

Views: 21135

Answers (6)

Ieuan Uys
Ieuan Uys

Reputation: 59

TLDR: use Repository.softRemove(entityWithRelations)

You want to pass in the response from a query which includes the relations that you hope to cascade the operation on.

...

I tried the { onDelete: 'CASCADE' } but that only worked for doing a hard delete and we need this to do soft-delete in order to be able to restore the entities in the database.

I found that I had to use the soft-remove or remove on the repository instead of delete or soft-delete which do not cascade any database actions to nested/child entities.

this also makes sense since the typeORM options for cascade include 'soft-remove', and not 'soft-delete'.

Upvotes: 0

Muhammad Nadeem
Muhammad Nadeem

Reputation: 1

To cascade delete all the related entities here are the steps. My case is that when I delete a signal, its signalDetail, signalClassificationJoins, signalPostJoin, and the posts should be deleted.

here is the signal entity

@OneToMany(
    () => SignalClassificationJoin,
    (SignalClassificationJoin) => SignalClassificationJoin.signal,
    {
      cascade: true,
      lazy: false,
    }
  )
  classificationDetails: SignalClassificationJoin[];

  @OneToOne(() => SignalDetail, (SignalDetail) => SignalDetail.signal, {
    cascade: true,
    lazy: false,
  })
  signalDetail: SignalDetail;

  @OneToMany(() => SignalPostJoin, (SignalPostJoin) => SignalPostJoin.signal, {
    cascade: true,
    lazy: false,
  })
  signalPostJoin?: SignalPostJoin[];

with the related entities when you are defining the inverse relation do it as

@ManyToOne(() => Signal, (Signal) => Signal.classificationDetails, {
    onDelete: "CASCADE",
})
@JoinColumn({ name: "signal_id" })
signal: Signal;

and finally, delete the signal with the related entities as follows by including all the nested relations you want to delete

const signal = await this.signalRepository.findOne({
  where: { id },
  relations: {
   classificationDetails: true,
   signalDetail: true,
   signalPostJoin: { posts: true },
  },
});

const deletedSignal = await this.signalRepository.remove(signal);

Upvotes: 0

gogoonline
gogoonline

Reputation: 69

- Parent Entity

@OneToMany(type => Comment, comment => comment.parent, { cascade: ["remove"] })
replies: Comment[];

Create cascade option in @OneToMany relation.
@OneToMany relation onDelete option is not needed.

{ cascade: ["remove"] } 

- Child Entity

@ManyToOne(type => Comment, comment => comment.replies, { onDelete: "CASCADE" })
parent: Comment;

Create onDelete option in @ManyToOne relation.
@ManyToOne relation cascade option is not needed.

{ onDelete: "CASCADE" }

Upvotes: 3

Abolfazl Roshanzamir
Abolfazl Roshanzamir

Reputation: 14792

Parent Entity => set cascade to "remove"

// Parent Entity
@OneToMany(type => Comment, comment => comment.parent,{ cascade: ["remove"] })
replies: Comment[];

Child Entity => set onDelete to CASCADE and orphanedRowAction to delete.

// Child Entity
@ManyToOne(type => Comment, comment => comment.replies, {
    onDelete: "CASCADE", orphanedRowAction: 'delete'
})
parent: Comment;

Note

When you want to delete Parent with its children you have to do as follows:

await ParentRepository.delete(parentId)

Upvotes: 6

Niccolò Fanton
Niccolò Fanton

Reputation: 552

This is a "bug" (not sure if it's the the desired behavior) inside the typeorm code, I fixed this issue by using https://www.npmjs.com/package/patch-package because I needed this fix asap

You need to patch the file at node_modules/typeorm/persistence/subject-builder/OneToManySubjectBuilder.js with this code (- is what you need to remove and + is what you need to add):

             var removedRelatedEntitySubject = new Subject_1.Subject({
                 metadata: relation.inverseEntityMetadata,
                 parentSubject: subject,
-                canBeUpdated: true,
+                canBeUpdated: !relation.isNullable,
+                mustBeRemoved: relation.isNullable,

Upvotes: 1

Sascha
Sascha

Reputation: 1849

Cascade only describes what to do with related entities, it doesn't have an effect when the entity itself is deleted. If you want to delete all segments when deleting a folder you need to use onDelete (this is a database feature, otherwise then cascade, which is implemented within TypeORM. This means you need to generate and execute migrations when adding onDelete). So this should work for you:

@Entity()
export class Segment {
  @PrimaryGeneratedColumn()
  public readonly id: number;

  @ManyToOne(() => Folder, (folder) => folder.segments, { onDelete: 'CASCADE' })
  @JoinColumn()
  public folder: Folder;
}

Upvotes: 13

Related Questions