Skip to content

Usage Patterns

Practical Use Cases

Specifications are valuable for filtering data, composing complex queries, and validating entities against business rules. They are particularly useful when working with Object-Relational Mapping (ORM) frameworks, as they can be translated into SQL queries for efficient database operations.

Using Specifications with Database Sessions

Once you've defined your specifications, you can use them with database sessions to query data efficiently. Here are practical examples:

csharp
public class ProductService
{
    private readonly IDbQuerySession _session;

    public ProductService(IDbQuerySession session)
    {
        _session = session;
    }

    public async Task<IReadOnlyList<Product>> GetAvailableProductsAsync(
        CancellationToken cancellationToken = default)
    {
        var query = _session.Query(ProductSpecification.IsInStock);
        return await query.ToListAsync(cancellationToken);
    }
}
csharp
public class ProductService
{
    private readonly IDbQuerySession _session;

    public ProductService(IDbQuerySession session)
    {
        _session = session;
    }

    public async Task<IReadOnlyList<Product>> GetDiscountedInStockProductsAsync(
        DateTimeOffset now,
        CancellationToken cancellationToken = default)
    {
        // Combine multiple specifications
        var specification = ProductSpecification.IsInStock
            .And(ProductSpecification.IsDiscountAvailable(now));

        var query = _session.Query(specification);
        return await query.ToListAsync(cancellationToken);
    }
}
csharp
public class GetProductNameQueryStrategy : EntityQueryStrategy<Product, ProductName>
{
    protected override IQueryable<ProductName> ExecuteCore(IQueryable<Product> source)
    {
        // Use specifications within query strategies
        return source
            .Where(ProductSpecification.IsInStock)
            .OrderBy(e => e.Name)
            .Select(e => new ProductName { Id = e.Id, Name = e.Name });
    }
}

Common Patterns

Parameterized Specifications

Create specifications that accept parameters to make them more flexible and reusable:

csharp
public static class ProductSpecification
{
    public static Specification<Product> HasMinimumQuantity(int minimumQuantity) =>
        Specification.Create<Product>(p => p.AvailableQuantity >= minimumQuantity);

    public static Specification<Product> InPriceRange(decimal minPrice, decimal maxPrice) =>
        Specification.Create<Product>(p => p.Price >= minPrice && p.Price <= maxPrice);

    public static Specification<Product> InCategory(string categoryName) =>
        Specification.Create<Product>(p => p.Category == categoryName);
}

// Usage
var specification = ProductSpecification.IsInStock
    .And(ProductSpecification.HasMinimumQuantity(10))
    .And(ProductSpecification.InPriceRange(10.0m, 100.0m));

Specification Chains

Build complex specifications by chaining multiple conditions:

csharp
public static class OrderSpecification
{
    public static Specification<Order> IsPending { get; } =
        Specification.Create<Order>(o => o.Status == OrderStatus.Pending);

    public static Specification<Order> IsOverdue(DateTimeOffset now) =>
        Specification.Create<Order>(o => o.DueDate < now);

    public static Specification<Order> HasValue(decimal minimumValue) =>
        Specification.Create<Order>(o => o.TotalAmount >= minimumValue);

    // Complex specification combining multiple conditions
    public static Specification<Order> RequiresAttention(DateTimeOffset now, decimal threshold) =>
        IsPending
            .And(IsOverdue(now))
            .And(HasValue(threshold));
}

Specification for Validation

Use specifications to validate entities against business rules:

csharp
public class CreateProductValidator
{
    private static readonly Specification<Product> HasValidName =
        Specification.Create<Product>(p => !string.IsNullOrWhiteSpace(p.Name) && p.Name.Length <= 100);

    private static readonly Specification<Product> HasValidPrice =
        Specification.Create<Product>(p => p.Price > 0);

    private static readonly Specification<Product> HasValidQuantity =
        Specification.Create<Product>(p => p.AvailableQuantity >= 0);

    public bool Validate(Product product, out string errorMessage)
    {
        if (!HasValidName.IsSatisfiedBy(product))
        {
            errorMessage = "Product name must be between 1 and 100 characters";
            return false;
        }

        if (!HasValidPrice.IsSatisfiedBy(product))
        {
            errorMessage = "Product price must be greater than zero";
            return false;
        }

        if (!HasValidQuantity.IsSatisfiedBy(product))
        {
            errorMessage = "Product quantity cannot be negative";
            return false;
        }

        errorMessage = string.Empty;
        return true;
    }
}

Released under the MIT License.