在 TypeORM 与 GraphQL 的多对多关系上使用数据加载器,查询多对多

Posted

技术标签:

【中文标题】在 TypeORM 与 GraphQL 的多对多关系上使用数据加载器,查询多对多【英文标题】:Using a dataloader on TypeORMs many-to-many relation with GraphQL, query many-to-many 【发布时间】:2021-02-20 13:07:03 【问题描述】:

我开始使用 TypeORM + type-graphql 并尝试实现不同的用例。

QueueUser 具有多对多关系。

我的问题是我想检索给定队列 ID 列表的用户列表:

队列实体

@ObjectType()
@Entity()
export class Queue extends BaseEntity 
  @Field(() => Int)
  @PrimaryGeneratedColumn()
  id!: number

  @Field(() => [User])
  @ManyToMany(() => User, (user) => user.adminOfQueues,  onDelete: "CASCADE" )
  @JoinTable()
  admins!: User[]

用户实体

@ObjectType()
@Entity()
export class User extends BaseEntity 
  @Field(() => Int)
  @PrimaryGeneratedColumn()
  id!: number

  @Field(() => [Queue],  nullable: true )
  @ManyToMany(() => Queue, (queue) => queue.admins)
  adminOfQueues!: Queue[]

队列解析器

@Resolver(() => Queue)
export class QueueResolver 
  @FieldResolver(() => User)
  async admins(
    @Root() queue: Queue,
    @Ctx()  userFromQueueLoader : MyContext
  )   
    const q = await Queue.findOne(queue.id,  relations: ["admins"] )

    const admins = q.admins?.map?.((user) => user.id)

    return User.find(
      where: 
        id: In(admins),
      ,
    )
  

  @Query(() => Queue)
  async queues(): Promise<Queue> 

    const qb = getConnection()
      .getRepository(Queue)
      .createQueryBuilder("q") // alias
      .orderBy("q.createdAt", "DESC")

    const queues = await qb.getMany()

    return queues
  

^ 工作正常,但是我想使用数据加载器,因为据我了解,n+1 问题会在获取所有队列时困扰我(并且需要分别在每个队列上查询数据库中的管理员用户)。

所以在管理员 FieldResolver 中我会像这样使用它:

  @FieldResolver(() => User)
  async admins(
    @Root() queue: Queue,
    @Ctx()  userFromQueueLoader : MyContext
  ) 
    const users = await userFromQueueLoader.load(queue.id)

    return users

但是,我不知道如何在给定数据加载器中的 queueId 列表的情况下检索管理员用户列表

// [queueId] > batched queueIds
// => [User] > should return a list of users where user.adminOfQueues contains a queueId

export const createUserFromQueueLoader = () =>
  new DataLoader<number, User>(async (queueIds) => 
    const users = await User.find(
      join: 
        alias: "userQueue",
        innerJoinAndSelect: 
          adminOfQueues: "userQueue.adminOfQueues",
        ,
      ,
      where:  adminOfQueues: In(queueIds as number[]) , // << can't compare 2 arrays, how to achieve that?
    )

    const userIdToUser: Record<number, User> = 
    users.forEach((user: User) => 
      const usersQueues = user.adminOfQueues
      usersQueues.forEach((userQueue) => 
        const userQueueId = userQueue.id

        userIdToUser[userQueueId] = userIdToUser[userQueueId]
          ? [...userIdToUser[userQueueId], user]
          : [user]
      )
    )

    return queueIds.map((queueId) => userIdToUser[queueId])
  )

以下也无济于事:

 const users = await User.find(
      relations: ['adminOfQueues'],
      where:  adminOfQueues: In(queueIds as number[]) , // << can't compare 2 arrays, how to achieve that?
    )

这如何可行?必须有一种方法可以查询给定多对多关系的 id 列表的所有用户,不是吗?

非常感谢您的帮助 :) 谢谢

【问题讨论】:

【参考方案1】:

不要从用户存储库加载,而是尝试从队列存储库加载,即

const queues = await Queue.find(
  relations: ['admins'],
  where:  id: In(queueIds) ,
)

然后你可以像这样提取用户:

const queueMap = new Map<number, User[]>()
queues.forEach(queue =>
    queueMap.set(
        queue.id,
        queue.admins
    )
)

return queueIds.map(queueId => queueMap.get(queueId) as User[])

不理想,但仍然要快得多,并且只获取一次而不是 N。

【讨论】:

以上是关于在 TypeORM 与 GraphQL 的多对多关系上使用数据加载器,查询多对多的主要内容,如果未能解决你的问题,请参考以下文章

如何创建如何在 typeorm 中创建多对多关系,[NestJS]

GraphQL Apollo 中的多对多关系

GraphQL 中的多对多关系

typeORM 多对多关系不同情况的处理

TypeORM:使用自定义属性查询多对多

与关联对象在同一张表上的多对多关系