typeorm icon indicating copy to clipboard operation
typeorm copied to clipboard

Changing fields in @BeforeSoftRemove and @BeforeRecover seems not persist the data

Open andreyicanpreneur opened this issue 3 years ago • 6 comments

Issue Description

I am attempting to add lightweight audit fields to all of my entities via @CreateDateColumn, @UpdateDateColumn, @DeleteDateColumn as well as @BeforeInsert, @BeforeUpdate, @BeforeSoftRemove and @BeforeRecover. I believe I am doing everything per documentation, however field updates do not go through in my methods decorated with @BeforeSoftRemove and @BeforeRecover.

Expected Behavior

I expect to have execution run through my BeforeSoftRemove and BeforeRecover methods and get the deletedBy field updated.

Actual Behavior

I've confirmed that execution indeed goes through my methods, however the change does not get persisted in the respective database row.

Steps to Reproduce

My code looks something like the following:

export class ... {
    @CreateDateColumn({ name: 'created_on', nullable: true })
    createdOn: Date;

    @Column({ name: 'created_by', nullable: true })
    createdBy: number;

    @UpdateDateColumn({ name: 'updated_on', nullable: true })
    updatedOn: Date;

    @Column({ name: 'updated_by', nullable: true })
    updatedBy: number;

    @DeleteDateColumn({ name: 'deleted_on', nullable: true })
    deletedOn: Date;

    @Column({ name: 'deleted_by', nullable: true })
    deletedBy: number;

    @VersionColumn({ name: 'version', nullable: true })
    version: number;

    @BeforeInsert()
    beforeInsert() {
        this.createdBy = this.getCurrentUserId();
    }

    @BeforeUpdate()
    beforeUpdate() {
        this.updatedBy = this.getCurrentUserId();
    }

    @BeforeSoftRemove()
    beforeSoftRemove() {
        // TODO: This does not work
        this.deletedBy = this.getCurrentUserId();
    }

    @BeforeRecover()
    BeforeRecover() {
        // TODO: This does not work
        this.deletedBy = null;
    }
...
}

I've created a bare bones repo demonstrating the issue here: https://github.com/andreyicanpreneur/typeorm-beforeSoftRemove

My Environment

Dependency Version
Operating System Windows 11 [Version 10.0.22000.778]
Node.js version 16.14.2
Typescript version 4.5.2
TypeORM version 0.3.6

Relevant Database Driver(s)

DB Type Reproducible
aurora-mysql no
aurora-postgres no
better-sqlite3 no
cockroachdb no
cordova no
expo no
mongodb no
mysql no
nativescript no
oracle no
postgres yes
react-native no
sap no
spanner no
sqlite no
sqlite-abstract no
sqljs no
sqlserver no

Are you willing to resolve this issue by submitting a Pull Request?

  • ✖️ Yes, I have the time, and I know how to start.
  • ✖️ Yes, I have the time, but I don't know how to start. I would need guidance.
  • ✖️ No, I don’t have the time, but I can support (using donations) development.
  • ✅ No, I don’t have the time and I’m okay to wait for the community / maintainers to resolve this issue.

andreyicanpreneur avatar Jun 28 '22 10:06 andreyicanpreneur

same result with mysql

ccamba avatar Sep 06 '23 22:09 ccamba

I did try the same using the EventSubscriber events and I get the events. You could try using those instead

import {
  EntitySubscriberInterface,
  EventSubscriber,
  InsertEvent,
  SoftRemoveEvent,
  UpdateEvent,
} from 'typeorm'

@EventSubscriber()
export class ApplicationSubscriber
  implements EntitySubscriberInterface<Entity>
{
  listenTo() {
    return Entity
  }

  afterUpdate(event: UpdateEvent<Entity>) {
    // do something
  }

  afterInsert(event: InsertEvent<Entity>) {
     // do something
  }

  beforeSoftRemove(event: SoftRemoveEvent<Entity>) {
    const { entity } = event
    // do something
  }
}

Reference : https://orkhan.gitbook.io/typeorm/docs/listeners-and-subscribers#event-object

jeet0007 avatar Sep 26 '23 08:09 jeet0007

In memory you can see the impact, it's like the commit was missing. If after calling softRemove, you call save, you see the updated field in the db. Clearly this is a workaround.

ccamba avatar Sep 26 '23 12:09 ccamba

In memory you can see the impact, it's like the commit was missing. If after calling softRemove, you call save, you see the updated field in the db. Clearly this is a workaround.在内存中你可以看到影响,就像提交丢失了一样。如果在调用 softRemove 后,您调用 save,您会在数据库中看到更新的字段。显然这是一个解决方法。

this is a good idea. i resolve this problem. this is my code. thx.

// User Entity
@Entity()
export class User {
  ... 
  @DeleteDateColumn({ type: 'bigint', nullable: true })
  deleted_at?: number;

  @AfterSoftRemove()
  public setDeletedAt() {
    this.deleted_at = dayjs().unix();
  }
}

// User Service
async softRemove(id: number) {
    const user = await this.usersRepository.findOne({ where: { id } });
    if (!user) {
      throw new Error('Not Found User');
    }
    await this.usersRepository.softRemove(user);
    // update database
    await this.usersRepository.save(user);
    return user;
  }

Infiee avatar Dec 25 '23 10:12 Infiee

The problem seems to be the SoftDeleteQueryBuilder src/query-builder/SoftDeleteQueryBuilder.ts which does not add entity field changes to the update query. Neither the EntityManager.recover method nor the EntityManager.softRemove method are updating any fields other than the deleted date field and the updated date field.

As @ccamba and @Infiee already wrote, the only option is to save the entity afterwards manually like this:

const recoveredEntity = await manager.recover(entityToRecover);
recoveredEntity.updatedById = actorUserId;
/* bypass the update timestamp */
await manager.query(
    `UPDATE entity SET updated_by_id = ? WHERE id = ?`,
    [actorUserId, recoveredEntity.id]
);

tobiasorth avatar Apr 22 '24 14:04 tobiasorth