如何在Prisma中扁平化多对多联结表查询,优化GraphQL结构?
解决方案
要实现你想要的扁平化GraphQL查询结构,需要从GraphQL类型定义(TypeDefs)和Resolver逻辑两方面入手,同时如果涉及联结表的额外字段,还要确保Prisma模型同步更新。下面分两种场景详细说明:
场景1:仅扁平化languages层级(无联结表额外字段)
第一步:更新GraphQL类型定义
首先需要修改你的Race类型,把嵌套的races_languages替换为直接的languages字段,匹配你期望的查询结构:
type Race { id: Int! name: String! languages: [Language!]! # 新增这个字段,替代原来的races_languages } type Language { id: Int! name: String! }
第二步:修改Resolver逻辑
有两种优雅的方式实现数据转换:
方式A:在Query Resolver中直接处理返回数据
在查询races后,手动将races_languages中的languages提取出来,扁平化结构:
Query: { races: async (root, args, { db }) => { // 从数据库获取包含关联数据的种族列表 const rawRaces = await db.race.findMany({ include: { races_languages: { include: { languages: true } }, }, }); // 转换数据结构,扁平化层级 return rawRaces.map(race => ({ ...race, languages: race.races_languages.map(rl => rl.languages), // 可选:移除原有的races_languages字段,避免返回多余数据 races_languages: undefined, })); }, },
方式B:为Race类型单独添加languages字段的Resolver
这种方式更符合GraphQL的职责分离原则,让每个字段的Resolver只负责自己的逻辑:
// 先保留原来的Query resolver,不需要修改查询逻辑 Query: { races: (root, args, { db }) => db.race.findMany({ include: { races_languages: { include: { languages: true } }, }, }), }, // 新增Race类型的languages字段Resolver Race: { languages: (parent) => { // parent是单个Race对象,包含已查询的races_languages数组 return parent.races_languages.map(rl => rl.languages); }, },
场景2:包含联结表的额外字段
如果races_languages表有额外字段(比如somethingFromRacesLanguages),需要先同步更新Prisma模型,再调整GraphQL类型和Resolver:
第一步:更新Prisma模型
在RaceLanguage模型中添加额外字段:
model RaceLanguage { race_id Int language_id Int somethingFromRacesLanguages String // 新增的联结表字段 languages Language @relation(fields: [language_id], references: [id]) races Race @relation("racesToraces_languages", fields: [race_id], references: [id]) @@id([race_id, language_id]) @@map("races_languages") }
更新后记得运行prisma migrate dev同步数据库,再执行prisma generate更新Prisma客户端。
第二步:更新GraphQL类型定义
需要定义一个中间类型来承载联结表字段和语言信息,再修改Race类型的languages字段:
// 定义关联类型,包含联结表字段和语言信息 type RaceLanguageDetail { somethingFromRacesLanguages: String! name: String! # 直接返回语言名称,或者返回完整的Language对象 // 也可以返回完整Language:language: Language! } type Race { id: Int! name: String! languages: [RaceLanguageDetail!]! } type Language { id: Int! name: String! }
第三步:修改Resolver逻辑
同样可以用方式B来实现,在Race的languagesResolver中组合联结表字段和语言数据:
Race: { languages: (parent) => { return parent.races_languages.map(rl => ({ somethingFromRacesLanguages: rl.somethingFromRacesLanguages, name: rl.languages.name, // 如果需要完整Language对象,就返回: // language: rl.languages })); }, },
这样就能完美支持你期望的两种查询结构了!
内容的提问来源于stack exchange,提问作者Nicolas SEPTIER
相关产品推荐
相关产品推荐

