skills/data/specification-pattern/SKILL.md
Use when building composable query criteria with the specification pattern.
npx skillsauth add faysilalshareef/dotnet-ai-kit specification-patternInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 Infrastructuredata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.