typeorm: ManyToMany relations save throws "Violation of PRIMARY KEY constraint" when a property from combined primary key from relation entity is an other relation property

Issue Description

While saving an entity with ManyToMany relation the first save works great. But if I change the ManyToMany relation children, the exception Violation of PRIMARY KEY constraint 'PK_c83e17466322282074e0360a96e'. Cannot insert duplicate key in object 'dbo.city_zips_zip'. The duplicate key value is (de, 60311, 41a3433e-f36b-1410-84db-00668d135ea6) throws.

For testing I remove the constraint in the relation table…

In the query log I can see, that you first insert all relations and after inserting you remove all relations. I guess the order is wrong.

INSERT INTO "city_zips_zip"("zipCountryCode", "zipCode", "cityId") VALUES (@0, @1, @2), (@3, @4, @5) -- PARAMETERS: [{"value":"de","type":"nvarchar","params":["2"]},{"value":"60311","type":"nvarchar","params":[]},{"value":"41A3433E-F36B-1410-84DB-00668D135EA6","type":"uniqueidentifier","params":[]},{"value":"de","type":"nvarchar","params":["2"]},{"value":"35781","type":"nvarchar","params":[]},{"value":"41A3433E-F36B-1410-84DB-00668D135EA6","type":"uniqueidentifier","params":[]}]

DELETE FROM "city_zips_zip" WHERE (("cityId" = @0 AND "zipCountryCode" = @1 AND "zipCode" = @2) OR ("cityId" = @3 AND "zipCountryCode" = @4 AND "zipCode" = @5)) -- PARAMETERS: ["41A3433E-F36B-1410-84DB-00668D135EA6","de","35781","41A3433E-F36B-1410-84DB-00668D135EA6","de","60311"]

Expected Behavior

Excepted on sync for ManyToMany I want to only remove relations they aren’t in the new array and only insert new relations.

Actual Behavior

At the moment the save method throws an exception, that an insert in relation table can’t execute because a violation on primary key constraint because you insert before delete all relations.

Steps to Reproduce

Create 2 entities:


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

  @Column()
  caption: string;

  @RelationId('zips')
  zipCodes: string[];

  @ManyToMany(() => Zip, zip => zip.cities, {
    cascade: true,
  })
  @JoinTable()
  zips: Zip[];
}

Entity()
export class Country {
  @PrimaryColumn({ length: 2 })
  code: string;

  @Column()
  caption: string;

  @OneToMany(() => Zip, zip => zip.country)
  zips: Zip[];
}

@Entity()
export class Zip {
  @RelationId('country')
  @PrimaryColumn({ length: 2 })
  countryCode: string;

  @ManyToOne(() => Country, country => country.zips, {
      createForeignKeyConstraints: true,
  })
  @JoinColumn({ name: 'countryCode' })
  country: Country;

  @PrimaryColumn()
  code: string;

  @RelationId('cities')
  cityIds: string[];

  @ManyToMany(() => City, city => city.zips)
  @JoinTable({ name: 'city_zips_zip' })
  cities: City[];
}

Execute following:


await connection.getRepository(Country).save({
  code: 'de',
  caption: 'Germany',
});

// create works great 
const city = await connection.getRepository(City).save({
  id: 'INSERT_ID_OF_TEST_CITY_HERE',
  caption: 'Test city',
  zips: [
    {
      countryCode: 'de',
      code: '60311',
    },
    {
      countryCode: 'de',
      code: '35781',
    },
  ],
}, {
  reload: true,
});

// but if we update the relations the exception throws

await connection.getRepository(City).save({
  id: city.id,
  zips: [
    {
      countryCode: 'de',
      code: '60311',
    },
  ],
});

My Environment

Dependency Version
Operating System Linux in Docker
Node.js version v14.19.1
Typescript version 4.3.5
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 yes
nativescript no
oracle no
postgres no
react-native no
sap no
spanner no
sqlite yes
sqlite-abstract no
sqljs no
sqlserver yes

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.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 4
  • Comments: 16 (9 by maintainers)

Most upvoted comments

@paraskapoorMM Sorry for bothering you, guys. After deeper investigation I found that the way of passing only permissions’ ids is the problem in my case:

return await this.rolesRepository.save(
    ...restRoleCreateInput,
    permissions: permissionIds.map(permissionId => ({ id: permissionId })),
);

When passing “full” entities everything works correctly. I wrongly thought this is related to this issue, because on previous versions (<0.3.0) it worked fine with PostgreSQL.

@paraskapoorMM Sorry for bothering you, guys. After deeper investigation I found that the way of passing only permissions’ ids is the problem in my case:

return await this.rolesRepository.save(
    ...restRoleCreateInput,
    permissions: permissionIds.map(permissionId => ({ id: permissionId })),
);

When passing “full” entities everything works correctly. I wrongly thought this is related to this issue, because on previous versions (<0.3.0) it worked fine with PostgreSQL.

reached this issue with exact same problem. 0.3.7. saving M2M entity with repo.save({id: item.id, manyItems: item.manyItems}) SOMETIMES causes duplicate entry error on junction table. I’ve never been able to reproduce this case intentionally cuz it’s always happens randomly.

your comment saved my life!