v6.0.0
EntityGraphQL 6.0 is the culmination of the 6.0.0-beta1 through 6.0.0-beta9 releases. The beta sections below carry the full, detailed lists of changes, fixes and migration notes - and see the upgrade guide for a consolidated migration path from 5.x.
6.0 Highlights
- GraphQL spec conformance - partial results per the spec (failing top-level fields no longer fail the whole operation), September 2025 GraphQL spec support (
@oneOf, argument/input-field deprecation, document descriptions, stricter validation) andMapGraphQL()follows the GraphQL over HTTP spec by default. - A real async model -
ResolveAsync<TService>()fields, end-to-endCancellationTokensupport (into resolvers, mutations, subscriptions and the in-flight EF query), async database materialization for root list fields, and concurrency control per field, per service and per query (MaxQueryConcurrencydefaults to 100). - Security hardening - authorization fails closed, introspection respects authorization, a parser recursion backstop against deeply-nested document DoS, opt-in query guards (
MaxQueryDepth,MaxFieldSelections,MaxFieldAliases,MaxQueryComplexity) and per-field rate limiting. - New schema-building features -
UseAggregate()for SQL-translated aggregates over collections,AddFieldsFrom<T>()/AddQueryFieldsFrom<T>()to group field definitions into classes, filter improvements (GraphQL variables, service fields,selectMany, filtering by paged child fields) and async subscription setup for external brokers. - Performance - paging answers
hasNextPagewith a cheapEXISTSinstead ofCOUNT(*), async result processing is compiled and targeted (~2x faster on async-heavy results), opt-in compiled-delegate caching, and WebSocket subscription delivery is ordered and non-blocking. - Modern targets -
net8.0,net9.0andnet10.0with a dependency-light core package.
Breaking Changes from Beta 9
-
MapGraphQL()HTTP status codes now fully follow the GraphQL over HTTP spec for theapplication/graphql-response+jsonmedia type (the default response type): a response with nodataentry (a request error - e.g. document parse/validation failure) returns400instead of200, and a response with bothdataanderrors(partial success) returns294per the spec. Clean results remain200. Responses negotiated as legacyapplication/jsonkeep returning200for every well-formed request. Clients following the spec read the response body regardless of status code forapplication/graphql-response+json. -
AddFieldsFrom<TFrom>()/AddQueryFieldsFrom<TType>()(introduced in beta 9) now require the grouping class to implement the newIFieldsFor<TContext>marker interface declaring which type its[GraphQLField]methods are written against - the entity type forschema.Type<Person>().AddFieldsFrom<T>(), the query context forAddQueryFieldsFrom<T>(). Registering a class against the wrong schema type is now a compile error instead of a schema-build surprise, and the marker gives compile-time tooling the intended context type. The interface is contravariant, so a fields class written for a base type or shared interface can be added to types deriving from/implementing it. A class may also implementIFieldsFor<>for several contexts to group a feature across types (e.g. occupancy fields forBuilding,FloorandSpacein one class) - each registration only adds the methods written for that context. See Grouping fields across multiple types. Migration: add: IFieldsFor<TheContextType>to each grouping class. -
A
[GraphQLField]method parameter whose type is a schema entity type (and not the field's context type) now fails at schema build with a clear error instead of being silently classified as a DI service - that classification almost always meant a mistyped context parameter that resolved (or failed confusingly) at query time. The query context type is excluded (aDbContextparameter remains a service), and the new[GraphQLService]parameter attribute opts out for the rare case where an entity type really is a DI service.
Changes since Beta 9
[GraphQLField]methods (viaAddFieldsFrom<T>()/AddQueryFieldsFrom<T>()or static on a schema type) can now return anExpression<Func<TContext, ...>>instead of a value. The method is a factory invoked once at schema build time and the returned expression is registered exactly likeAddField(...).Resolve(...)- a service-free expression composes fully into the database-bound pass (translates to SQL, only the columns it uses are selected), and extra expression parameters after the context are resolved as services with the standard two-pass execution and dependency extraction. The factory method itself takes no parameters. See Expression fields with AddFieldsFrom.
Bug Fixes since Beta 9
- Fixed
ResolveBulkfields failing withKeyNotFoundExceptionwhen selected via a fragment spread (...MyFragment) or an inline fragment (... on Type { bulkField }) - the bulk loader was never registered because fragments expand after bulk registration ran. Bulk fields are now registered through fragments at any nesting depth, the bulk list expression path is taken from the compile-time selection path rather than a field's parent-node chain (which for fragment-selected fields pointed at the fragment definition, not the selection point), and selecting the same bulk field both directly and via a fragment loads the data once, not twice. - Fixed
ResolveBulkfields failing when reached through a nested single-object navigation (e.g.tasks { project { createdBy } }) or a mutation result selection (e.g.mutation { updateTask { project { createdBy } } }) - single-object contexts are now wrapped for the bulk key batching, object navigations off a list context project per element, and the duplicate mutation result-selection path node is skipped by reference. Also released in 5.7.3. Thanks @alex-birch #526 - Fixed custom type converters (
AddCustomTypeConverter) registered for a base type never matching values of derived types - the lookup was an exact type match, so the documented Newtonsoft patternAddCustomTypeConverter<JToken>(...)never fired (JTokenis abstract; runtime values areJArray/JValue/JObject), breaking e.g. list-of-enum variables sent through Newtonsoft.Json withSupplied variable can not be applied to defined variable type. From-type converter lookup now walks the base-type chain (the most derived registration wins). - Fixed
ResolveBulkfields reached through chained single-object navigations (e.g.projects { tasks { assignee { manager { bulkField } } } }) throwingValue cannot be null- the null-guard built while collecting the bulk keys assumed every navigation step was a list. Single-object steps are now null-guarded properly (a null parent yields null and is filtered before the bulk key selection). Also released in 5.7.3.