You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Changes
ToGraphQLSchemaStringAsync(ClaimsPrincipal? user) (and ToGraphQLSchemaString(QueryRequestContext? requestContext)) output SDL containing only what that user may access, using the schema's AuthorizationService as query execution does. The result is self-consistent: a type the filtering leaves empty is hidden along with the fields returning it, a field is hidden when an argument's type is hidden, hidden interfaces and union members are dropped from implements and union lists, and a directive with an argument of a hidden type is hidden. Useful for handing a schema to a user or AI agent (e.g. an MCP server tool) where SDL is more compact than introspection. ToGraphQLSchemaString() without a user still outputs the full schema. Both are default interface methods on ISchemaProvider, so custom implementations do not break.
Introspection for a user now hides the same things the SDL does. Previously a type with no type-level authorization but every field protected was still listed (with fields: []), a field taking an argument of a protected enum or input type was listed, protected interfaces still appeared in interfaces, and a directive with an argument of a protected type was listed. These are now hidden, so a client building a schema from introspection gets a valid one. Introspection without a user is unchanged.
[Required] on an argument (a property/field of an arguments object or a method parameter) now behaves like ArgumentHelper.Required<T>() - the argument has no default value and must be provided by the query. Previously a value type kept its implicit default(T) as the schema default, so [Required] int Take printed take: Int! = 0 (optional for clients) and a query leaving it out ran with 0. It is now take: Int! and leaving it out is a missing required argument error. Remove [Required] if you relied on the old default.
Introspection __InputValue.defaultValue no longer strips the quotes off string-like defaults. The spec defines it as the GraphQL literal, so a string default of hello is now returned as "hello" - the same text as in the SDL. Clients building a schema from introspection (buildClientSchema, GraphiQL, codegen against an endpoint) previously dropped such defaults or failed to parse them (Guid, strings with spaces). If you read defaultValue directly as text rather than parsing it as GraphQL, string defaults now include their quotes.
Added IFieldExtension.ProcessExpressionPostSelection (a default interface method, and virtual on BaseFieldExtension), called with a list field's projected result so an extension can wrap it.
ConnectionEdgeExtension(Type listType, bool isQueryable) is obsolete - isQueryable is not used. Use ConnectionEdgeExtension(Type listType). It will be removed in 7.0.
Fixes
#562 - Fixed invalid SDL for argument default values of value types. Guid, DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan and char defaults were printed bare (runId: ID! = 00000000-0000-0000-0000-000000000000), which tools like GraphQL Code Generator reject. They are now quoted string literals (dates in ISO format). Numeric defaults are printed with the invariant culture (3.14, not 3,14 under de-DE) and string defaults are escaped (", \, newlines).
Fixed UseOffsetPaging() on a field that is not on the root query type when its collection comes from EF - e.g. schema.Type<Actor>().ReplaceField("movies", new { }, (a, _) => a.Movies.OrderBy(m => m.Released)).UseOffsetPaging() - failing with The LINQ expression 'p_Movie => new Dynamic_items...' could not be translated. The collection is part of the parent's projection, so EF has to translate the paging, and items was paged with EntityGraphQL's own Skip/Take(int?) helpers, which EF does not know. A collection that is not an IQueryable is now paged with System.Linq's Skip/Take (a null skip is 0, a null take is the rest of the collection - int.MaxValue - skip, as EF pages a nested collection with row <= skip + take, which would overflow an int on SQL Server and Postgres). hasNextPage on such a field is translated too: EF evaluated EntityGraphQL's PageHasNext helper in memory, loading the whole collection for every parent, and it is now an EXISTS query. Paging on root query fields was not affected.
#574 - Fixed UseConnectionPaging() on a field that is not on the root query type when its collection comes from EF - e.g. schema.Type<Actor>().ReplaceField("movies", a => a.Movies.OrderByDescending(m => m.Released)).UseConnectionPaging() - failing with The LINQ expression 'edgeNode => ...' could not be translated. As with offset paging above, the edges are now paged with System.Linq's Skip/Take. Cursors were assigned by ConnectionHelper.ApplyCursors while enumerating the page, which EF can not translate inside a parent's projection, so for such a collection they are now set on the edges after EF materializes them. last (without before) on such a collection pages from the end of each parent's own collection - it relied on one total count shared by the whole request - and is taken as Reverse().Take(last) so EF can translate it. With after too, the rows up to after are then dropped from that page. last with or without before on such a collection needs it to be ordered (e.g. a.Movies.OrderBy(m => m.Released)) when it comes from EF, which can not Reverse() an unordered collection - it fails with EF's could not be translated error. An in-memory collection does not need to be ordered. pageInfo { hasNextPage } alone is an EXISTS query rather than loading the whole collection. Paging on root query fields is unchanged.
Fixed a connection field paging a collection of the same type at the root (IQueryable) and on a parent object sharing one ConnectionEdgeExtension, which used whether the first configured field was IQueryable for both (No generic method 'Skip' on type 'QueryableExtensions'). It is now decided per use.
Fixed connection paging pageInfo.hasPreviousPage being false for last without before when exactly one item comes before the page (e.g. last: 2 of 3 items).
Fixed connection paging with last and a before cursor past the end of the collection - e.g. last: 3, before: <cursor 5> of 3 items - returning too few items (2 and 3 rather than all 3), as the page was taken as ending at the cursor. It is now the last items of the collection, with matching cursors and pageInfo. On a nested collection the cursor is shared by every parent and can be past the end of only some of them: each parent's page is chosen from its own count, at the cost of also selecting the last last items of each parent alongside the page.
Fixed connection paging pageInfo.hasNextPage being false for before when the cursor is the last item (e.g. last: 3, before: <cursor 6> of 6 items), although the item at the cursor comes after the page.
A connection's pageInfo is no longer null checked - it can't be null. The check repeated the whole connection expression, so EF translated it twice - pageInfo { hasNextPage } on a nested connection ran its EXISTS subquery twice per parent.