Queries
The Query type is the entry point for reading data from a GraphQL server. It is the only required root type. Every public method or property on a query class becomes a field that clients can request. Query fields are expected to be side-effect-free, which allows the execution engine to run them in parallel.
GraphQL schema
type Query { books: [Book!]! author(id: Int!): Author}
Client query
query { books { title } author(id: 1) { name }}
Both books and author run concurrently. The execution engine is free to parallelize and reorder query fields because they have no side effects.
Defining a Query Type
Mark a class with [QueryType] and the source generator registers it as part of the Query type. The class must be partial so the source generator can add code at build time.
// Types/BookQueries.cs[QueryType]public static partial class BookQueries{ public static Book GetBook() => new Book { Title = "C# in depth", Author = "Jon Skeet" };}
The source generator creates a book field on the Query type. No additional registration is needed beyond the AddTypes call generated by the source generator.
Splitting the Query Type Across Classes
GraphQL allows only one Query type per schema, but your codebase does not have to define all query fields in a single class. With the source generator, you can annotate multiple classes with [QueryType]. The source generator merges them into one Query type.
// Types/ProductQueries.cs[QueryType]public static partial class ProductQueries{ public static async Task<Product?> GetProductByIdAsync( int id, CatalogContext db, CancellationToken ct) => await db.Products.FindAsync([id], ct);}
// Types/AuthorQueries.cs[QueryType]public static partial class AuthorQueries{ public static async Task<Author?> GetAuthorByIdAsync( int id, CatalogContext db, CancellationToken ct) => await db.Authors.FindAsync([id], ct);}
This produces a schema with both fields on the Query type:
type Query { productById(id: Int!): Product authorById(id: Int!): Author}
Group your query classes by domain area. This keeps each file focused and makes it clear which team or module owns each part of the API.
These query classes can also be split across multiple assemblies, as long as each assembly uses the Hot Chocolate source generator.
Static vs Instance Classes
A [QueryType] class can be either static or non-static.
Static classes are the recommended default. They have no instance state, which makes resolvers predictable and testable.
// Types/ProductQueries.cs[QueryType]public static partial class ProductQueries{ public static Product? GetProductById(int id, CatalogContext db) => db.Products.Find(id);}
Non-static classes are registered as singletons on the service collection by the source generator. Use this when you need constructor-injected dependencies, though injecting services directly into resolver method parameters is preferred in most cases.
// Types/ProductQueries.cs[QueryType]public partial class ProductQueries{ public Product? GetProductById(int id, CatalogContext db) => db.Products.Find(id);}
Naming Conventions
Hot Chocolate converts C# method names to GraphQL field names using these rules:
| C# Method | GraphQL Field | Rule |
|---|---|---|
GetBook() | book | Get prefix stripped, camelCased |
GetBookByIdAsync() | bookById | Get prefix and Async suffix stripped |
Books() | books | camelCased as-is |
You can override the generated name with [GraphQLName]:
[GraphQLName("allBooks")]public static List<Book> GetBooks() => /* ... */;
Next Steps
- Need to write data? See Mutations.
- Need real-time updates? See Subscriptions.
- Need to understand how types map to the schema? See Object Types.
- Need to fetch data efficiently? See Resolvers and DataLoader.