C#客户端GraphQL Mutation调用失败,求错误原因解析
问题背景
服务端基于C#实现了GraphQL的UpdateWebService Mutation接口,通过Postman调用可正常更新Web服务,但C#客户端使用变量参数的方式调用时持续失败,改为硬编码参数后可正常工作,需排查变量方式出错的原因。
服务端代码
UpdateWebService方法
public WebServiceDTO UpdateWebService(WebServiceDTO req){}
MutationType配置
public class MutationType : ObjectType<Mutation> { protected override void Configure(IObjectTypeDescriptor<Mutation> descriptor) { descriptor.Field(m => m.UpdateWebService(default)).Type<NonNullType<WebServiceType>>() .Argument("req", arg => arg.Type<WebServiceInputType>()).Name("UpdateWebService"); descriptor.Field(m => m.RemoveWebService(default)).Type<NonNullType<StringType>>() .Argument("id", arg => arg.Type<NonNullType<IdType>>()).Name("RemoveWebService"); } }
WebServiceInputType配置
public class WebServiceInputType : InputObjectType<WebServiceDTO> { protected override void Configure(IInputObjectTypeDescriptor<WebServiceDTO> descriptor) { descriptor.Field(x => x.WebServiceId).Type<NonNullType<IdType>>(); descriptor.Field(x => x.Name).Type<NonNullType<StringType>>(); descriptor.Field(x => x.Delay).Type<IntType>(); descriptor.Field(x => x.Bundle).Type<BooleanType>(); descriptor.Field(x => x.KeepSequence).Type<BooleanType>(); descriptor.Field(x => x.RetryCount).Type<IntType>(); descriptor.Field(x => x.RetryDelay).Type<IntType>(); descriptor.Field(x => x.HistoryHours).Type<IntType>(); descriptor.Field(x => x.Enabled).Type<BooleanType>(); descriptor.Field(x => x.Instant).Type<BooleanType>(); descriptor.Field(x => x.DestinationId).Type<StringType>(); descriptor.Field(x => x.WebServiceGroupId).Type<StringType>(); descriptor.Field(x => x.ElementIds).Type<ListType<StringType>>(); descriptor.Field(x => x.CallIds).Type<ListType<StringType>>(); } }
最初的客户端代码(变量传参失败)
public async Task<WebServiceDTO> UpdateWebServiceGraph(WebServiceDTO webService) { var graphQLClient = new GraphQLHttpClient("http://localhost:81/graphql", new NewtonsoftJsonSerializer()); var query = @" mutation UpdateWebService($req: WebServiceDTOInput!) { UpdateWebService(req: $req) { webServiceId name delay bundle keepSequence retryCount retryDelay historyHours enabled instant destinationId webServiceGroupId elementIds callIds } }"; var variables = new { req = webService }; var request = new GraphQLRequest { Query = query, Variables = variables }; try { var response = await graphQLClient.SendMutationAsync<WebServiceDTO>(request); }catch(Exception ex) { var iets = ex; } return webService; }
修改后的客户端代码(硬编码参数成功)
public async Task<WebServiceDTO> UpdateWebServiceGraph3(WebServiceDTO webService) { var graphQLClient = new GraphQLHttpClient("http://localhost:81/graphql", new NewtonsoftJsonSerializer()); var mutation = @" mutation { UpdateWebService(req: { webServiceId: """ + webService.WebServiceId + @""", name: """ + webService.Name + @""", delay: " + webService.Delay + @", bundle: " + webService.Bundle.ToString().ToLower() + @", keepSequence: " + webService.KeepSequence.ToString().ToLower() + @", retryCount: " + webService.RetryCount + @", retryDelay: " + webService.RetryDelay + @", historyHours: " + webService.HistoryHours + @", enabled: " + webService.Enabled.ToString().ToLower() + @", instant: " + webService.Instant.ToString().ToLower() + @", destinationId: """ + webService.DestinationId + @""", webServiceGroupId: """ + webService.GroupId + @""", elementIds: [" + string.Join(",", webService.ElementIds.Select(id => "\"" + id + "\"")) + @"], callIds: [" + string.Join(",", webService.CallIds.Select(id => "\"" + id + "\"")) + @"] }) { webServiceId name delay bundle keepSequence retryCount retryDelay historyHours enabled instant destinationId webServiceGroupId elementIds callIds } }"; var request = new GraphQLRequest { Query = mutation }; var response = await graphQLClient.SendMutationAsync<WebServiceDTO>(request); return response.Data; }
核心错误原因
1. 输入类型名称不匹配
服务端定义的输入类型是WebServiceInputType,默认情况下HotChocolate会将其Schema名称设置为WebServiceInput,但客户端查询中变量类型写的是WebServiceDTOInput,这会导致GraphQL服务无法识别变量类型,直接返回类型错误。
2. DTO属性与GraphQL字段映射不一致
从修改后的代码可以看到,你手动将webService.GroupId赋值给了GraphQL输入的webServiceGroupId字段,说明WebServiceDTO类中的属性是GroupId,但服务端WebServiceInputType中配置的是映射WebServiceGroupId字段。变量传参时,客户端序列化WebServiceDTO对象后只会包含GroupId字段,服务端期望的webServiceGroupId字段缺失,导致参数校验失败。而硬编码时手动修正了这个映射关系,所以能正常工作。
3. 序列化命名规则不匹配
服务端GraphQL输入字段采用驼峰命名(如webServiceId),而WebServiceDTO的属性是帕斯卡命名(如WebServiceId)。客户端使用默认的NewtonsoftJsonSerializer时,会将帕斯卡属性序列化为帕斯卡字段名,和服务端期望的驼峰字段不匹配,导致服务端无法正确解析参数。
修复方案
- 修正变量类型名称:访问服务端GraphQL Playground(通常是
/graphql路径)查看Schema,确认输入类型的正确名称,将客户端查询中的$req: WebServiceDTOInput!改为实际类型名(比如WebServiceInput!)。 - 对齐属性与字段映射:要么修改
WebServiceDTO的属性名为WebServiceGroupId,要么在WebServiceInputType中显式指定字段名映射:descriptor.Field(x => x.GroupId).Type<StringType>().Name("webServiceGroupId"); - 配置驼峰序列化:初始化客户端序列化器时添加驼峰命名策略:
var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var graphQLClient = new GraphQLHttpClient("http://localhost:81/graphql", new NewtonsoftJsonSerializer(serializerSettings));
内容的提问来源于stack exchange,提问作者Tomvdcs

