Why the Specification Pattern
Overview
Section titled “Overview”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.
Target Audience
Section titled “Target Audience”| Level | Audience | Recommended Scope |
|---|---|---|
| Beginner | Developers who know basic C# syntax and want to get started with the Specification pattern | Part 1 |
| Intermediate | Developers who understand the pattern and want practical application | Parts 1—3 |
| Advanced | Developers interested in architecture design and domain modeling | Parts 4—5 + Appendix |
Learning Prerequisites
Section titled “Learning Prerequisites”The following knowledge is needed to effectively learn this tutorial:
Required
Section titled “Required”- Understanding of basic C# syntax (classes, interfaces, generics)
- Object-oriented programming fundamentals
- Experience running .NET projects
Recommended (Nice to Have)
Section titled “Recommended (Nice to Have)”- Basic LINQ syntax
- Unit testing experience
- Domain-Driven Design (DDD) basic concepts
- Basic understanding of Expression Trees
Expected Outcomes
Section titled “Expected Outcomes”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 logicpublic List<Product> GetActiveProducts(List<Product> products){ return products.Where(p => p.IsActive && !p.IsDiscontinued).ToList();}
// Good: Domain rules encapsulated with Specificationvar 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 Specificationsvar isActive = new ActiveProductSpec();var isInStock = new ProductInStockSpec();var isPremium = new PremiumProductSpec();
// Express compound rules through compositionvar 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 Corepublic sealed class ActiveProductSpec : ExpressionSpecification<Product>{ public override Expression<Func<Product, bool>> ToExpression() => product => product.IsActive && !product.IsDiscontinued;}
// Use directly in Repositoryvar 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 Specificationpublic interface IProductRepository{ Task<IReadOnlyList<Product>> FindAsync(Specification<Product> spec); Task<int> CountAsync(Specification<Product> spec);}
// Dynamic filter chainingvar 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);Comparison with Quick Tutorials
Section titled “Comparison with Quick Tutorials”The following table compares quick practice-oriented tutorials with this in-depth tutorial.
| Aspect | Quick Tutorial | This Tutorial |
|---|---|---|
| Purpose | Quick practice and result verification | Concept understanding and design principle learning |
| Depth | Core usage focused | Internal implementation and principle deep dive |
| Scope | Basic Specification usage | Expression Trees, Repository integration, testing strategies |
| Audience | Developers wanting immediate application | Developers wanting deep pattern understanding |
Learning Path
Section titled “Learning Path”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 examplesQ1: 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.
Next Steps
Section titled “Next Steps”Now that we have reviewed the structure and goals of this tutorial, let’s first prepare the practice environment.