Skip to content

Pragmatic.Specification

The specification pattern for composable, reusable query predicates in .NET 10.

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) // repository
db.Orders.Where(o => !o.IsCancelled && !o.IsDeleted && o.Total >= t) // service — same rule again
!order.IsCancelled && !order.IsDeleted // validator — same rule, drifts

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))); // → SQL
bool ok = new ActiveOrder().IsSatisfiedBy(order); // in-memory, same rule

One definition, reusable and independently unit-testable; dynamic filters compose without if-chains.

Terminal window
dotnet add package Pragmatic.Specification

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 |

  • .NET 10.0+

Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Specification is MIT-licensed.