type-graphql-dataloader icon indicating copy to clipboard operation
type-graphql-dataloader copied to clipboard

Using TypeormLoader with Lazy fields causes N+1 issue

Open MWhite-22 opened this issue 2 years ago • 3 comments

I am trying to find a way to get entity relations in other internal functions, no just through a resolver.

For example:

  • Two Entities (users and companies) are joined by a third entity (user_company_link)
  • When resolving User -> User_Company_Link -> Company through a gql resolver, everything works exactly as expected. Dataloader is triggered and the result is only 3 queries against the DB
  • When trying to resolve a single User for a specific UCL through an internal request (example below), the User is undefined.
  • Turning the User on a UCL into a Promise<User> works for my internal requests as long as I await the UCL.User, but now the resolver has the N+1 problem again as it looks like it is skipping the dataloader

Examples:

//User.entity.ts
export class User {
    @Field()
    name: string

    @Field(() => [UserCompanyLink])
    @OneToMany(() => UserCompanyLink, UCL=>UCL.user)
    @TypeormLoader()
    companyProfiles: UserCompanyLink[]
}

//UserCompanyLink.entity.ts
export class UserCompanyLink {
    @Field()
    company_title: string

    @Field()
    company_email: string

    @Field(() => User)
    @ManyToOne(() => User, U => U.companyProfiles)
    @TypeormLoader()
    user: User
}


//EmailService.ts
export class EmailService {
//...

    async sendEmail(UCL: UserCompanyLink){
        const user = UCL.user // <- this is undefined as typeorm hasn't loaded anything

        //alternative if UCL.user is Promise<User>
        const user = await UCL.user // <- this works as intended here, but now gql resolvers don't use the dataloader

        // Use the user.name in the email body, but use the company_email from UCL
    }
}

Any advice would be hugely helpful. Thanks!

MWhite-22 avatar Aug 11 '21 17:08 MWhite-22

I dove deeper into the src code and see all the decorators use UseMiddleware from 'typegraphql' so they will only fire on resolvers.

MWhite-22 avatar Aug 11 '21 18:08 MWhite-22

You look understand it correctly. If you directly accessed to a lazy property, it works as it is.

slaypni avatar Aug 11 '21 23:08 slaypni

You look understand it correctly. If you directly accessed to a lazy property, it works as it is.

Yes, but turning on Lazy properties causes either N+1 problem, or a very strange infinite loop issue where promises keep trying to resolver themselves and spawn new promises until the whole system crashes.

MWhite-22 avatar Aug 12 '21 02:08 MWhite-22