diff --git a/packages/core/src/unit-of-work/UnitOfWork.ts b/packages/core/src/unit-of-work/UnitOfWork.ts index 681881a4cf54..1086df852f2a 100644 --- a/packages/core/src/unit-of-work/UnitOfWork.ts +++ b/packages/core/src/unit-of-work/UnitOfWork.ts @@ -101,7 +101,7 @@ export class UnitOfWork { wrapped.__meta.relations.forEach(prop => { if (Utils.isPlainObject(data[prop.name as string])) { - data[prop.name as string] = Utils.getPrimaryKeyValues(data[prop.name as string], wrapped.__meta.primaryKeys, true); + data[prop.name as string] = Utils.getPrimaryKeyValues(data[prop.name as string], prop.targetMeta!.primaryKeys, true); } }); wrapped.__originalEntityData = data; diff --git a/tests/features/unit-of-work/GH4129.test.ts b/tests/features/unit-of-work/GH4129.test.ts new file mode 100644 index 000000000000..9f7f8c98270e --- /dev/null +++ b/tests/features/unit-of-work/GH4129.test.ts @@ -0,0 +1,109 @@ +import { Collection, Entity, LoadStrategy, ManyToOne, OneToMany, PrimaryKey, Property, SimpleLogger } from '@mikro-orm/core'; +import { MikroORM } from '@mikro-orm/sqlite'; +import { mockLogger } from '../../helpers'; + +@Entity() +class Channel { + + @PrimaryKey() + id!: number; + + @Property() + name!: string; + + @OneToMany(() => DeviceChannel, item => item.channel, { orphanRemoval: true }) + links = new Collection(this); + +} + +@Entity() +class Device { + + @PrimaryKey() + id!: number; + + @Property() + serial!: string; + + @OneToMany(() => DeviceChannel, item => item.device, { orphanRemoval: true }) + links = new Collection(this); + +} + +@Entity() +class DeviceChannel { + + @ManyToOne({ entity: () => Device, primary: true }) + device!: Device; + + @ManyToOne({ entity: () => Channel, primary: true }) + channel!: Channel; + + @Property() + lastTime!: Date; + +} + +let orm: MikroORM; + +beforeAll(async () => { + orm = await MikroORM.init({ + entities: [Device, Channel, DeviceChannel], + dbName: ':memory:', + loggerFactory: options => new SimpleLogger(options), + loadStrategy: LoadStrategy.JOINED, + }); + await orm.schema.refreshDatabase(); +}); + +afterAll(async () => { + await orm.close(true); +}); + +beforeEach(async () => { + await orm.schema.clearDatabase(); + + const chn = orm.em.create(Channel, { id: 1, name: 'ChannelOne', links: [] }); + const dev1 = orm.em.create(Device, { id: 2, serial: 'DeviceOne', links: [] }); + const link1 = orm.em.create(DeviceChannel, { device: dev1, channel: chn, lastTime: new Date() }); + chn.links.add(link1); + + await orm.em.flush(); + orm.em.clear(); +}); + +test('GH 4129 (1/3)', async () => { + const channel = await orm.em.findOneOrFail(Channel, { name: 'ChannelOne' }, { populate: ['links'] }); + + for (const link of channel.links) { + link.lastTime = new Date(1678803173316); + } + + const mock = mockLogger(orm); + await orm.em.flush(); + expect(mock.mock.calls[1][0]).toBe('[query] update `device_channel` set `last_time` = 1678803173316 where (`device_id`, `channel_id`) in ((2, 1))'); +}); + +test('GH 4129 (2/3)', async () => { + const channel = await orm.em.findOneOrFail(Channel, { name: 'ChannelOne' }, { populate: ['links.device', 'links.channel'] }); + + for (const link of channel.links) { + link.lastTime = new Date(1678803173316); + } + + const mock = mockLogger(orm); + await orm.em.flush(); + expect(mock.mock.calls[1][0]).toBe('[query] update `device_channel` set `last_time` = 1678803173316 where (`device_id`, `channel_id`) in ((2, 1))'); +}); + +test('GH 4129 (3/3)', async () => { + const channel = await orm.em.findOneOrFail(Channel, { name: 'ChannelOne' }, { populate: ['links.device'] }); + + for (const link of channel.links) { + link.lastTime = new Date(1678803173316); + } + + const mock = mockLogger(orm); + await orm.em.flush(); + expect(mock.mock.calls[1][0]).toBe('[query] update `device_channel` set `last_time` = 1678803173316 where (`device_id`, `channel_id`) in ((2, 1))'); +});