Skip to content

Commit e296063

Browse files
growmsblemoine
andauthored
fix: ManyToMany ER_DUP_ENTRY error (#10343)
* fix: ensure comparing EntityMapIds in `buildForSubjectRelation` I have found the issue (for me at least). In the method `buildForSubjectRelation` of `ManyToManySubjectBuilder`, in order to know if it is needed to insert the relation rows `databaseRelatedEntityIds` is compared with `relatedEntityRelationIdMap` (line 154). The problem is that `databaseRelatedEntityIds` is not always an "EntityIdMap". If one is overloading the entity in the `afterLoad` event of an EventSubscriber for example the line 102 : `const databaseRelatedEntityValue = relation.getEntityValue(subject.databaseEntity);` will return an "enhanced" object with additional properties. To fix this, in this commit, we ensure that `databaseRelatedEntityIds` is containing an "EntityIdMap". Closes #5704 * refactor: describe.only -> describe removes describe.only. * style: npm run format format code. * Update issue-5704.ts refactor: typo on post title --------- Co-authored-by: blemoine <[email protected]>
1 parent b8af97a commit e296063

File tree

5 files changed

+132
-2
lines changed

5 files changed

+132
-2
lines changed

src/persistence/subject-builder/ManyToManySubjectBuilder.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,17 @@ export class ManyToManySubjectBuilder {
9898

9999
// if subject don't have database entity it means all related entities in persisted subject are new and must be bind
100100
// and we don't need to remove something that is not exist
101-
if (subject.databaseEntity)
102-
databaseRelatedEntityIds = relation.getEntityValue(
101+
if (subject.databaseEntity) {
102+
const databaseRelatedEntityValue = relation.getEntityValue(
103103
subject.databaseEntity,
104104
)
105+
if (databaseRelatedEntityValue) {
106+
databaseRelatedEntityIds = databaseRelatedEntityValue.map(
107+
(e: any) =>
108+
relation.inverseEntityMetadata.getEntityIdMap(e),
109+
)
110+
}
111+
}
105112

106113
// extract entity's relation value
107114
// by example: categories inside our post (subject.entity is post)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
Column,
3+
ManyToMany,
4+
Entity,
5+
PrimaryGeneratedColumn,
6+
} from "../../../../src"
7+
import { Post } from "./Post"
8+
9+
@Entity()
10+
export class Category {
11+
@PrimaryGeneratedColumn()
12+
id: number
13+
14+
@Column()
15+
name: string
16+
17+
@ManyToMany(() => Post, (o) => o.categories)
18+
posts!: Promise<Post[]>
19+
20+
addedProp: boolean = false
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {
2+
Column,
3+
Entity,
4+
JoinTable,
5+
ManyToMany,
6+
PrimaryGeneratedColumn,
7+
} from "../../../../src"
8+
import { Category } from "./Category"
9+
10+
@Entity()
11+
export class Post {
12+
@PrimaryGeneratedColumn()
13+
id: number
14+
15+
@Column()
16+
title: string
17+
18+
@ManyToMany(() => Category, (o) => o.posts, {
19+
cascade: true,
20+
})
21+
@JoinTable()
22+
categories!: Promise<Category[]>
23+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import "reflect-metadata"
2+
import {
3+
closeTestingConnections,
4+
createTestingConnections,
5+
reloadTestingDatabases,
6+
} from "../../utils/test-utils"
7+
import { DataSource } from "../../../src"
8+
import { Post } from "./entity/Post"
9+
import { Category } from "./entity/Category"
10+
import { assert } from "chai"
11+
12+
describe("github issues > #5704 Many-to-many gives error ER_DUP_ENTRY everytime I save. This one also related to inverseJoinColumn.", () => {
13+
let connections: DataSource[]
14+
before(
15+
async () =>
16+
(connections = await createTestingConnections({
17+
entities: [__dirname + "/entity/*{.js,.ts}"],
18+
subscribers: [__dirname + "/subscriber/*{.js,.ts}"],
19+
enabledDrivers: ["mysql"],
20+
})),
21+
)
22+
beforeEach(() => reloadTestingDatabases(connections))
23+
after(() => closeTestingConnections(connections))
24+
25+
it("should work as expected", () =>
26+
Promise.all(
27+
connections.map(async (connection) => {
28+
const postName = "post for issue #5704"
29+
const catName = "cat for issue #5704"
30+
31+
let post1 = await connection.manager.findOne(Post, {
32+
where: { title: postName },
33+
})
34+
35+
let category1 = await connection.manager.findOne(Category, {
36+
where: { name: catName },
37+
})
38+
39+
if (!category1) {
40+
category1 = new Category()
41+
category1.name = catName
42+
await connection.manager.save(category1)
43+
}
44+
45+
if (!post1) {
46+
post1 = new Post()
47+
post1.title = postName
48+
post1.categories = Promise.resolve([category1])
49+
await connection.manager.save(post1)
50+
}
51+
52+
const categoryTest = await connection.manager.findOne(
53+
Category,
54+
{
55+
where: { name: catName },
56+
},
57+
)
58+
assert.isTrue(categoryTest instanceof Category)
59+
60+
post1.categories = Promise.resolve([categoryTest as Category])
61+
62+
// This is the line that causes the error "QueryFailedError: ER_DUP_ENTRY: Duplicate entry '1-1' for key 'PRIMARY'" with previous code
63+
await connection.manager.save(post1)
64+
}),
65+
))
66+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Category } from "../entity/Category"
2+
import { EntitySubscriberInterface, EventSubscriber } from "../../../../src"
3+
4+
@EventSubscriber()
5+
export class CategorySubscriber implements EntitySubscriberInterface<Category> {
6+
listenTo() {
7+
return Category
8+
}
9+
10+
async afterLoad(entity: Category): Promise<any> {
11+
entity.addedProp = true
12+
}
13+
}

0 commit comments

Comments
 (0)