Skip to content

Lens Integration

Verified by tests

GraphQLLensAttributeTests, GraphQLLensScopeTests — library CI run #31657041675 (2026-08-13)

The [GraphQLLens] attribute marks lens interfaces for GraphQL exposure, enabling automatic query generation with configurable filtering, sorting, and paging.

Basic Usage

Basic Usage

[GraphQLLens(QueryName = "orders")]
public interface IOrderLens : ILensQuery<OrderReadModel> { }

This generates a GraphQL query field named orders with full data operations support.

Attribute Properties

Property Type Default Description
QueryName string? Pluralized model name (OrderReadModelorders) GraphQL field name
Scope GraphQLLensScopes None (uses system default) Which fields to expose
EnableFiltering bool true Enable where argument
EnableSorting bool true Enable order argument
EnablePaging bool true Enable Relay-style paging
EnableProjection bool true Enable field selection optimization
DefaultPageSize int 10 Default items per page
MaxPageSize int 100 Maximum items per page

GraphQLLensScopes

Control which parts of PerspectiveRow<T> are exposed through the GraphQL schema. The scope determines which nested fields are available for querying.

The data Parameter

When you query a lens-backed GraphQL field, the results are wrapped in PerspectiveRow<TModel>, which contains:

  • data - The business model (your perspective's projection)
  • metadata - Event sourcing metadata (eventType, eventId, timestamp, correlationId, causationId)
  • scope - Security/tenancy context (tenantId, userId, organizationId, customerId, allowedPrincipals)
  • System fields - Infrastructure fields exposed at the top level of each row (id, version, createdAt, updatedAt) when the scope includes SystemFields

The data field contains your actual business model that the perspective projects. For example, if your perspective projects to ProductReadModel, the data field will expose ProductReadModel's properties.

Scope Control

Control which parts of PerspectiveRow<T> are exposed:

Scope Control

[Flags]
public enum GraphQLLensScopes {
    None = 0,              // Use system default (WhizbangGraphQLOptions.DefaultScope)
    Data = 1 << 0,         // TModel properties
    Metadata = 1 << 1,     // EventType, EventId, Timestamp, CorrelationId, CausationId
    Scope = 1 << 2,        // TenantId, UserId, OrganizationId, CustomerId, AllowedPrincipals
    SystemFields = 1 << 3, // Id, CreatedAt, UpdatedAt, Version

    // Presets
    DataOnly = Data,
    NoData = Metadata | Scope | SystemFields,
    All = Data | Metadata | Scope | SystemFields
}

Configuration Examples

Data Only (Default)

Expose only the business data:

Data Only (Default)

[GraphQLLens(QueryName = "products", Scope = GraphQLLensScopes.DataOnly)]
public interface IProductLens : ILensQuery<ProductReadModel> { }

{
  products {
    nodes {
      data {
        name
        price
        category
      }
    }
  }
}

With Metadata

Include event sourcing metadata:

With Metadata

[GraphQLLens(
    QueryName = "auditLog",
    Scope = GraphQLLensScopes.Data | GraphQLLensScopes.Metadata | GraphQLLensScopes.SystemFields)]
public interface IAuditLens : ILensQuery<AuditReadModel> { }

{
  auditLog {
    nodes {
      id
      version
      data {
        action
        description
      }
      metadata {
        eventType
        correlationId
        timestamp
      }
    }
  }
}

Full Row (Admin View)

Expose everything including scope data:

Full Row (Admin View)

[GraphQLLens(QueryName = "adminOrders", Scope = GraphQLLensScopes.All)]
public interface IAdminOrderLens : ILensQuery<OrderReadModel> { }

{
  adminOrders {
    nodes {
      id
      version
      createdAt
      updatedAt
      data {
        customerName
        status
      }
      metadata {
        eventType
        correlationId
      }
      scope {
        tenantId
        userId
      }
    }
  }
}

Filter-Only (No Paging)

Disable paging for simple lists:

Filter-Only (No Paging)

[GraphQLLens(
    QueryName = "statuses",
    EnablePaging = false,
    EnableSorting = false)]
public interface IStatusLens : ILensQuery<StatusReadModel> { }

{
  statuses(where: { data: { isActive: { eq: true } } }) {
    id
    data {
      name
      isActive
    }
  }
}

Custom Page Sizes

Configure paging limits:

Custom Page Sizes

[GraphQLLens(
    QueryName = "transactions",
    DefaultPageSize = 50,
    MaxPageSize = 500)]
public interface ITransactionLens : ILensQuery<TransactionReadModel> { }

Generated Schema

For a lens like:

Generated Schema

[GraphQLLens(QueryName = "orders")]
public interface IOrderLens : ILensQuery<OrderReadModel> { }

public record OrderReadModel {
    public string CustomerName { get; init; }
    public string Status { get; init; }
    public decimal TotalAmount { get; init; }
}

The generated GraphQL schema includes:

type Query

type Query {
  orders(
    where: OrderFilterInput
    order: [OrderSortInput!]
    first: Int
    after: String
    last: Int
    before: String
  ): OrdersConnection
}

type OrdersConnection {
  nodes: [Order!]
  edges: [OrderEdge!]
  pageInfo: PageInfo!
  # totalCount is only added when the resolver uses
  # [UsePaging(IncludeTotalCount = true)] - not enabled by the generated resolvers
}

type Order {
  id: UUID!
  version: Int!
  data: OrderData!
  metadata: PerspectiveMetadata
  scope: PerspectiveScope
  createdAt: DateTime!
  updatedAt: DateTime!
}

type OrderData {
  customerName: String!
  status: String!
  totalAmount: Decimal!
}

input OrderFilterInput {
  and: [OrderFilterInput!]
  or: [OrderFilterInput!]
  data: OrderDataFilterInput
  id: UuidOperationFilterInput
  version: IntOperationFilterInput
}

Multiple Lenses for Same Model

You can create multiple lenses for the same model with different configurations:

Multiple Lenses for Same Model

// Public API - data only
[GraphQLLens(QueryName = "orders", Scope = GraphQLLensScopes.DataOnly)]
public interface IOrderLens : ILensQuery<OrderReadModel> { }

// Admin API - full access
[GraphQLLens(QueryName = "adminOrders", Scope = GraphQLLensScopes.All)]
public interface IAdminOrderLens : ILensQuery<OrderReadModel> { }

// Audit API - metadata focus
[GraphQLLens(
    QueryName = "orderAudit",
    Scope = GraphQLLensScopes.Metadata | GraphQLLensScopes.SystemFields,
    EnableFiltering = false)]
public interface IOrderAuditLens : ILensQuery<OrderReadModel> { }

Next Steps