- 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>
2.9 KiB
2.9 KiB
| paths | ||||
|---|---|---|---|---|
|
Dynamic Queries — The Default Query Pattern
99% of queries should be dynamic queries. They provide pagination, filtering, sorting, grouping, and aggregation for free.
The Standard Pattern: IQueryableProviderOverride
// File: Features/Orders/OrderQueryableProvider.cs
public class OrderQueryableProvider : IQueryableProviderOverride<Order>
{
private readonly AppDbContext _db;
public OrderQueryableProvider(AppDbContext db) => _db = db;
public Task<IQueryable<Order>> GetQueryableAsync(object query, CancellationToken ct = default)
=> Task.FromResult(_db.Orders.AsQueryable());
}
Registration — one line:
builder.Services.AddDynamicQueryWithProvider<Order, OrderQueryableProvider>();
This automatically creates endpoints with full filtering, sorting, and pagination support.
Required Dependencies
These must be registered before AddSvrntyCqrs:
builder.Services.AddTransient<IAsyncQueryableService, SimpleAsyncQueryableService>();
builder.Services.AddTransient<IQueryHandlerAsync, QueryHandlerAsync>();
Alter Queryable Services (Security Filters, Tenant Isolation)
Use IAlterQueryableService to modify the queryable before execution — for security filters, tenant isolation, default ordering, etc.
public class OrderTenantFilter : IAlterQueryableService<Order, Order>
{
private readonly ITenantContext _tenant;
public OrderTenantFilter(ITenantContext tenant) => _tenant = tenant;
public Task<IQueryable<Order>> AlterQueryableAsync(
IQueryable<Order> query,
IDynamicQuery dynamicQuery,
CancellationToken ct = default)
{
return Task.FromResult(query.Where(o => o.TenantId == _tenant.Id));
}
}
Registration:
builder.Services.AddAlterQueryable<Order, Order, OrderTenantFilter>();
Interceptors
Up to 5 interceptors per query type. These modify the PoweredSoft DynamicQuery criteria at query build time.
builder.Services.AddDynamicQueryInterceptor<Order, Order, OrderInterceptor>();
Source to Destination Mapping
When the entity type differs from the DTO:
// Provider returns the source entity queryable
public class OrderQueryableProvider : IQueryableProviderOverride<Order> { ... }
// Registration maps source -> destination
builder.Services.AddDynamicQueryWithProvider<Order, OrderDto, OrderQueryableProvider>();
Key Interfaces
IQueryableProvider<TSource>
Task<IQueryable<TSource>> GetQueryableAsync(object query, CancellationToken ct)
IQueryableProviderOverride<TSource> : IQueryableProvider<TSource>
// Marker interface — same method, signals override registration
IAlterQueryableService<TSource, TDestination>
Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQuery dynamicQuery, CancellationToken ct)