dotnet-cqrs/.claude/skills/add-dynamic-query/SKILL.md
Mathias Beaulieu-Duncan a4525bad6a Add Claude Code harness: rules, skills, hooks, and editorconfig
- Add path-specific rules for commands/queries, dynamic queries, validation, and gRPC
- Add /add-command, /add-query, /add-dynamic-query scaffolding skills
- Add project settings with post-edit formatting, proto validation, and build-gate hooks
- Add .editorconfig codifying existing code style conventions
- Trim CLAUDE.md from 414 to 130 lines (domain details moved to rules)
- Add .harness-version tracking for the shared claude-harness repo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 03:30:27 -04:00

3.9 KiB

name description argument-hint
add-dynamic-query Scaffold a dynamic query with IQueryableProviderOverride for automatic pagination, filtering, and sorting. This is the DEFAULT choice for any list/collection query. <entity name and optional description>

Add Dynamic Query

Scaffold a dynamic query based on: $ARGUMENTS

This is the default query pattern. It gives pagination, filtering, sorting, grouping, and aggregation for free.

Instructions

1. Identify the entity

Determine the source entity type from the user's description. If the project uses a DAL with EF Core, the entity should already exist in the DbContext.

2. Determine the feature folder

Place the file in: Features/{DomainArea}/

3. Create the queryable provider

Create Features/{DomainArea}/{Entity}QueryableProvider.cs:

using Svrnty.CQRS.DynamicQuery.Abstractions;

namespace {ProjectNamespace}.Features.{DomainArea};

public class {Entity}QueryableProvider : IQueryableProviderOverride<{Entity}>
{
    private readonly {DbContextType} _db;
    public {Entity}QueryableProvider({DbContextType} db) => _db = db;

    public Task<IQueryable<{Entity}>> GetQueryableAsync(object query, CancellationToken cancellationToken = default)
        => Task.FromResult(_db.{EntityPlural}.AsQueryable());
}

If the entity needs a DTO projection (different shape for the API), note this for the registration step.

4. Create alter queryable service (if needed)

If the entity needs security filtering, tenant isolation, or default ordering, create Features/{DomainArea}/{Entity}AlterQueryable.cs:

using Svrnty.CQRS.DynamicQuery.Abstractions;

namespace {ProjectNamespace}.Features.{DomainArea};

public class {Entity}AlterQueryable : IAlterQueryableService<{Entity}, {EntityOrDto}>
{
    public Task<IQueryable<{Entity}>> AlterQueryableAsync(
        IQueryable<{Entity}> query,
        IDynamicQuery dynamicQuery,
        CancellationToken cancellationToken = default)
    {
        // Add default ordering, security filters, etc.
        return Task.FromResult(query.OrderByDescending(x => x.CreatedAt));
    }
}

5. Register in DI

Find the project's service registration and add:

// Basic — entity returned as-is
builder.Services.AddDynamicQueryWithProvider<{Entity}, {Entity}QueryableProvider>();

// With DTO projection — entity mapped to DTO
builder.Services.AddDynamicQueryWithProvider<{Entity}, {EntityDto}, {Entity}QueryableProvider>();

// With alter service (add after provider registration)
builder.Services.AddAlterQueryable<{Entity}, {EntityOrDto}, {Entity}AlterQueryable>();

6. Ensure dynamic query dependencies are registered

Check that these are registered (typically once per project, before AddSvrntyCqrs):

builder.Services.AddTransient<IAsyncQueryableService, SimpleAsyncQueryableService>();
builder.Services.AddTransient<IQueryHandlerAsync, QueryHandlerAsync>();

If they're already present, don't add duplicates.

7. Proto message (if project uses gRPC)

// In the DynamicQueryService definition
rpc Query{EntityPlural} (DynamicQuery{EntityPlural}Request) returns (DynamicQuery{EntityPlural}Response);

// Entity message (if not already defined)
message {Entity} {
  // fields matching entity properties
}

// Request — uses standard dynamic query fields
message DynamicQuery{EntityPlural}Request {
  int32 page = 1;
  int32 page_size = 2;
  repeated DynamicQueryFilter filters = 3;
  repeated DynamicQuerySort sorts = 4;
  repeated DynamicQueryGroup groups = 5;
  repeated DynamicQueryAggregate aggregates = 6;
}

// Response
message DynamicQuery{EntityPlural}Response {
  repeated {Entity} data = 1;
  int64 total_records = 2;
  int32 number_of_pages = 3;
}

The standard DynamicQueryFilter, DynamicQuerySort, DynamicQueryGroup, and DynamicQueryAggregate messages should already be defined — reuse them.

8. Summary

After creating everything, list what was created and where.