Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot persist an entity with a 1-to-1 reference to another entity #140

Closed
thiagomini opened this issue Sep 8, 2023 · 5 comments
Closed
Assignees
Labels
bug Something isn't working

Comments

@thiagomini
Copy link
Contributor

Describe the bug

  • Given we are using Entity Schema definitions
  • And we have a User entity class with its schema
  • And we have a Profile entity class that has a one-to-one relationship with the User
  • And we persist a new user
  • When we try to create and persist a new Profileentity, passing the user as an attribute
  • Then we got an error: NotNullConstraintViolationException: insert into "profile" ("image_url") values ('https://example.com/image.png') returning "id" - null value in column "user_id" violates not-null constraint

Stack trace

src/mikro-orm/mikro-orm-internal.module.spec.ts > MikroOrmInternalModule > creates a new profile
NotNullConstraintViolationException: insert into "profile" ("image_url") values ('https://example.com/image.png') returning "id" - null value in column "user_id" violates not-null constraint
 ❯ PostgreSqlExceptionConverter.convertException node_modules/@mikro-orm/postgresql/PostgreSqlExceptionConverter.js:24:24
 ❯ PostgreSqlDriver.convertException node_modules/@mikro-orm/core/drivers/DatabaseDriver.js:197:54
 ❯ node_modules/@mikro-orm/core/drivers/DatabaseDriver.js:201:24
 ❯ PostgreSqlDriver.nativeInsertMany node_modules/@mikro-orm/knex/AbstractSqlDriver.js:303:21
 ❯ ChangeSetPersister.persistNewEntity node_modules/@mikro-orm/core/unit-of-work/ChangeSetPersister.js:85:21
 ❯ ChangeSetPersister.executeInserts node_modules/@mikro-orm/core/unit-of-work/ChangeSetPersister.js:29:13
 ❯ ChangeSetPersister.runForEachSchema node_modules/@mikro-orm/core/unit-of-work/ChangeSetPersister.js:68:13
 ❯ UnitOfWork.commitCreateChangeSets node_modules/@mikro-orm/core/unit-of-work/UnitOfWork.js:739:9
 ❯ UnitOfWork.persistToDatabase node_modules/@mikro-orm/core/unit-of-work/UnitOfWork.js:703:13
 ❯ Parser.parseErrorMessage node_modules/pg-protocol/src/parser.ts:369:69
 ❯ Parser.handlePacket node_modules/pg-protocol/src/parser.ts:188:21
 ❯ Parser.parse node_modules/pg-protocol/src/parser.ts:103:30
 ❯ Socket.<anonymous> node_modules/pg-protocol/src/index.ts:7:48
 ❯ Socket.emit node:events:513:28

To Reproduce
Steps to reproduce the behavior:

  1. Clone the minimum reproducible code repository: https://github.com/thiagomini/nest-mikro-orm-example/tree/bug/creating-entity-with-reference
  2. run yarn
  3. run docker compose up -d
  4. run yarn test

Expected behavior
The Profile should be saved correctly

Additional context
Summary of the test case that is failing:

  test('creates a new profile', async () => {
    // Arrange
    const entityManager = testingModule.get(EntityManager).fork();

    const user = new User({
      email: 'user3@mail.com',
      firstName: 'John3',
      lastName: 'Doe3',
      createdAt: new Date(),
      updatedAt: new Date(),
    });
    await entityManager.persistAndFlush(user);

    // Act
    const newProfile = new Profile({
      user: ref(user),
      imageUrl: 'https://example.com/image.png',
    });

    await entityManager.persistAndFlush(newProfile);

    // Assert
    expect(newProfile.id).toBeDefined();
  });

Versions

Dependency Version
node 18.12.1
typescript 5.1.3
mikro-orm next
pg 8.11.2
@thiagomini thiagomini added the bug Something isn't working label Sep 8, 2023
@thiagomini
Copy link
Contributor Author

Additional info below:

Profile Schema

import { BigIntType, EntitySchema } from '@mikro-orm/core';
import { Profile } from './profile.entity';
import { User } from './user.entity';

export const profileSchema = new EntitySchema<Profile>({
  class: Profile,
  tableName: 'profile',
  forceConstructor: true,
  properties: {
    id: {
      type: BigIntType,
      primary: true,
      autoincrement: true,
    },
    user: {
      entity: () => User,
      reference: '1:1',
      mappedBy: 'profile',
      ref: true,
    },
    imageUrl: {
      type: String,
    },
  },
});

User Schema

import { BigIntType, EntitySchema } from '@mikro-orm/core';
import { User } from './user.entity';
import { Address } from './address.entity';
import { Profile } from './profile.entity';

export const userSchema = new EntitySchema<User>({
  class: User,
  tableName: 'user',
  forceConstructor: true,
  properties: {
    id: {
      type: BigIntType,
      primary: true,
      autoincrement: true,
    },
    firstName: {
      type: 'string',
    },
    lastName: {
      type: 'string',
    },
    email: {
      type: 'string',
    },
    addresses: {
      reference: '1:m',
      entity: () => Address,
      mappedBy: 'user',
    },
    profile: {
      reference: '1:1',
      entity: () => Profile,
      inversedBy: 'user',
      nullable: true,
    },
    createdAt: {
      type: 'timestamp',
    },
    updatedAt: {
      type: 'timestamp',
    },
  },
});

⚠️ Apparently, the user's id is not being populated. I have no idea why 🤔

@B4nan
Copy link
Member

B4nan commented Sep 8, 2023

because it is inverse side, as such it does not represent any database column. your schema is not in sync with entity definition apparently, if you expect such column to exist. swap inversedBy with mappedBy (in both entities)

@B4nan B4nan closed this as completed Sep 8, 2023
@thiagomini
Copy link
Contributor Author

thiagomini commented Sep 8, 2023

Hey Martin, thanks for the speedy reply! But can we define mappedBy in both entities? I actually get this error when I do so:

MetadataError: Both User.profile and Profile.user are defined as owning sides, use 'mappedBy' on one of them

Btw I thought the inversedBy was supposed to be used in the owning side. What is it's purpose instead?

@B4nan
Copy link
Member

B4nan commented Sep 8, 2023

No you can't. I told you to swap those, so where you have mappedBy you will have inversedBy and vice versa.

Btw I thought the inversedBy was supposed to be used in the owning side.

Yes, and you had it on the inverse instead. The owning side is where you have the database column.

FYI the inverserBy is actually optional, the owning side does not need to point anywhere, only the inverse side needs to specify the mappedBy option.

@thiagomini
Copy link
Contributor Author

Got it! Thanks, Martin!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants