Etienne
Etienne

Reputation: 2287

Nest.js + Mikro-ORM: Collection of entity not initialized when using createQueryBuilder and leftJoin

I'm using Nest.js, and considering migrating from TypeORM to Mikro-ORM. I'm using the nestjs-mikro-orm module. But I'm stuck on something that seems very simple...

I've 3 entities, AuthorEntity, BookEntity and BookMetadata. From my Author module, I try to left join the Book and BookMetadata tables with the createQueryBuilder method. But when running my query, I'm getting an error where Collection<BookEntity> of entity AuthorEntity[3390] not initialized. However columns from the Author table are well retrieved.

My 3 entities:

@Entity()
@Unique({ properties: ['key'] })
export class AuthorEntity {
  @PrimaryKey()
  id!: number;

  @Property({ length: 255 })
  key!: string;

  @OneToMany('BookEntity', 'author', { orphanRemoval: true })
  books? = new Collection<BookEntity>(this);
}

@Entity()
export class BookEntity {
  @PrimaryKey()
  id!: number;

  @ManyToOne(() => AuthorEntity)
  author!: AuthorEntity;

  @OneToMany('BookMetadataEntity', 'book', { orphanRemoval: true })
  bookMetadata? = new Collection<BookMetadataEntity>(this);
}

@Entity()
@Unique({ properties: ['book', 'localeKey'] })
export class BookMetadataEntity {
  @PrimaryKey()
  id!: number;

  @Property({ length: 5 })
  localeKey!: string;

  @ManyToOne(() => BookEntity)
  book!: BookEntity;
}

And the service file where I run my query:

@Injectable()
export class AuthorService {
  constructor(
    @InjectRepository(AuthorEntity)
    private readonly authorRepository: EntityRepository<AuthorEntity>,
  ) {}

  async findOneByKey(props: { key: string; localeKey: string; }): Promise<AuthorEntity> {
    const { key, localeKey } = props;

    return this.authorRepository
      .createQueryBuilder('a')
      .select(['a.*', 'b.*', 'c.*'])
      .leftJoin('a.books', 'b')
      .leftJoin('b.bookMetadata', 'c')
      .where('a.key = ?', [key])
      .andWhere('c.localeKey = ?', [localeKey])
      .getSingleResult();
  }
}

Am I missing something? Might be not related, but I also noticed that there is a special autoLoadEntities: true for TypeORM users using Nest.js. Is there something similar for Mikro-ORM? Thanks ;)

Upvotes: 2

Views: 6321

Answers (1)

Martin Ad&#225;mek
Martin Ad&#225;mek

Reputation: 18389

Mapping of multiple entities from single query is not yet supported, it is planned for v4. You can subscribe here: https://github.com/mikro-orm/mikro-orm/issues/440

In v3 you need to use 2 queries to load 2 entities, which for your use case is much easier without the QB involved.

return this.authorRepository.findOne({ key }, ['books']);

Or you could use qb.execute() to get the raw results and map them yourself, but you would also have to manually alias all the fields to get around duplicities (Author.name vs Book.name), as doing qb.select(['a.*', 'b.*']) will result in query select a.*, b.* ... and the duplicate columns would not be correctly mapped.

https://mikro-orm.io/docs/query-builder/#mapping-raw-results-to-entities

About the autoLoadEntities thing, never heard of that, will take a look how it works, but in general, the nestjs adapter is not developed by me, so if its something only nest related, it would be better to ask on their GH repo.

Or you could use folder based discovery (entitiesDirs).

here is the new example with 3 entities:

return this.authorRepository.findOne({ 
  key,
  books: { bookMetadata: localeKey } },
}, ['books.bookMetadata']);

This will produce 3 queries, one for each db table, but the first one will auto-join books and bookMetadata to be able to filter by them. The condition will be propagated down in the second and third query.

If you omit the populate parameter (['books.bookMetadata']), then only the first query will be fired and you will end up with books not being populated (but the Author will be queried with the joined condition).

Upvotes: 1

Related Questions