Search code examples
mikro-orm

Cannot insert into column "Id" error while trying to insert entity that has a nested parent entity


I have 2 entities with One to Many relationship in my Mikro-Orm (v3.6.15) model (connected to Postgresql - pg v8.3.0):

Uygulama (Parent)

@Entity({ tableName: "Uygulamalar", collection: "Uygulamalar" })
export class Uygulama {
  @PrimaryKey({ fieldName: "Id" })
  id!: number;
  @Property({ fieldName: "Adi" })
  adi!: string;
  @Property({ fieldName: "Kod" })
  kod!: string;
  @Property({ fieldName: "UygulamaSahibi" })
  uygulamaSahibi!: string;
  @Property({ fieldName: "createdAt" })
  createdAt = new Date();
  @Property({ fieldName: "updatedAt", onUpdate: () => new Date() })
  updatedAt = new Date();
  @OneToMany({ entity: () => Modul, mappedBy: "rootUygulama", cascade: [] })
  moduller = new Collection<Modul>(this);
}

Modul (Child)

export class Modul {
  @PrimaryKey({ fieldName: "Id" })
  id!: number;
  @Property({ fieldName: "Adi" })
  adi!: string;
  @Property({ fieldName: "Kod" })
  kod!: string;
  @Property({ fieldName: "createdAt" })
  createdAt = new Date();
  @Property({ fieldName: "updatedAt", onUpdate: () => new Date() })
  updatedAt = new Date();
  @ManyToOne({ entity: () => Uygulama, joinColumn: "UygulamaId", cascade: [],})
  rootUygulama!: Uygulama;
  @OneToMany({ entity: () => Ekran, mappedBy: "rootModul", orphanRemoval: true,})
  ekranlar = new Collection<Ekran>(this);
}

I have a rest endpoint (Expressjs) to create Modul object from posted Http request body :

router.post("/", async (req, res) => {
  const modul = DI.modulRepository.create(req.body);
  await DI.modulRepository.persistAndFlush(modul);
  res.send(modul);
});

When I try to post JSON object below to create a new Modul (rootUygulama object is already in the database):

{
    "adi": "Deneme Modülü 3",
    "kod": "DM1",
    "rootUygulama": {
        "id": 66,
        "adi": "Deneme Uygulaması",
        "kod": "DU",
        "uygulamaSahibi": "xxxxxx",
        "createdAt": "2020-07-24T21:18:47.874Z",
        "updatedAt": "2020-07-24T21:18:47.874Z",
        "moduller": [
        ]
    }
}

I get error :

[query] insert into "Uygulamalar" ("Adi", "Id", "Kod", "UygulamaSahibi", "createdAt", "updatedAt") values ('Deneme Uygulaması', 66, 'DU', 'szengin', '2020-07-25 00:18:47.874', '2020-07-25 00:18:47.874') returning "Id" [took 6 ms]
node_modules/mikro-orm/dist/utils/Logger.js:22
[query] rollback
node_modules/mikro-orm/dist/utils/Logger.js:22
(node:14344) UnhandledPromiseRejectionWarning: error: insert into "Uygulamalar" ("Adi", "Id", "Kod", "UygulamaSahibi", "createdAt", "updatedAt") values ($1, $2, $3, $4, $5, $6) returning "Id" - cannot insert into column "Id"
    at Parser.parseErrorMessage (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\parser.js:278:15)
    at Parser.handlePacket (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\parser.js:126:29)
    at Parser.parse (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\parser.js:39:38)
    at Socket.<anonymous> (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\index.js:8:42)
    at Socket.emit (events.js:311:20)
    at Socket.EventEmitter.emit (domain.js:482:12)
    at addChunk (_stream_readable.js:294:12)
    at readableAddChunk (_stream_readable.js:275:11)
    at Socket.Readable.push (_stream_readable.js:209:10)
    at TCP.onStreamRead (internal/stream_base_commons.js:186:23)
<node_internals>/internal/process/warning.js:32
(node:14344) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
<node_internals>/internal/process/warning.js:32
(node:14344) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

When I send JSON as below object is created and inserted to database successfuly

{
    "adi": "Deneme Modülü 3",
    "kod": "DM1",
    "rootUygulama": 66
}

Even if I set empty cascade array to relationship attribute repository tries to insert parent object and fails.

Am I missing something in configuration?

Edit:

For my Typescript client how will I define rootUygulama property?

export interface Modul {
  id: number;
  adi: string;
  kod: string;
  createdAt: Date;
  updatedAt: Date;
  rootUygulama: Uygulama;
  ekranlar: Array<Ekran>;
}

Should it be like

rootUygulama: Uygulama | number;

Solution

  • This is not about cascading, the behaviour is correct. When you pass just the PK, it is considered as existing entity, if you pass an object, it is considered as new entity. It's not about the PK being set or not, if you want that behaviour, you need to program it yourself.

    MikroORM works based on change set tracking, so only managed objects (entities loaded from database) can produce update queries. If you want to fire update query for the rootUygulama object, then use em.nativeUpdate(). If you want to still send the payload with it as object, but you care just about the PK, you could also explicitly merge that entity to the EM (that way it becomes managed, just like if you have loaded it from the db).

    const modul = DI.modulRepository.create(req.body);
    
    // if we see PK, we merge, so it won't be considered as new object
    if (modul.rootUygulama.id) {
      DI.em.merge(modul.rootUygulama);
      // here we could also fire the `em.nativeUpdate(Uygulama, modul.rootUygulama);` 
      // to fire an update query, but then you should use `em.transactional()` to have that update query inside the same TX as the flush
    }
    
    await DI.modulRepository.persistAndFlush(modul);
    res.send(modul);
    

    Btw I would strongly suggest not to disable cascading unless you truly understand what you are doing, as the defaults are to cascade merge and persist, which you usually want/need.