Specification pattern for composable query criteria. ISpecification interface, BaseSpecification with expression trees, repository integration.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
namespace {Company}.{Domain}.Domain.Interfaces;
public interface ISpecification<T>
{
Expression<Func<T, bool>>? Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
Expression<Func<T, object>>? OrderBy { get; }
Expression<Func<T, object>>? OrderByDescending { get; }
int? Take { get; }
int? Skip { get; }
bool IsPagingEnabled { get; }
}
namespace {Company}.{Domain}.Domain.Specifications;
public abstract class BaseSpecification<T> : ISpecification<T>
{
public Expression<Func<T, bool>>? Criteria { get; private set; }
public List<Expression<Func<T, object>>> Includes { get; } = [];
public List<string> IncludeStrings { get; } = [];
public Expression<Func<T, object>>? OrderBy { get; private set; }
public Expression<Func<T, object>>? OrderByDescending { get; private set; }
public int? Take { get; private set; }
public int? Skip { get; private set; }
public bool IsPagingEnabled { get; private set; }
protected BaseSpecification() { }
protected BaseSpecification(Expression<Func<T, bool>> criteria)
=> Criteria = criteria;
protected void AddInclude(Expression<Func<T, object>> include)
=> Includes.Add(include);
protected void AddInclude(string include)
=> IncludeStrings.Add(include);
protected void ApplyOrderBy(Expression<Func<T, object>> orderBy)
=> OrderBy = orderBy;
protected void ApplyOrderByDescending(Expression<Func<T, object>> orderByDesc)
=> OrderByDescending = orderByDesc;
protected void ApplyPaging(int skip, int take)
{
Skip = skip;
Take = take;
IsPagingEnabled = true;
}
}
public sealed class ActiveOrdersByCustomerSpec : BaseSpecification<Order>
{
public ActiveOrdersByCustomerSpec(Guid customerId, int page, int pageSize)
: base(o => o.CustomerId == customerId && o.Status != OrderStatus.Cancelled)
{
AddInclude(o => o.Items);
ApplyOrderByDescending(o => o.CreatedAt);
ApplyPaging((page - 1) * pageSize, pageSize);
}
}
public interface IRepository<T> where T : class
{
Task<T?> GetBySpecAsync(ISpecification<T> spec, CancellationToken ct = default);
Task<List<T>> ListAsync(ISpecification<T> spec, CancellationToken ct = default);
Task<int> CountAsync(ISpecification<T> spec, CancellationToken ct = default);
}
public sealed class Repository<T>(ApplicationDbContext context)
: IRepository<T> where T : class
{
public async Task<List<T>> ListAsync(
ISpecification<T> spec, CancellationToken ct = default)
{
return await ApplySpecification(spec).ToListAsync(ct);
}
public async Task<int> CountAsync(
ISpecification<T> spec, CancellationToken ct = default)
{
return await ApplySpecification(spec).CountAsync(ct);
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(
context.Set<T>().AsQueryable(), spec);
}
}
public static class SpecificationEvaluator<T> where T : class
{
public static IQueryable<T> GetQuery(
IQueryable<T> query, ISpecification<T> spec)
{
if (spec.Criteria is not null)
query = query.Where(spec.Criteria);
query = spec.Includes.Aggregate(query,
(current, include) => current.Include(include));
query = spec.IncludeStrings.Aggregate(query,
(current, include) => current.Include(include));
if (spec.OrderBy is not null)
query = query.OrderBy(spec.OrderBy);
else if (spec.OrderByDescending is not null)
query = query.OrderByDescending(spec.OrderByDescending);
if (spec.IsPagingEnabled)
query = query.Skip(spec.Skip!.Value).Take(spec.Take!.Value);
return query;
}
}
| Anti-Pattern | Correct Approach |
|---|---|
| Complex Where clauses in repositories | Encapsulate in specification |
| Duplicating query logic | Reuse specifications |
| Specifications with side effects | Keep specifications pure (queries only) |
grep -r "ISpecification\|BaseSpecification" --include="*.cs"
grep -r "SpecificationEvaluator" --include="*.cs"
ISpecification<T> interface in Domain layerBaseSpecification<T> in DomainSpecificationEvaluator<T> in Infrastructure