7
votes

I'm trying to figure out the best way (or best-practice way) to resolve the lazy relationships in my Nestjs TypeORM postgresql app.

I've defined some OneToMany and ManyToOne relationships in the entities as lazy, and when querying for an array of one of these entities, the best solution I have found so far is a huge mess of promise rosolving.

My Customer entity (customer.entity.ts):

import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm';
import { IsEmail } from 'class-validator';
import { ReservationDate } from '../reservation_date/reservation_date.entity';

@Entity()
export class Customer {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Index()
    @Column()
    @IsEmail()
    email: string;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @CreateDateColumn()
    createDate: Date;

    @UpdateDateColumn()
    updateDate: Date;

    @OneToMany(type => ReservationDate, reservationDate => reservationDate.customer)
    reservationDates: Promise<ReservationDate[]>;
}

and my ReservationDate entity (reservation_date.entity.ts):

import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne } from 'typeorm';
import { Customer } from '../customer/customer.entity';

@Entity()
export class ReservationDate {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    date: Date;

    @CreateDateColumn()
    createDate: Date;

    @UpdateDateColumn()
    updateDate: Date;

    @ManyToOne(type => Customer, customer => customer.reservationDates, { onDelete: 'CASCADE' })
    customer: Promise<Customer>;
}

I need to retrieve an array of ReservationDates, and send that off somewhere, but I need it to resolve the customer field (eg eager-load that relationship). This is what "works":

const reservationDates: ReservationDate[] = await this.reservationDateRepository
    .createQueryBuilder('reservationDate')
    .leftJoinAndSelect('reservationDate.customer', 'customer')
    .getMany();

// Same result as query builder above
// const reservationDates: ReservationDate[] = await this.reservationDateRepository
//     .find({
//         relations: ['customer'],
//         take: 5
//     });

console.log(reservationDates[0].car);      // => Promise { <pending> }
console.log(reservationDates[0].__car__);  // => Car { id: string, owner... }

// this is where it gets ugly
const reservationDatesWithResolvedRelationships = await Promise.all(
    reservationDates.map(async (resDate: ReservationDate) => {
        const withResolved: ReservationDateRepoObject = { ...resDate };

        withResolved.customer = await resDate.customer;

        return withResolved;
    })
);

console.log(reservationDatesWithResolvedRelationships[0].car);  // => Car { id: string, owner... }

// send off the JSON-like object here

I feel like there should be a way to do the joins and force an eager load, but neither the query builder or repository.find methods seem to do this with the way I have it set up.

I've also tried adding { lazy: true } and { eager: true } to the relationships in the entities, but nothing changed.

EDIT:

Still not much luck yet, however I was able to hack together a pseudo-solution.

private parseRaw = name => raw => {
    const parsed = new ReservationDate();
    Object.entries(raw).forEach(([typeKey, value]) => {
        const [type, key] = typeKey.split('_');
        if (type === name) {
            parsed[key] = value;
        } else {
            parsed[type] = parsed[type] || {};
            parsed[type][key] = value;
        }
    });
    return parsed;
};

let reservationDates: ReservationDate[] = (await this.reservationDateRepository
    .createQueryBuilder('reservationDate')
    .leftJoinAndSelect('reservationDate.customer', 'customer')
    .limit(5)
    .getRawMany()).map(this.parseRaw('reservationDate'));

Super ugly hack, but it converts the raw, snake_case db results to the camelCase json object I need to send...

1

1 Answers

-1
votes

You need to use find() in order to achieve eager loading,

Eager relations only work when you use find* methods. If you use QueryBuilder eager relations are disabled and have to use leftJoinAndSelect to load the relation. Eager relations can only be used on one side of the relationship, using eager: true on both sides of relationship is disallowed.

Docs here