Pragmatic.Specification
The specification pattern for composable, reusable query predicates in .NET 10.
The Problem
Section titled “The Problem”Business rules embedded in queries get duplicated across repositories, services, and validators — each copy an inline lambda. Change the rule and you hunt every method; worse, SQL (expression trees) and in-memory checks (compiled delegates) drift apart.
db.Orders.Where(o => !o.IsCancelled && !o.IsDeleted) // repositorydb.Orders.Where(o => !o.IsCancelled && !o.IsDeleted && o.Total >= t) // service — same rule again!order.IsCancelled && !order.IsDeleted // validator — same rule, driftsThe Solution
Section titled “The Solution”Encapsulate a rule once as a Specification<T>, compose with And/Or/Not, and use the same
specification for EF Core queries (translated to SQL) and in-memory checks (IsSatisfiedBy).
public sealed class ActiveOrder : Specification<Order>{ public override Expression<Func<Order, bool>> ToExpression() => o => !o.IsCancelled && !o.IsDeleted;}
db.Orders.Where(new ActiveOrder().And(new OrderOverThreshold(100m))); // → SQLbool ok = new ActiveOrder().IsSatisfiedBy(order); // in-memory, same ruleOne definition, reusable and independently unit-testable; dynamic filters compose without if-chains.
Installation
Section titled “Installation”dotnet add package Pragmatic.SpecificationStatus
Section titled “Status”Stable within the 0.8 preview — composition (And/Or/Not), EF Core translation, and in-memory
evaluation are settled. See the roadmap.
| Concepts | The pattern, expression vs delegate, when to use it |
| Getting Started | Your first specification, querying, in-memory checks |
| Composition Patterns | And/Or/Not, dynamic filters from user input, reusable building blocks |
| Common Mistakes | The most frequent specification pitfalls |
| Troubleshooting | Problem/solution guide |
Requirements
Section titled “Requirements”- .NET 10.0+
License
Section titled “License”Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Specification is MIT-licensed.