You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免GraphQL解析器中多对多关系的无限递归问题?

多对多关系下GraphQL解析器的类型递归问题解决办法

问题背景

你定义了Player与Match的多对多关系,使用graphql-codegen生成TypeScript类型后,编写Query.players和Query.matches解析器时遇到矛盾:

  • 直接用prisma.player.findMany()返回的对象缺少matches字段,不符合生成的Player类型要求(报错Property 'matches' is missing...)
  • 如果强制关联查询matches,又会触发Match关联的players,陷入无限递归查询

你的schema定义如下:

schema.graphql

scalar Score

type Player {
    id: ID!
    name: String!
    rating: Int!
    matches: [Match!]!
}

enum MatchStatus {
    ONGOING
    COMPLETED
    CANCELLED
}

type Match {
    id: ID!
    players: [Player!]!
    score: Score!
    status: MatchStatus!
}

type Query {
    players: [Player!]!
    player(id: ID!): Player
    matches: [Match!]!
    match(id: ID!): Match
}

schema.prisma

model Player {
  id      String  @id @default(cuid())
  name    String
  rating  Int
  matches Match[]
}

model Match {
  id      String   @id @default(cuid())
  score   String
  status  String
  players Player[]
}

解决方法

方法1:按需加载关联字段(推荐)

利用GraphQL的分层解析特性,根查询只返回基础字段,关联字段交给对应类型的解析器懒加载,同时用类型断言绕过TS的严格检查:

import { Resolvers } from './generated/graphql';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const resolvers: Resolvers = {
  Query: {
    // 根查询仅返回Player基础字段,matches交给Player类型的解析器处理
    players: async () => {
      return prisma.player.findMany() as unknown as Resolvers['Query']['players']['returnType'];
    },
    matches: async () => {
      return prisma.match.findMany() as unknown as Resolvers['Query']['matches']['returnType'];
    },
    // ...其他查询解析器
  },
  Player: {
    // 单独处理Player的matches字段,按需查询关联数据
    matches: async (parent) => {
      return prisma.player.findUnique({ where: { id: parent.id } }).matches();
    },
  },
  Match: {
    // 单独处理Match的players字段,按需查询关联数据
    players: async (parent) => {
      return prisma.match.findUnique({ where: { id: parent.id } }).players();
    },
  },
};

这种方式既避免了一次性查询所有关联数据的性能问题,也解决了类型不匹配问题,符合GraphQL的设计理念。

方法2:调整GraphQL Schema的字段可选性

如果业务允许关联字段为可选,可去掉关联字段的!标记,让生成的TS类型允许这些字段缺失:

type Player {
    id: ID!
    name: String!
    rating: Int!
    matches: [Match] # 去掉!,允许为null或空数组
}

type Match {
    id: ID!
    players: [Player] # 去掉!,允许为null或空数组
    score: Score!
    status: MatchStatus!
}

修改后,prisma.player.findMany()返回的对象会直接符合生成的Player类型,无需额外处理。

方法3:配置GraphQL Codegen映射类型

通过codegen的mappers配置,将GraphQL类型映射为Prisma返回的基础字段类型,让生成的解析器类型自动匹配实际返回的数据:

在你的codegen配置文件(如codegen.ts)中添加如下配置:

import type { Prisma } from '@prisma/client';

export default {
  schema: './schema.graphql',
  generates: {
    './src/generated/graphql.ts': {
      plugins: ['typescript', 'typescript-resolvers'],
      config: {
        // 将GraphQL的Player类型映射为只包含基础字段的Prisma类型
        mappers: {
          Player: Prisma.PlayerGetPayload<{ select: { id: true, name: true, rating: true } }>,
          Match: Prisma.MatchGetPayload<{ select: { id: true, score: true, status: true } }>,
        },
      },
    },
  },
};

重新生成类型后,根查询解析器返回的Prisma基础字段对象会完全符合类型要求,无需类型断言。


内容的提问来源于stack exchange,提问作者marvincyk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 22:30:37