Changing fields in @BeforeSoftRemove and @BeforeRecover seems not persist the data
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.
same result with mysql
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
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.
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;
}
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]
);