如何为多个GraphQL Schema实现依赖注入?Azure函数场景
解决Azure函数中多个GraphQL Schema冲突的问题
这个问题我之前碰到过,根源很明确:你在DI容器里多次注册ISchema接口时,容器会覆盖之前的注册记录,最终只有最后添加的SchemaTwo生效,这就是为什么执行queryOne会报错提示在QueryTwoType上找不到对应字段的原因。下面给你几个可行的解决方案,你可以根据业务场景选择:
方案一:创建独立的GraphQL端点(推荐)
这种方式最清晰,给每个Schema分配单独的路由,客户端请求不同的URL就会使用对应的Schema,完全避免冲突。
步骤1:注册具体的Schema类型
先别直接注册ISchema,而是注册每个Schema的具体实现:
// 注册SchemaOne的具体实例 builder.Services.AddTransient<SchemaOne>(sp => new SchemaOne(new FuncDependencyResolver(type => (IGraphType)sp.GetRequiredService(type)))); // 注册SchemaTwo的具体实例 builder.Services.AddTransient<SchemaTwo>(sp => new SchemaTwo(new FuncDependencyResolver(type => (IGraphType)sp.GetRequiredService(type))));
步骤2:创建两个独立的Azure函数触发器
分别对应两个Schema的请求处理:
// 处理SchemaOne的请求,路由为 /api/graphql/schemaone [FunctionName("GraphQL_SchemaOne")] public async Task<IActionResult> HandleSchemaOneRequest( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "graphql/schemaone")] HttpRequest req, SchemaOne schema, ILogger log) { // 解析GraphQL请求 var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); var graphQLRequest = JsonSerializer.Deserialize<GraphQLRequest>(requestBody); // 执行查询 var executionResult = await new DocumentExecuter().ExecuteAsync(options => { options.Schema = schema; options.Query = graphQLRequest.Query; options.Variables = graphQLRequest.Variables; // 按需添加上下文、错误处理等配置 }); return new OkObjectResult(executionResult); } // 处理SchemaTwo的请求,路由为 /api/graphql/schematwo [FunctionName("GraphQL_SchemaTwo")] public async Task<IActionResult> HandleSchemaTwoRequest( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "graphql/schematwo")] HttpRequest req, SchemaTwo schema, ILogger log) { // 重复类似的请求处理逻辑,使用SchemaTwo执行查询 var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); var graphQLRequest = JsonSerializer.Deserialize<GraphQLRequest>(requestBody); var executionResult = await new DocumentExecuter().ExecuteAsync(options => { options.Schema = schema; options.Query = graphQLRequest.Query; options.Variables = graphQLRequest.Variables; }); return new OkObjectResult(executionResult); }
之后客户端只要请求对应的路由:
- 调用
queryOne时请求/api/graphql/schemaone - 调用
queryTwo时请求/api/graphql/schematwo
方案二:使用命名服务(Keyed Services)动态选择Schema
如果想在同一个端点处理两个Schema,可以用.NET 6+支持的键控服务(Keyed Services)来区分不同的Schema,然后根据请求中的查询内容选择对应的Schema。
步骤1:注册键控的Schema服务
// 用"schemaOne"作为键注册SchemaOne builder.Services.AddKeyedTransient<ISchema, SchemaOne>("schemaOne", sp => new SchemaOne(new FuncDependencyResolver(type => (IGraphType)sp.GetRequiredService(type)))); // 用"schemaTwo"作为键注册SchemaTwo builder.Services.AddKeyedTransient<ISchema, SchemaTwo>("schemaTwo", sp => new SchemaTwo(new FuncDependencyResolver(type => (IGraphType)sp.GetRequiredService(type))));
步骤2:在函数中根据查询选择Schema
解析请求中的查询字符串,判断要使用哪个Schema:
[FunctionName("GraphQL_Combined")] public async Task<IActionResult> HandleCombinedRequest( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "graphql")] HttpRequest req, IServiceProvider serviceProvider, ILogger log) { var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); var graphQLRequest = JsonSerializer.Deserialize<GraphQLRequest>(requestBody); // 根据查询内容判断使用哪个Schema ISchema targetSchema; if (graphQLRequest.Query.Contains("queryOne")) { targetSchema = serviceProvider.GetRequiredKeyedService<ISchema>("schemaOne"); } else if (graphQLRequest.Query.Contains("queryTwo")) { targetSchema = serviceProvider.GetRequiredKeyedService<ISchema>("schemaTwo"); } else { return new BadRequestObjectResult("无法识别的查询字段"); } // 执行查询 var executionResult = await new DocumentExecuter().ExecuteAsync(options => { options.Schema = targetSchema; options.Query = graphQLRequest.Query; options.Variables = graphQLRequest.Variables; }); return new OkObjectResult(executionResult); }
注意:这种方式依赖查询字符串的内容判断,如果查询比较复杂(比如使用片段、变量),可能需要更严谨的解析逻辑,比如用GraphQL的语法解析器来提取根字段。
方案三:合并两个Schema到一个根Schema
如果业务允许,可以把两个Schema的查询类型合并成一个统一的根查询,这样所有字段都在同一个Schema中,自然不会有冲突。
步骤1:创建合并的根查询类型
// 假设SchemaOne的查询类型是SchemaOneQuery,SchemaTwo的是SchemaTwoQuery public class CombinedRootQuery : ObjectGraphType { public CombinedRootQuery(SchemaOneQuery schemaOneQuery, SchemaTwoQuery schemaTwoQuery) { // 将SchemaOne的查询作为子字段暴露 Field<ObjectGraphType<SchemaOneQuery>>("queryOne", resolve: ctx => schemaOneQuery); // 将SchemaTwo的查询作为子字段暴露 Field<ObjectGraphType<SchemaTwoQuery>>("queryTwo", resolve: ctx => schemaTwoQuery); } }
步骤2:注册合并后的Schema
builder.Services.AddTransient<SchemaOneQuery>(); builder.Services.AddTransient<SchemaTwoQuery>(); builder.Services.AddTransient<CombinedRootQuery>(); // 注册合并后的Schema builder.Services.AddTransient<ISchema>(sp => new Schema(new FuncDependencyResolver(type => sp.GetRequiredService(type))) { Query = sp.GetRequiredService<CombinedRootQuery>() });
之后客户端就可以在同一个端点执行查询:
{ queryOne { name } }
和
{ queryTwo { # SchemaTwo的字段 } }
内容的提问来源于stack exchange,提问作者Jacq
相关产品推荐
相关产品推荐

