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 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 |

  • .NET 10.0+

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