- 后端
【免费下载链接】graphql-dotnet
GraphQL for .NET
导读
本文基于 graphql-dotnet 仓库官方入门文档(docs2/site/docs/getting-started/introduction.md),系统讲解 GraphQL.NET 中两种构建 Schema 的方式:Schema First(基于 GraphQL schema language)与GraphType First(基于GraphType类继承)。通过 Hello World 示例、字段映射、嵌套类型等实战代码,读者将掌握Schema.For(...)+schema.ExecuteAsync(...)的核心用法、GraphQLMetadata/FromSource等关键属性的作用,并理解两种方式各自的适用场景与底层原理。
GraphQL 与 GraphQL.NET 的基本概念
GraphQL 是一种面向 API 的查询语言,同时也是一个服务端运行时:它通过你为数据定义的类型系统来执行查询。GraphQL 并不绑定任何特定数据库或存储引擎,而是由你现有的代码和数据来支撑。
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn't tied to any specific database or storage engine and is instead backed by your existing code and data.
一个 GraphQL 服务由两部分组成:定义类型与字段,以及为每个类型上的每个字段提供函数(resolver)。GraphQL.NET 正是这套理念在 .NET 生态中的完整实现。
官方学习资料可参考 GraphQL.org 的 Learn 板块,而本仓库的入门文档(docs2/site/docs/getting-started/introduction.md)则给出了面向 GraphQL.NET 的具体编码方式。
安装与 Hello World
安装核心库与序列化器
使用 GraphQL.NET 前,需要安装核心库与执行引擎,以及一个序列化器实现(用于IGraphQLSerializer):
dotnet add package GraphQL # 推荐在 .NET Core 3+ 上使用 System.Text.Json 序列化引擎 dotnet add package GraphQL.SystemTextJson dotnet add package GraphQL.NewtonsoftJson # 也可以自行实现 IGraphQLSerializer完整的包与项目结构可参考 src/GraphQL/GraphQL.csproj 与 src/GraphQL.SystemTextJson/GraphQL.SystemTextJson.csproj,仓库中的 Hello World 示例位于 samples/GraphQL.AotCompilationSample.TypeFirst/Program.cs。
Hello World 完整代码
下面是最精简的 Hello World,使用Schema.For从 SDL 字符串构建 Schema,再通过schema.ExecuteAsync执行查询,输出 JSON:
using System; using System.Threading.Tasks; using GraphQL; using GraphQL.Types; using GraphQL.SystemTextJson; public class Program { public static async Task Main(string[] args) { var schema = Schema.For(@" type Query { hello: String } "); var json = await schema.ExecuteAsync(_ => { _.Query = "{ hello }"; _.Root = new { Hello = "Hello World!" }; }); Console.WriteLine(json); } }输出:
{ "data": { "hello": "Hello World!" } }这里有两个值得注意的点:
_.Root = new { Hello = "Hello World!" };为根对象提供了匿名类型实例,hello字段会通过名称匹配从根对象取值;- 默认情况下,SDL 中的字段名(
hello)会按 Camel Case 命名约定与 CLR 属性(Hello)匹配,这正是 Schema.cs 中NameConverter默认使用CamelCaseNameConverter.Instance的体现。
ExecuteAsync扩展方法定义在 SchemaExtensions.cs,接收一个配置委托来设置ExecutionOptions,返回序列化后的结果字符串;其底层执行链路是:DocumentExecuter解析查询 → 校验 → 调用对应策略(如SerialExecutionStrategy/ParallelExecutionStrategy)执行字段解析。
两种建 Schema 方式总览
GraphQL.NET 支持两种构建 Schema 的方式,两者都基于下面这段 SDL 定义:
!表示该字段非空(non-nullable)。
type Droid { id: String! name: String! } type Query { hero: Droid }| 方式 | 核心思路 | 优点 | 局限 |
|---|---|---|---|
| Schema First | 直接写 SDL 字符串,配合 CLR 类与约定映射 | 上手最快、代码量最少 | 目前不支持部分高级场景 |
| GraphType First | 编写继承GraphType的类显式定义字段与解析器 | 可访问GraphType与Schema的全部属性、功能最完整 | 代码更冗长,必须使用继承 |
Schema First 方式
Schema First 依赖 GraphQL schema language、编码约定,并尽量提供最少的语法,是最容易上手的方式,不过它目前不支持某些高级场景。
可选地使用
GraphQLMetadata特性来定制 CLR 类型到 Schema 类型的映射。
基础用法
public class Droid { public string Id { get; set; } public string Name { get; set; } } public class Query { [GraphQLMetadata("hero")] public Droid GetHero() { return new Droid { Id = "1", Name = "R2-D2" }; } } var schema = Schema.For(@" type Droid { id: String! name: String! } type Query { hero: Droid } ", _ => { _.Types.Include<Query>(); }); var json = await schema.ExecuteAsync(_ => { _.Query = "{ hero { id name } }"; });输出:
{ "data": { "hero": { "id": "1", "name": "R2-D2" } } }关键机制:SchemaBuilder 与 Types.Include
Schema.For的完整签名定义在 Schema.cs:它会创建SchemaBuilder(可由泛型参数替换为自定义 Builder),调用可选的配置委托,然后执行builder.Build(typeDefinitions)完成解析、校验与 Schema 构建。
配置委托操作的是 SchemaBuilder.cs 中的SchemaBuilder实例,其中:
Types是TypeSettings类型的属性(SchemaBuilder.cs),用于维护 SDL 类型名与 CLR 类型的映射;_.Types.Include<Query>()会把Query类注册到该映射中(TypeSettings.cs),使 SDL 中的hero字段能够绑定到GetHero()方法;- 构建 Schema 前还会对 SDL 做基础校验,例如不允许出现同名类型的重复定义(SchemaBuilder.cs)。
SchemaBuilder还暴露了若干可配置项,便于应对更复杂的场景:
| 属性 | 默认值 | 作用 |
|---|---|---|
ServiceProvider | DefaultServiceProvider | 构建 Schema 时用于创建所需对象(SchemaBuilder.cs) |
IgnoreComments | true | 解析 GraphQL 文档时是否忽略注释 |
IgnoreLocations | false | 解析时是否忽略 token 位置信息 |
AllowUnknownTypes | true | 遇到未注册到Types中的类型时是否允许构建成功 |
AllowUnknownFields | true | 遇到没有 resolver 的字段时是否允许构建成功 |
RunConfigurations | true | 是否从ServiceProvider拉取并执行已注册的IConfigureSchema实例 |
GraphQLMetadata 属性详解
GraphQLMetadataAttribute定义于 GraphQLMetadataAttribute.cs,可标注在类、接口、结构体、枚举、方法、属性、字段与参数上,主要属性包括:
Name:指定 GraphType 或字段的名称(如示例中的"hero");Description:为 GraphType 或字段添加描述;DeprecationReason:标记弃用原因;IsTypeOf:指示该 GraphType 所代表的 CLR 类型(用于类型判定);ResolverType:指示方法是字段 resolver 还是订阅事件流 resolver(Resolver/StreamResolver)。
当方法被标注为字段名后,SchemaBuilder会通过AutoRegisteringHelper.BuildFieldResolver把该方法包装成字段的 resolver(见 SchemaBuilder.cs)。
GraphType First 方式
GraphTypefirst 方式代码更多,但可以访问GraphType与Schema提供的所有属性。要利用这些功能,必须使用继承。
基础用法
using System; using System.Threading.Tasks; using GraphQL; using GraphQL.Types; using GraphQL.SystemTextJson; public class Droid { public string Id { get; set; } public string Name { get; set; } } public class DroidType : ObjectGraphType<Droid> { public DroidType() { Field(x => x.Id).Description("The Id of the Droid."); Field(x => x.Name).Description("The name of the Droid."); } } public class StarWarsQuery : ObjectGraphType { public StarWarsQuery() { Field<DroidType>("hero") .Resolve(context => new Droid { Id = "1", Name = "R2-D2" }); } } public class Program { public static async Task Main(string[] args) { var schema = new Schema { Query = new StarWarsQuery() }; var json = await schema.ExecuteAsync(_ => { _.Query = "{ hero { id name } }"; }); Console.WriteLine(json); } }输出:
{ "data": { "hero": { "id": "1", "name": "R2-D2" } } }核心类型与字段配置
ObjectGraphType<T>:为 CLR 类型T创建对象图类型,Field(x => x.Id)通过表达式直接绑定属性;ObjectGraphType(非泛型):用于定义无强类型源对象的根类型(如StarWarsQuery);Field<DroidType>("hero").Resolve(context => ...):显式声明字段类型、名称与解析函数。
这种方式下,字段的Description、默认值、参数、中间件等一切能力都通过FieldBuilder链式配置获得,参考 src/GraphQL/Builders/FieldBuilder.cs 与 src/GraphQL/Builders/FieldBuilder_Typed.cs。仓库中的完整示例可参考 samples/GraphQL.StarWars.TypeFirst 与 src/GraphQL.StarWars。
Schema First 嵌套类型与 FromSource
Schema First 方式支持在 SDL 中声明多个“顶层”类型,并通过 CLR 类为每个类型提供解析实现。
完整示例
public class Droid { public string Id { get; set; } public string Name { get; set; } } public class Character { public string Name { get; set; } } public class Query { [GraphQLMetadata("hero")] public Droid GetHero() { return new Droid { Id = "1", Name = "R2-D2" }; } } [GraphQLMetadata("Droid", IsTypeOf=typeof(Droid))] public class DroidType { public string Id([FromSource] Droid droid) => droid.Id; public string Name([FromSource] Droid droid) => droid.Name; // 这两个参数是可选的 // IResolveFieldContext 提供关于字段的上下文信息 public Character Friend(IResolveFieldContext context, [FromSource] Droid source) { return new Character { Name = "C3-PO" }; } } public class Program { public static async Task Main(string[] args) { var schema = Schema.For(@" type Droid { id: String! name: String! friend: Character } type Character { name: String! } type Query { hero: Droid } ", _ => { _.Types.Include<DroidType>(); _.Types.Include<Query>(); }); var json = await schema.ExecuteAsync(_ => { _.Query = "{ hero { id name friend { name } } }"; }); Console.WriteLine(json); } }输出:
{ "data": { "hero": { "id": "1", "name": "R2-D2", "friend": { "name": "C3-PO" } } } }关键点解读
IsTypeOf绑定源类型:[GraphQLMetadata("Droid", IsTypeOf = typeof(Droid))]声明这个DroidType类对应 SDL 中的Droid类型,并关联 CLR 类型Droid。在 GraphQLMetadataAttribute.cs 中,IsTypeOf会被转换成IsTypeOfFunc,用于在运行时判断某个对象实例是否属于该 GraphType。FromSource注入源对象:[FromSource] Droid droid参数会把当前字段解析的 Source(父级返回的Droid实例)注入到方法参数中。FromSourceAttribute.cs 的实现会在参数类型与 Source 类型不匹配时抛出异常,并通过context.Source完成参数绑定。IResolveFieldContext上下文注入:方法可以额外接收IResolveFieldContext context参数,用于访问字段级上下文信息(如参数、UserContext、路径等),此参数是可选的。嵌套查询天然支持:SDL 中
Droid.friend: Character声明了嵌套关系,查询{ hero { id name friend { name } } }会依次触发hero→id/name→friend字段的解析,最终得到完整嵌套 JSON。
两种方式的选型建议
- 快速原型、小型服务、团队熟悉 SDL:优先 Schema First。它声明式强、代码少,
Types.Include<T>()的约定映射足够应付大多数 CRUD 场景。 - 大型复杂服务、需要字段中间件、自定义参数注入、精细控制类型行为:选择 GraphType First。通过继承
ObjectGraphType<T>可获得全部能力(描述、弃用、参数、中间件、接口/联合类型等)。 - 两者可混用:GraphQL.NET 允许在一个应用中同时使用两种方式;例如用 Schema First 定义整体 SDL,用
Types.Include将复杂的 GraphType 类接入。
延伸学习
入门之后,可继续阅读本仓库 docs2/site/docs/getting-started 目录下的进阶主题:
- 安装与包说明
- Schema 类型体系
- 字段与参数定义 / arguments.md
- 列表与非空类型
- 查询(Queries)与变更(Mutations) / mutations.md
- 订阅(Subscriptions)
- 依赖注入
- 错误处理
同时,samples 目录下的各示例项目(如 GraphQL.DataLoader.Sample.Default、GraphQL.Federation.SchemaFirst.Sample1)提供了可直接运行的完整工程,便于对照学习 Schema First 与 GraphType First 在生产场景中的真实组织方式。
- 后端
【免费下载链接】graphql-dotnet
GraphQL for .NET
相关推荐
使用 Java 与 graphql-java 构建 GraphQL 服务器:从 schema-first 到 code-first 的完整指南
使用 Java 与 graphql java 构建 GraphQL 服务器:从 schema first 到 code first 的完整指南 本指南以 how
Electron App错误处理:进程间通信异常监控与恢复机制终极指南
Electron App错误处理:进程间通信异常监控与恢复机制终极指南 想要构建稳定可靠的Electron应用?进程间通信(IPC)异常处理是每个Electro
GraphQL Java 实战:从 Schema-First 到 Code-First,用 graphql-spqr 从业务代码直接生成 Schema
GraphQL Java 实战:从 Schema First 到 Code First,用 graphql spqr 从业务代码直接生成 Schema 本文围绕
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考