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 1.0.0-alpha: 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.
