Skip to content

Why the Specification Pattern

Have you ever sighed every time you had to add another method to a Repository? GetActiveProducts, GetActiveProductsByCategory, GetActiveProductsByCategoryAndPrice… As condition combinations grow, the Repository interface becomes bloated, and the same conditions are duplicated across multiple methods.

This tutorial covers the entire process of solving this problem with the Specification pattern. Starting from a basic Specification class and progressing to Expression Tree-based Repository integration, you can systematically learn every aspect of the Specification pattern through 18 hands-on projects.


LevelAudienceRecommended Scope
BeginnerDevelopers who know basic C# syntax and want to get started with the Specification patternPart 1
IntermediateDevelopers who understand the pattern and want practical applicationParts 1—3
AdvancedDevelopers interested in architecture design and domain modelingParts 4—5 + Appendix

The following knowledge is needed to effectively learn this tutorial:

  • Understanding of basic C# syntax (classes, interfaces, generics)
  • Object-oriented programming fundamentals
  • Experience running .NET projects
  • Basic LINQ syntax
  • Unit testing experience
  • Domain-Driven Design (DDD) basic concepts
  • Basic understanding of Expression Trees

After completing this tutorial, you will be able to:

1. Encapsulate business rules into reusable Specifications

Section titled “1. Encapsulate business rules into reusable Specifications”
// Bad: Conditions scattered across service logic
public List<Product> GetActiveProducts(List<Product> products)
{
return products.Where(p => p.IsActive && !p.IsDiscontinued).ToList();
}
// Good: Domain rules encapsulated with Specification
var spec = new ActiveProductSpec();
var activeProducts = products.Where(spec.IsSatisfiedBy).ToList();

2. Express compound rules using And, Or, Not composition

Section titled “2. Express compound rules using And, Or, Not composition”
// Define individual Specifications
var isActive = new ActiveProductSpec();
var isInStock = new ProductInStockSpec();
var isPremium = new PremiumProductSpec();
// Express compound rules through composition
var availablePremium = isActive & isInStock & isPremium;
var discountTarget = isActive & (isPremium | !isInStock);

3. Implement ORM-compatible Specifications using Expression Trees

Section titled “3. Implement ORM-compatible Specifications using Expression Trees”
// Expression-based Specification -> Translated to SQL by EF Core
public sealed class ActiveProductSpec : ExpressionSpecification<Product>
{
public override Expression<Func<Product, bool>> ToExpression() =>
product => product.IsActive && !product.IsDiscontinued;
}
// Use directly in Repository
var products = await repository.FindAsync(new ActiveProductSpec());

4. Flexibly query data by integrating Repository with Specification

Section titled “4. Flexibly query data by integrating Repository with Specification”
// Repository method that accepts Specification
public interface IProductRepository
{
Task<IReadOnlyList<Product>> FindAsync(Specification<Product> spec);
Task<int> CountAsync(Specification<Product> spec);
}
// Dynamic filter chaining
var spec = Specification<Product>.All;
if (filter.Category is not null)
spec &= new ProductCategorySpec(filter.Category);
if (filter.MinPrice is not null)
spec &= new MinPriceSpec(filter.MinPrice.Value);

The following table compares quick practice-oriented tutorials with this in-depth tutorial.

AspectQuick TutorialThis Tutorial
PurposeQuick practice and result verificationConcept understanding and design principle learning
DepthCore usage focusedInternal implementation and principle deep dive
ScopeBasic Specification usageExpression Trees, Repository integration, testing strategies
AudienceDevelopers wanting immediate applicationDevelopers wanting deep pattern understanding

Beginner (Part 1: Chapters 1-4)
├── First Specification implementation
├── And, Or, Not composition
├── Operator overloading
└── All identity element and dynamic chaining
Intermediate (Parts 2-3: Chapters 1-4 each)
├── Expression Tree-based Specification
├── Value Object conversion pattern
├── Repository integration
└── EF Core implementation
Advanced (Parts 4-5 + Appendix)
├── CQRS + Specification
├── Dynamic filter builder
├── Testing strategies
└── Domain-specific practical examples

Q1: Is the Specification pattern necessary for every project?

Section titled “Q1: Is the Specification pattern necessary for every project?”

A: No. In simple CRUD applications where query conditions are fixed to 1-2, it can become excessive abstraction. It proves its value in domains where condition combinations are diverse, dynamic filtering is needed, and search logic reuse is important.

Q2: What is the difference between Specification and LINQ Where?

Section titled “Q2: What is the difference between Specification and LINQ Where?”

A: When passing lambdas directly to LINQ Where, conditions are scattered at call sites making reuse difficult. Specifications encapsulate conditions as independent classes, conveying intent through names, composable with And, Or, Not, and individually unit-testable.

Q3: Why does ExpressionSpecification<T> exist separately?

Section titled “Q3: Why does ExpressionSpecification<T> exist separately?”

A: Specification<T>’s IsSatisfiedBy can only evaluate in C# memory. ExpressionSpecification<T> provides Expression<Func<T, bool>> so ORMs like EF Core can translate it to SQL. This enables filtering at the database level.

Q4: What order should this tutorial be studied in?

Section titled “Q4: What order should this tutorial be studied in?”

A: Beginners should proceed sequentially starting from Part 1 (Specification Basics). After learning IsSatisfiedBy, And/Or/Not composition, and operator overloading, moving to Part 2 (Expression Specification) is natural. If you have Repository integration experience, you can start from Part 3.


Now that we have reviewed the structure and goals of this tutorial, let’s first prepare the practice environment.

-> 0.2 Prerequisites and Environment Setup