在 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 并尝试实现不同的用例。
Queue
与 User
具有多对多关系。
我的问题是我想检索给定队列 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 的多对多关系上使用数据加载器,查询多对多的主要内容,如果未能解决你的问题,请参考以下文章