Query Models

Query models expose EF Core entities directly as GraphQL queries with automatic cursor pagination, filtering, sorting, and projection. Unlike [TraxQuery] which wraps a train (business logic), [TraxQueryModel] maps a database table to a GraphQL field with zero boilerplate.

Quick Start

  1. Mark your entity with [TraxQueryModel]:
[TraxQueryModel(Description = "Player profiles")]
public class PlayerRecord
{
    public long Id { get; set; }
    public string PlayerId { get; set; } = "";
    public string DisplayName { get; set; } = "";
    public int Rating { get; set; }
}
  1. Add the entity to a DbSet<T> on a DbContext:
public class GameDbContext(DbContextOptions<GameDbContext> options) : DbContext(options)
{
    public DbSet<PlayerRecord> Players { get; set; } = null!;
}
  1. Register the DbContext and enable model discovery:
builder.Services.AddDbContextFactory<GameDbContext>(options =>
    options.UseNpgsql(connectionString));
 
builder.Services.AddTraxGraphQL(graphql =>
    graphql.AddDbContext<GameDbContext>());

This generates a playerRecords query field under discover:

query {
  discover {
    playerRecords(first: 10, where: { rating: { gte: 1500 } }, order: { rating: DESC }) {
      nodes {
        playerId
        displayName
        rating
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
}

TraxQueryModel Attribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TraxQueryModelAttribute : Attribute
{
    public string? Name { get; init; }
    public string? Description { get; init; }
    public string? DeprecationReason { get; init; }
    public string? Namespace { get; init; }
    public bool Paging { get; init; } = true;
    public bool Filtering { get; init; } = true;
    public bool Sorting { get; init; } = true;
    public bool Projection { get; init; } = true;
}

Properties

PropertyTypeDefaultDescription
Namestring?nullOverrides the auto-derived GraphQL field name. When null, derived by pluralizing and camelCasing the class name (e.g. Playerplayers).
Descriptionstring?nullHuman-readable description that appears in the GraphQL schema documentation.
DeprecationReasonstring?nullMarks the generated field as deprecated in the schema.
Namespacestring?nullGroups this field under a sub-namespace. When set, the field appears under discover { namespace { field } } instead of directly under discover.
PagingbooltrueEnables cursor-based pagination (Relay Connection spec). When true, the field returns a Connection type with nodes, edges, pageInfo, and totalCount.
FilteringbooltrueEnables filtering via a where argument. HotChocolate generates filter input types for all entity properties.
SortingbooltrueEnables sorting via an order argument. HotChocolate generates sort input types for all entity properties.
ProjectionbooltrueEnables field projection — only the columns requested by the GraphQL client are selected from the database.

Feature Configuration

Each feature can be independently disabled per model. All default to true.

// Full-featured (default)
[TraxQueryModel]
public class Player { ... }
 
// Pagination and filtering only — no sorting or projection
[TraxQueryModel(Sorting = false, Projection = false)]
public class AuditLog { ... }
 
// Simple list query — no middleware at all
[TraxQueryModel(Paging = false, Filtering = false, Sorting = false, Projection = false)]
public class StatusCode { ... }

When Paging = false, the field returns a plain list ([Entity!]!) instead of a Connection type.

Name Derivation

When Name is null, the field name is derived automatically:

  1. Pluralize the class name (naive English rules: PlayerPlayers, MatchMatches, CategoryCategories)
  2. camelCase the result (Playersplayers)

Override with Name for cases where the automatic pluralization is incorrect:

[TraxQueryModel(Name = "people")]
public class Person { ... }

AddDbContext

Register one or more DbContext types whose DbSet<T> properties contain attributed entities:

builder.Services.AddTraxGraphQL(graphql => graphql
    .AddDbContext<GameDbContext>()
    .AddDbContext<InventoryDbContext>());

Only DbSet<T> properties where T has [TraxQueryModel] are exposed. Other DbSet properties on the same DbContext are ignored.

The DbContext must be registered in DI separately (via AddDbContext, AddDbContextFactory, or AddPooledDbContextFactory).

vs TraxQuery

[TraxQuery][TraxQueryModel]
TargetTrain class (workflow)Entity class (data model)
Resolves viaITrainBus.RunAsyncDbContext.Set<T>() → IQueryable
InputTyped input DTOFilter/sort/page arguments (auto-generated)
OutputTyped output DTOEntity properties (with projection)
Use caseBusiness logic, computed resultsDirect CRUD reads, admin dashboards
Schema locationdiscover { trainName(input: ...) }discover { modelNames(first: ..., where: ...) }

Both appear under the discover namespace in the GraphQL schema.