typeorm: Update not work for ManyToMany relationship

Issue type:

[ ] question [ x] bug report [ ] feature request [ ] documentation issue

Database system/driver:

[ ] cordova [ ] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [x ] postgres [ ] sqlite [ ] sqljs [ ] react-native [ ] expo

TypeORM version:

[x ] latest [ ] @next [ ] 0.x.x (or put your version here)

Steps to reproduce or a small repository showing the problem:

@Entity()
export class Image {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    isRemoved: boolean = false;

}

@Entity()
export class Post {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @ManyToMany(type => Image, { eager: true, cascade: ["update"] })
    @JoinTable()
    images: Image[];

}

        const img1 = new Image();
        img1.name = "kids";
        await connection.manager.save(img1);

        const img2 = new Image();
        img2.name = "future";
        await connection.manager.save(img2);

        const img3 = new Image();
        img3.name = "changed";
        await connection.manager.save(img3);

        let post = new Post();
        post.title = "about kids";
        post.images = [img1, img2];
        post = await connection.manager.save(post);

        const postdata: any = await connection.getRepository(Post).findOne(post.id);

        expect(postdata).to.not.be.empty;
        expect(postdata.images[0].id).to.be.equal(1);
        expect(postdata.images[1].id).to.be.equal(2);

        let updjson = {"id": post.id, "title": "about other kids", "images": [{"id": img3.id, "name": img3.name}]};
        // const updpost = connection.getRepository<Post>(Post).create(updjson);
        // await connection.getRepository(Post).save(updjson);
        await connection.getRepository(Post).update(post.id, updjson);


        const postdata2: any = await connection.getRepository(Post).findOne(post.id);
        expect(postdata2).to.not.be.empty;
        expect(postdata2.images).to.be.an("array").with.length(1);
        expect(postdata2.images[0].id).to.be.equal(1);

Given example crashes with QueryFailedError: column “postId” of relation “post” does not exist

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 48
  • Comments: 25 (4 by maintainers)

Most upvoted comments

Hi, also faced with this issue. I’ve looked into the code and found out that inserting relational data in many-to-many case is not implemented for repository.update operations, but you can update entities with many-to-many relations via repository.save, so your example should works after small modification:

...
const postdata: any = await connection.getRepository(Post).findOne(post.id);

expect(postdata).to.not.be.empty;
expect(postdata.images[0].id).to.be.equal(1);
expect(postdata.images[1].id).to.be.equal(2);

postdata.title = "about other kids";
postdata.images.push(img3);

await connection.getRepository(Post).save(postdata);

In API says:

save - Saves a given entity or array of entities. If the entity already exist in the database, it is updated. If the entity does not exist in the database, it is inserted. It saves all given entities in a single transaction (in the case of entity, manager is not transactional). Also supports partial updating since all undefined properties are skipped.

This issue is open since many years, kindly provide some update if it is something work in progress or hasn’t started yet? It seems to be occurring for multiple databases and not just limited to PostgreSQL.

Please give bugs priority over new features. We are waiting for this to be fixed for so long.

Not even a single update about this issue, it still on with 0.2.29, and in fact the workaround would be use the repository.save() method

Also bumped into this issue. Using save() instead of update() works for me too, and updatedAt works as usual.

Using .save() does not seem to update the updatedAt (UpdateDateColumn) attribute thinking

update() should definitely be updating UpdateDateColumns, if someone can make a reproducible test please send in a new issue.

Question is, should update work?

update() is meant to be a high performance UPDATE query at the cost of not doing all the magic persistence stuff of save(). Unfortunately Many-to-Many relations become a bit more difficult to deal with because of that, since they make their own junction table that you don’t have direct access to.

The solution to that is either to make your own junction table (and use a Many-to-One on each side), or to use RelationQueryBuilder.add().

Using .save() does not seem to update the updatedAt (UpdateDateColumn) attribute 🤔

I replaced : await this.shopService.update(id, updateShopDto ) with:

shop.name = updateShopDto.name;
shop.phone = updateShopDto.phone;
shop.is_active = updateShopDto.is_active;
shop.is_special = updateShopDto.is_special;
shop.categories= updateShopDto.descriptioncategories; // many to many relation
return await this.shopService.save(shop);

and it worked! Though I hope to find a smarter solution than this

You can make new newPost = this.await connection.getRepository(Post).create(); newPost.id = post.id newPost.images = [{“id”: img3.id, “name”: img3.name}] this.await connection.getRepository(Post).save(newPost);

it may work.

I am also getting this exact same problem when using update.

Question is, should update work?

Absolutely it should!

Also, not exclusive to postgres, got into this problem with MySQL too. Doesn’t seem to be driver related then.

I’m getting an awful problem where when I add something to the many-to-many list and save, it deletes all the previous entries. I’m absolutely baffled as to why but this is a dangerous problem.

Was having the same problem because it fails silently and most of the replies here didn’t work. It was driving me absolutely nuts. After some research I found this method. Using a User as an example entity.

await getRepository(UserEntity)
.createQueryBuilder()
.relation(UserEntity, 'tasks')
.of(existingUser)
.addAndRemove(dto.tasks, existingUser.tasks)

This will delete all entries in the junction table that match the provided existing user and replace for the new ones. It’s relatively fast but probably won’t save you from executing two queries. Please note you’ll still have to call update/save to change the existingUser WITHOUT the many-to-many portion of the DTO. To circle this one, I’ve made an if that checks if a “tasks” update is present in the DTO and after running the query above I call delete dto.tasks and close the if. TL;DR: one query to update the many-to-many column, another for the rest. It’s a dangerous problem indeed and to make things worse it’s a silent one.

Still get this issue, after some workaround, I used to use this way to kinda solve the issue temporarily (probably). For example for the user and role many-to-many relations.

@Entity()
export class Role {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  name: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @ManyToMany(() => User, (user: User) => user.roles)
  users: User[];
}
@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  username: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @ManyToMany(() => Role, (role: Role) => role.users, { cascade: true })
  @JoinTable({ name: 'users_roles' })
  roles: Role[];
}

And then I used DTO for the update process such:

export class UpdateUserRequestDto {
    @IsUUID('4')
    id: string;
  
    @IsNotEmpty()
    @IsString()
    username: string;
  
    @IsArray()
    @ArrayMinSize(1)
    @IsUUID('4', { each: true })
    roleIds: Role[];
  }

Finally, the update process only uses the save() method:

    await this.usersRepository.save({
      ...updateUserRequestDto,
      roles: await this.rolesRepository.findByIds(updateUserRequestDto.roleIds),
      updatedAt: new Date(Date.now()),
    });

There’s still room for improvements but using this approach is kinda simple for me.