EntityGraphQL Documentation

website·Indexed 30 days ago

https://entitygraphql.github.io/

Documentation for EntityGraphQL, covering schema creation, field extensions for filtering, paging, and aggregation, and integration with EF. Includes guides on authorization services, error handling, mutation validation, and library compatibility for versions 5.7 and 6.x.

Tokens
65.1K
Snippets
291
Records
359
Agent score
72%

What's inside EntityGraphQL

  1. Overview of EntityGraphQL for .NET

    EntityGraphQL is a .NET library used to build GraphQL APIs by mapping a GraphQL schema to .NET objects. It parses GraphQL query documents and executes them against mapped objects, such as an Entity Framework DbContext or any other .NET object.

    When used with Entity Framework, EntityGraphQL optimizes performance by using the LINQ Select() projection operator to ensure only the fields requested in the GraphQL query are retrieved from the database, preventing the retrieval of all columns from a table.

  2. Use Custom Type Converters for runtime type conversion

    EntityGraphQL provides a type converter system to enable runtime conversion between types. This is primarily used for:

    • Converting query variables and mutation arguments.
    • Using custom types (e.g., Version, Uri) in filter expressions (such as isAny or binary comparisons).
    • Handling JSON library types like JObject or JToken from Newtonsoft JSON.
  3. Translate GraphQL queries to Entity Framework expressions

    EntityGraphQL parses GraphQL documents and uses the defined schema to construct .NET expressions. When the DbContext is based on Entity Framework, EF executes these expressions to fetch only the fields requested in the GraphQL query, preventing over-fetching from the database.
    // GraphQL Query
    query {
      movie(id: 11) {
        id
        name
      }
    }
    
    // Resulting .NET Expression
    var expression = (DemoContext ctx, AnonymousType<> args) =>
        ctx.Movies
            .Where(movie => movie.Id == args.id)
            .Select(movie => new {
                id = movie.Id,
                name = movie.Name
            })
            .FirstOrDefault();
  4. Translate GraphQL queries to Entity Framework LINQ expressions

    EntityGraphQL parses GraphQL documents and uses the defined schema to construct .NET expressions. When used with an Entity Framework DbContext, these expressions are executed by EF, ensuring that only the fields requested in the GraphQL query are selected from the database, which prevents over-fetching.
    // GraphQL Query:
    // query { movie(id: 11) { id name } }
    
    // Resulting .NET expression:
    var expression = (DemoContext ctx, AnonymousType<> args) =>
        ctx.Movies
            .Where(movie => movie.Id == args.id)
            .Select(movie => new {
                id = movie.Id,
                name = movie.Name
            })
            .FirstOrDefault();
  5. Understand Partial Results behavior in EntityGraphQL 6.x

    EntityGraphQL 6.x now adheres to the GraphQL specification regarding partial results. Previously, any field failure caused the entire operation to fail. In 6.x:

    • Each top-level field in an operation is executed separately.
    • If some fields fail, the response contains partial results from successful fields and error information for failed ones.
    • If an error prevents a valid response, the data entry will be null.
    • AddGraphQLValidator now registers IGraphQLValidator as Transient. To revert to Scoped behavior, manually register IGraphQLValidator as Scoped and remove AddGraphQLValidator.
  6. Secure GraphQL Mutations and Queries using Attributes

    Use the [Authorize] or [GraphQLAuthorize] attributes to secure fields, mutations, or entire types.

    • Authentication Only: A bare [Authorize] attribute requires the user to be authenticated (Identity.IsAuthenticated).
    • OR Logic: A single attribute with comma-separated roles/policies (e.g., Roles = "role1,role2") allows access if the user has ANY of the listed roles.
    • AND Logic: Multiple [Authorize] attributes on a single field require the user to satisfy ALL attributes.
    • Fail Closed: If a requirement is present but cannot be satisfied (e.g., no user provided or missing service), access is denied.
    // Securing a Mutation
    public class MovieMutations
    {
      [GraphQLMutation]
      [Authorize(Roles = "movie-editor")]
      public Movie AddActor(MyDbContext db, ActorArgs args)
      { 
        // ... 
      }
    }
    
    // Securing a DbContext property (Query)
    public class MyDbContext : DbContext {
      [Authorize(Roles = "property-role,admin-property-role")]
      public DbSet<Property> Properties { get; set; }
    }
    
    // Securing a specific field within a type (AND logic)
    public class Property {
      [Authorize(Roles = "property-admin")]
      [Authorize(Roles = "super-admin")]
      public Location Location { get; set; }
    }
    
    // Securing an entire type
    [Authorize(Roles = "property-user")]
    public class Property { ... }
  7. Map class methods to GraphQL fields

    You can map methods in your classes to GraphQL query fields by decorating them with the [GraphQLField] attribute. When using SchemaBuilder or SchemaType.AddAllFields(), these methods are automatically added as fields. Method parameters are automatically mapped to GraphQL arguments based on these rules:

    1. Scalar/non-complex types are added as arguments.
    2. Types already present in the schema are added as arguments.
    3. Parameters marked with [GraphQLInputType] are added as InputType.
    4. Parameters marked with [GraphQLArguments] have their properties flattened into multiple schema arguments.
    5. Parameters without attributes are assumed to be services and are excluded from the schema.
    public class Movie
    {
        public uint Id { get; set; }
        public virtual DateTime Released { get; set; }
        public virtual Person Director { get; set; }
    
        [GraphQLField]
        public uint DirectorAgeAtRelease => (uint)((Released - Director.Dob).Days / 365);
    }
  8. Execute GraphQL requests using SchemaProvider in v5.0+

    In version 5.0.0 and later, SchemaProvider.ExecuteRequest and SchemaProvider.ExecuteRequestAsync no longer accept a schema context instance. Instead, the context is automatically fetched from the provided ServiceProvider, ensuring that ServiceLifetime.Transient rules are correctly followed. This is the recommended way to execute queries.

    To maintain legacy 4.x behavior where a specific schema context instance is used for all references, use ExecuteRequestWithContext or ExecuteRequestWithContextAsync.

  9. Implement a custom field extension in EntityGraphQL

    Field extensions allow you to encapsulate common patterns or logic and apply them across multiple fields. To implement a custom extension, you must create a class that implements the IFieldExtension interface (or extends BaseFieldExtension) and provide an extension method to apply it to a Field object. You can also create a FieldExtensionAttribute that inherits from FieldExtensionAttribute to apply the extension via attributes in the SchemaBuilder.
    public static class UseFormatExtension
    {
        public static Field UseFormat(this Field field)
        {
            if (field.Resolve.Type != typeof(string))
                throw new ArgumentException("UseFormat must only be called on a field that returns a string");
    
            // Register the extension on the field
            field.AddExtension(new FormatStringExtension());
            return field;
        }
    
        public class UseFormatAttribute : FieldExtensionAttribute
        {
            public override void ApplyExtension(Field field)
            {
                field.UseFormat();
            }
        }
    }
  10. Implement a custom IQueryLimitObserver for structured logging

    For complex logging requirements (e.g., needing IHttpContextAccessor or a metrics sink), register an IQueryLimitObserver in the service provider. If both a registered observer and the OnQueryLimitExceeded callback are present, the callback takes precedence.
    // Registration
    builder.Services.AddSingleton<IQueryLimitObserver, LoggingQueryLimitObserver>();
    
    public class LoggingQueryLimitObserver(ILogger<LoggingQueryLimitObserver> logger, IHttpContextAccessor http) : IQueryLimitObserver
    {
        public void OnQueryLimitExceeded(QueryLimitExceededContext ctx) =>
            logger.LogWarning("GraphQL limit {Limit} exceeded: {Actual} > {Maximum} (operation {Operation}, user {User})",
                ctx.Limit, ctx.Actual, ctx.Maximum, ctx.OperationName, http.HttpContext?.User?.Identity?.Name);
    }
  11. Join external service types back to the core context

    To create a rich object graph, you can add fields to a type returned by an external service that resolve data back from the core context (e.g., a database context). This is achieved by calling Resolve<TContext>() on the field of the external type.
    schema.UpdateType<User>(userType => {
        userType.AddField("tasks", "List of projects assigned to the user")
            .Resolve<DemoContext>(
                (user, db) => db.Projects.Where(project => project.AssignedToId == user.Id)
            );
    });
  12. Apply EntityGraphQL field extensions in the correct order

    When combining multiple field extensions on a single field in EntityGraphQL, the order of application is critical for correct behavior. The required sequence is: Filter → Sort → Paging → Aggregate.

    UseAggregate must be placed last because it adapts to the output shape of preceding extensions. If a field is paged, UseAggregate attaches an aggregate field to the paging result; otherwise, it wraps the field in a { items, aggregate } structure.