Search code examples
postgresqltypeormtypeorm-datamapper

Queries in AfterUpdate are not working as expected in TypeORM


I have one entity in TypeORM containing an array of child other entities:

MyEntity
  children (OneToMany)

When I remove one child (e.g. with ID = 2) and save this entity, an update is triggered as expected by the framework in the child entities table:

UPDATE "my_children_table" SET "myEntityId" = null WHERE "id" = 2 

I also have a subscriber listening to the after update event that is also being triggered as expected. However, the update does not delete the entry. The entry is now orphaned with a null relation ID. In the example below, I am using the subscriber to cleanup the orphaned entities:

@EventSubscriber()
export class MyChildSubscriber implements EntitySubscriberInterface<MyChildEntity> {
  public listenTo(): string | Function {
    return MyChildEntity;
  }

  public async afterUpdate(event: LoadEvent<MyChildEntity>) {
    console.log('It works! The event is triggered as expected!');

    const builder = event.connection
      .createQueryBuilder()
      .delete()
      .from(MyChildEntity)
      .where('"myEntityId" IS NULL');

    return builder.execute();
  }
}

However, when the query above runs, the update did not run yet, so nothing is removed from the database. I was expecting the delete to remove the orphaned rows.

According to TypeORM logs, the queries are executed in the following order:

START TRANSACTION
UPDATE "my_children_table" SET "myEntityId" = $2 WHERE "id" = $1 -- PARAMETERS: [2,null]
DELETE FROM "my_children_table" WHERE "myEntityId" IS NULL
COMMIT

If I run the same transaction directly in my database (I am using PostgreSQL), everything work as expected, but TypeORM behaves as the DELETE was running before the UPDATE.

Could you help me to understand what I am doing wrong?


Solution

  • I solved it by committing the current transaction (so the data is saved to the database) and starting a new transaction, so TypeORM will commit this new transaction later. I returned my query execution (a Promise) to ensure TypeORM will wait for it:

    public async afterUpdate(event: LoadEvent<MyChildEntity>) {
      await event.queryRunner.commitTransaction();
      await event.queryRunner.startTransaction();
      return event.manager
        .createQueryBuilder()
        .delete()
        .from(MyChildEntity)
        .where('"myEntityId" IS NULL')
        .execute();
    }