agentleFS
martincjarvismartincjarvis/development-skills/skills/dotnet-specification-pattern/SKILL.md

dotnet-specification-pattern

Skill4 starsChanged 10 months agoMITView on GitHub

Prefer Specification Pattern for query composition and domain selection logic; avoid generic Repository Pattern duplication of ORM semantics.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 10 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈10

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Prefer the Specification Pattern over generic Repository Pattern for query composition and domain selection logic.

    line 11
  2. Distinctive:

    Create a named specification class implementing ISpecification<T>

    line 26
  3. Distinctive:

    Update repository to accept specifications (ListAsync, CountAsync)

    line 28
  4. Distinctive:

    Prefer Specification Pattern over a generic Repository Pattern.

    line 41
  5. Very specific:

    Keep IQueryable exposure inside infrastructure boundaries.

    line 43
  6. Distinctive:

    Use split queries or includes judiciously.

    line 66
  7. Distinctive:

    Consider compiled queries for hot paths.

    line 67
  8. Distinctive:

    Ensure specs don't inadvertently cause N+1 query patterns.

    line 68
  9. Distinctive:

    When migrating from generic repository to specification pattern:

    line 144
  10. Distinctive:

    Week 1: Add specification infrastructure (base class, repository support)

    line 189
  11. Distinctive:

    Week 4: Remove deprecated methods, enforce specs in code review

    line 192

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Overview

Prefer the Specification Pattern over generic Repository Pattern for query composition and domain selection logic. Specifications define what to fetch while infrastructure decides how to execute, enabling composable, testable selection logic without exposing ORM details.

When to Use

  • Implementing query logic beyond trivial CRUD (filtering, paging, sorting, includes)
  • Building systems with multiple query shapes or evolving selection logic
  • Reviewing code that introduces generic repository patterns with GetAll/Find methods
  • Refactoring scattered LINQ queries in handlers or controllers
  • Adding tenancy, authorization, or other cross-cutting query constraints

Core Workflow

  1. Identify selection logic that is reused or non-trivial
  2. Create a named specification class implementing ISpecification<T>
  3. Define criteria, includes, ordering, and paging in the specification
  4. Update repository to accept specifications (ListAsync, CountAsync)
  5. Replace inline LINQ chains with the named specification
  6. Compose specifications using And/Or combinators where needed

Core

When to use

  • Any query logic beyond trivial CRUD: filtering, paging, sorting, includes, tenancy constraints, authorization constraints.
  • Systems with multiple query shapes or evolving selection logic.

Defaults

  • Prefer Specification Pattern over a generic Repository Pattern.
  • Specifications define what to fetch; infrastructure decides how to execute.
  • Keep IQueryable exposure inside infrastructure boundaries; application/domain layers use specs or criteria objects.

Rationale

  • Composability and reuse (AND/OR composition).
  • Testability of selection logic.
  • Reduces accidental duplication of ORM capabilities.

Anti-patterns to avoid

  • "Generic repository" wrappers that re-expose EF semantics (GetAll, Find, etc.).
  • Leaking ORM-specific details into domain/application layers.

Load: examples

  • A ActiveCustomersForTenantSpec composed with paging/sorting.
  • A OrdersNeedingFulfilmentSpec with includes and date filters.
  • A global TenantIsolationSpec applied consistently.

Load: advanced

Performance considerations

  • Use split queries or includes judiciously.
  • Consider compiled queries for hot paths.
  • Ensure specs don't inadvertently cause N+1 query patterns.

Governance

  • Specs are versioned and discoverable; avoid ad-hoc LINQ scattered through handlers/controllers.

Load: enforcement

  • Reject introduction of new generic repository abstractions unless domain-specific and justified.
  • Query selection logic should be captured as specs when it is reused or non-trivial.

Load: specification examples

Basic Specification Implementation

// Base specification interface
public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    List<Expression<Func<T, object>>> Includes { get; }
    Expression<Func<T, object>>? OrderBy { get; }
    Expression<Func<T, object>>? OrderByDescending { get; }
    int? Take { get; }
    int? Skip { get; }
}

// Concrete specification
public class ActiveCustomersForTenantSpec : Specification<Customer>
{
    public ActiveCustomersForTenantSpec(Guid tenantId)
    {
        Criteria = c => c.TenantId == tenantId && c.IsActive;
        Includes.Add(c => c.Orders);
        OrderBy = c => c.Name;
    }
}

// Usage in repository
public async Task<List<Customer>> ListAsync(ISpecification<Customer> spec)
{
    return await ApplySpecification(spec).ToListAsync();
}

// Composing specifications
var spec = new ActiveCustomersForTenantSpec(tenantId)
    .And(new HasRecentOrdersSpec(DateTime.UtcNow.AddDays(-30)));

Ardalis.Specification Example (Recommended Library)

// Using Ardalis.Specification NuGet package
public class OrdersNeedingFulfilmentSpec : Specification<Order>
{
    public OrdersNeedingFulfilmentSpec(DateTime cutoffDate)
    {
        Query
            .Where(o => o.Status == OrderStatus.Pending)
            .Where(o => o.CreatedAt >= cutoffDate)
            .Include(o => o.Customer)
            .Include(o => o.LineItems)
            .OrderByDescending(o => o.Priority)
            .ThenBy(o => o.CreatedAt);
    }
}

// Repository usage
var orders = await _repository.ListAsync(
    new OrdersNeedingFulfilmentSpec(DateTime.UtcNow.AddDays(-7)));

Load: migration from repository pattern

Migration Path Checklist

When migrating from generic repository to specification pattern:

  • Identify query hotspots: Find all GetAll(), Find(), Where() calls
  • Extract selection logic: Create specifications for reused/complex queries
  • Replace inline LINQ: Convert ad-hoc .Where() chains to named specs
  • Remove generic methods: Deprecate GetAll(), keep only ListAsync(spec)
  • Add composition: Combine specs with .And() / .Or() where needed

Before: Generic Repository (Anti-Pattern)

// Generic repository exposes ORM semantics
public interface IRepository<T>
{
    IQueryable<T> GetAll();
    T? GetById(int id);
    IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
}

// Scattered LINQ in handlers
var customers = _repo.GetAll()
    .Where(c => c.TenantId == tenantId && c.IsActive)
    .Include(c => c.Orders)
    .OrderBy(c => c.Name)
    .ToList();

After: Specification Pattern

// Specification-based repository
public interface IRepository<T>
{
    Task<T?> GetByIdAsync(int id);
    Task<List<T>> ListAsync(ISpecification<T> spec);
    Task<int> CountAsync(ISpecification<T> spec);
}

// Named, testable specification
var customers = await _repo.ListAsync(
    new ActiveCustomersForTenantSpec(tenantId));

Migration Sequencing

  1. Week 1: Add specification infrastructure (base class, repository support)
  2. Week 2: Extract 3-5 most-used query patterns into specifications
  3. Week 3: Deprecate generic query methods (GetAll(), Find())
  4. Week 4: Remove deprecated methods, enforce specs in code review

Red Flags - STOP

These statements indicate specification pattern misuse:

ThoughtReality
"Generic repository with GetAll() is fine"GetAll() duplicates ORM semantics; use specifications
"IQueryable in handlers is convenient"Keep IQueryable in infrastructure; expose specs to application
"Each query needs its own repository method"Compose specifications instead; avoid method explosion
"Specifications add too much overhead"Named specs improve testability and reuse; worth the structure
"LINQ in handlers is more readable"Scattered LINQ causes duplication; named specs are self-documenting
"N+1 queries are fine for now"Specs should include eager loading strategy; prevent N+1 upfront

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.