'TypeORM AfterInsert() can't add userId in another table

I have a user entity:

@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  //...

  @OneToOne(() => UserActive)
  userActive: UserActive;

  @AfterInsert()
  public async handleAfterInsert() {
    const userActive = new UserActive();
    userActive.token = randomString();
    userActive.user = this;
    await getConnection().getRepository(UserActive).save(userActive);
  }
}

and UserActive:

export class UserActive extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({
    length: 25,
    unique: true,
  })
  token: string;

  @OneToOne(() => User)
  @JoinColumn()
  user: User;
}

I need to insert module token and user id into UserActive. this in line userActive.user = this; is object user (of course with id).

When I insert new user, then TypeORM return error:

Key (userId)=(438dc281-3f03-45b0-bee8-76aecc894d14) is not present in table "user".

Of course when i change to:

userActive.user = '438dc281-3f03-45b0-bee8-76aecc894d14'

then everything works correctly.

I'm using Next.js

EDIT:

I insert new user using:

const connection = getConnection().manager;

const user = new User();
user.name = name;
user.email = email;
user.password = await bcrypt.hash(password, 10);
await connection.save(user);

And I change method handleAfterInsert to:

@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;
  
   //...

  @OneToOne(() => UserActive)
  userActive: UserActive;

  @AfterInsert()
  public async handleAfterInsert() {
    const userActive = new UserActive();
    userActive.token = randomString();
    userActive.user = this;
    await getConnection().manager.save(userActive);
  }
}

But Nest returns the same error as before.

EDIT 2:

I found this problem: Retrieving ID in @AfterInsert operation

And I try change my method afterInsert to:

@EventSubscriber()
export class UserSubscriber
  implements EntitySubscriberInterface<User> {
  listenTo() {
    return User;
  }

  async afterInsert(event: InsertEvent<User>) {
    const userActive = new UserActive();
    userActive.token = randomString();
    userActive.user = event.entity;
    await getConnection().getRepository(UserActive).save(userActive);
  }
}

But now nothing is added to table user active, only is added new user to table user.



Solution 1:[1]

I suspect it's because the save(user) and save(userActive) are being done in separate database transactions, so the User has not yet been committed to the database when save(userActive) is executed -- even though it has its id generated, it is not yet committed -- so the save(UserActive) transaction cannot see the new user so you see error "Key (userId=xxx) is not present in table user".

You may be able to fix this but using entity manager instead of repository. You cannot use repository because it is limited to a single entity, but you are dealing here with 2 entities (user & userActive).

Change your 2 saves to use the same entity manager, something like this:

Saving user:

const connection: Connection = await createConnection();
const manager = connection.manager;
var user = new User();
user.Name = "John";
// etc
// N.B. Use entity manager, not repository, to save: 
await manager.save(user);

and likewise saving the userActive record:

@Entity()
export class User extends BaseEntity {
// ...
    @AfterInsert()
    public async handleAfterInsert() {
        const userActive = new UserActive();
        userActive.token = randomString();
        userActive.user = this;
        // N.B. Use entity manager, not repository, here: 
        await getConnection().manager.save(userActive);
    }

UPDATED ANSWER

I tested my answer above and indeed it does not work - if I enable typeorm logging I can see there there are two separate transactions.

I found the answer in the same post as you: further down it says you must enable the 'subscribers' connection option - this is not obvious at all.

I did it like this: Create 'afteruser.ts' in 'subscriber' folder in the project, and make sure this folder is included on the 'subscribers' connection option in ormconfig.json:

{
  // ...
  "logging": true,
  "entities": [ "entity/*.js" ],
  "subscribers": [ "subscriber/*.js" ],
  // ...
}

I suspect the problem in your updated solution is this line, because this creates a new transaction:

await getConnection().getRepository(UserActive).save(userActive);

You need to use the same "entity manager", like this:

await event.manager.getRepository(UserActive).save(userActive);

Solution 2:[2]

I got the same issue, for some reasons listeners like @AfterInsert are executed before the transaction is committed.

To solve that, you need to use subscribers https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md

And do your transactions with entity.manager.getRepository(Entity).find(...)

Solution 3:[3]

You can remove the await keyword and make it async, but it starts another transaction which may lead to unexpected errors if you have high write traffic.

It states in the docs that Note: Do not make any database calls within a listener, opt for subscribers instead.

Make your after-insertion upserts using subscribers, otherwise, listeners may behave unexpectedly, such as starting another transaction before the first one finishes, using subscribers you can kinda intercept transactions before committing, which means if you want to create a child entity after the parent is created, you can do this in the same transaction.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Almaju
Solution 3 Oguz Turkay