Softoverse.EntityFrameworkCore.Specification 10.2.0

dotnet add package Softoverse.EntityFrameworkCore.Specification --version 10.2.0
                    
NuGet\Install-Package Softoverse.EntityFrameworkCore.Specification -Version 10.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Softoverse.EntityFrameworkCore.Specification" Version="10.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Softoverse.EntityFrameworkCore.Specification" Version="10.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Softoverse.EntityFrameworkCore.Specification" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Softoverse.EntityFrameworkCore.Specification --version 10.2.0
                    
#r "nuget: Softoverse.EntityFrameworkCore.Specification, 10.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Softoverse.EntityFrameworkCore.Specification@10.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Softoverse.EntityFrameworkCore.Specification&version=10.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Softoverse.EntityFrameworkCore.Specification&version=10.2.0
                    
Install as a Cake Tool

Specification Pattern for Entity Framework Core

NuGet License

A powerful, lightweight implementation of the Specification Pattern for Entity Framework Core. Encapsulate your query logic into reusable, testable classes and keep your repositories clean.

Table of Contents


Installation

Install via NuGet Package Manager Console:

Install-Package Softoverse.EntityFrameworkCore.Specification

Or via .NET CLI:

dotnet add package Softoverse.EntityFrameworkCore.Specification

Quick Start

1. Define a Specification

public class ActivePremiumUsersSpec : Specification<User>
{
    public ActivePremiumUsersSpec()
    {
        Criteria = u => u.IsActive && u.IsPremium;
        
        // Eager load related data
        Include(u => u.Profile);
        Include(u => u.Orders)
            .ThenInclude(o => o.OrderItems);
            
        // Ordering
        OrderByDescending(u => u.CreatedAt);
        
        // Query options
        AsNoTracking = true;
    }
}

2. Apply it in your Repository

public async Task<List<User>> GetPremiumUsersAsync(ISpecification<User> spec)
{
    // ApplySpecification is an extension method for IQueryable<T>, DbSet<T>, and DbContext
    return await _context.Users.ApplySpecification(spec).ToListAsync();
}

Core Features

Filtering (Criteria)

The Criteria property defines the Where clause of your query.

var spec = new Specification<User>
{
    Criteria = u => u.Age > 18
};

You can also pass multiple expressions to the constructor:

var spec = new Specification<User>(
    [u => u.IsActive, u => u.Email.Contains("@gmail.com")], 
    CombineType.And
);

Includes (Eager Loading)

The library provides a fluent API for Include and ThenInclude, supporting both collections and single navigation properties.

Simple & Nested Includes
spec.Include(u => u.Orders)
    .ThenInclude(o => o.Items);
String-based Includes
spec.IncludeString("Orders.Items");
Filtered Includes (EF Core 5+)

You can apply filters directly within the Include:

spec.Include(u => u.Orders.Where(o => o.Status == OrderStatus.Shipped));

Ordering

The library supports fluent OrderBy and ThenBy methods for multi-level sorting, providing a chainable API similar to LINQ.

Simple Ordering
// Ascending order
spec.OrderBy(u => u.LastName);
// SQL: ORDER BY [u].[LastName]

// Descending order
spec.OrderByDescending(u => u.CreatedAt);
// SQL: ORDER BY [u].[CreatedAt] DESC
Multi-Level Ordering with ThenBy

Chain multiple ordering levels for complex sorting scenarios:

// Order by multiple fields
spec.OrderBy(u => u.IsActive)
    .ThenByDescending(u => u.CreatedAt)
    .ThenBy(u => u.LastName);
// SQL: ORDER BY [u].[IsActive], [u].[CreatedAt] DESC, [u].[LastName]
Combining with Includes

OrderBy can be seamlessly chained with Include operations:

// Chain OrderBy after Include
spec.Include(u => u.Orders)
    .ThenInclude(o => o.Items)
    .OrderByDescending(u => u.CreatedAt);

// Or chain Include after OrderBy
spec.OrderBy(u => u.Name)
    .Include(u => u.Profile);
Real-World Examples
// E-commerce: Sort products by category, then rating, then price
spec.Criteria = p => p.InStock;
spec.OrderBy(p => p.CategoryId)
    .ThenByDescending(p => p.AverageRating)
    .ThenBy(p => p.Price);

// User management: Sort by department, last name, first name
spec.OrderBy(u => u.Department)
    .ThenBy(u => u.LastName)
    .ThenBy(u => u.FirstName);

// Blog: Featured posts first, then by date
spec.OrderByDescending(p => p.IsFeatured)
    .ThenByDescending(p => p.PublishedDate);
Important Notes
  • Database-level sorting: All ordering is applied at the SQL level, not in memory
  • Performance: Add database indexes on frequently ordered columns for optimal performance
  • OrderBy clears previous ordering: Calling OrderBy() or OrderByDescending() clears any previous ordering
  • Use ThenBy for secondary sorts: Always use ThenBy()/ThenByDescending() for additional sorting levels

Migration Note: The old AddOrderBy() and AddOrderByDescending() methods are still supported but marked as obsolete. Migrate to the new fluent API:

// ❌ Old (Obsolete)
spec.AddOrderBy(u => u.Name);

// ✅ New (Recommended)
spec.OrderBy(u => u.Name);

Projections

Use SetProjection to select only the fields you need.

spec.SetProjection(u => new { u.Id, u.FullName });

No-Tracking & Split Queries

Optimize performance for read-only or complex queries.

spec.AsNoTracking = true;
spec.AsSplitQuery = true;

Advanced Usage

Dynamic Expression Generation

The ToConditionalExpression static methods allow you to generate expressions dynamically based on values and operators.

// Generates: u => u.Age > 25
var ageExp = Specification<User>.ToConditionalExpression(
    u => u.Age, 
    25, 
    CompareOperation.GreaterThan
);

// Generates: u => u.Name == "John"
var nameExp = Specification<User>.ToConditionalExpression(
    u => u.Name, 
    "John", 
    EqualOperation.Equal
);

Smart Filtering (ToConditionalExpressionInternal)

The ToConditionalExpression method internally uses ToConditionalExpressionInternal to provide "Smart Filtering" capabilities. This allows you to generate complex expressions using simple string patterns, which is especially useful for handling query string parameters from an API.

Supported Operators
Operator Description Example
eq / ne Equal / Not Equal eq:John, ne:John
gt / gte Greater Than / Or Equal gt:18, gte:18
lt / lte Less Than / Or Equal lt:18, lte:18
eqci Equal (Case-Insensitive) eqci:john
like / likeci Contains / Case-Insensitive like:jo, likeci:JO
range Range (Inclusive) range:18,30
in / nin In List / Not In List in:1,2,3, nin:1,2,3
inci / ninci In/Not In List (Case-Insensitive) inci:a,b, ninci:c,d
inlike / ninlike In/Not In List (Pattern Match) inlike:a,b, ninlike:x,y
inlikeci / ninlikeci Case-Insensitive Pattern Match inlikeci:A,B
Usage Example
// These strings would typically come from your API query parameters
string ageFilter = "gt:18";
string nameFilter = "like:John";
string categoryFilter = "in:Electronics,Books";

var spec = new Specification<User>();
spec.Criteria = Specification<User>.ToConditionalExpression(u => u.Age, ageFilter);
spec.Criteria = spec.Criteria.And(Specification<User>.ToConditionalExpression(u => u.Name, nameFilter));
spec.Criteria = spec.Criteria.And(Specification<User>.ToConditionalExpression(u => u.Category, categoryFilter));

Bulk Updates (ExecuteUpdate)

Integration with EF Core's ExecuteUpdate for efficient bulk operations.

// Option 1: Using property selectors
var spec = new Specification<User>
{
    Criteria = u => u.Id == 1,
    ExecuteUpdateProperties = [u => u.Name, u => u.Email]
};
// After updating properties on the user object:
await context.Users.ExecuteUpdateAsync(spec, userObject);

// Option 2: Using fluent SetProperty
spec.SetExecuteUpdateExpression(setters => setters
    .SetProperty(u => u.IsActive, true)
    .SetProperty(u => u.LastLogin, DateTime.UtcNow));

Expression Combiner

The ExpressionBuilder provides extension methods to combine expressions manually.

Expression<Func<User, bool>> criteria1 = u => u.IsActive;
Expression<Func<User, bool>> criteria2 = u => u.Age > 18;

var combined = criteria1.And(criteria2);
var either = criteria1.Or(criteria2);

Repository Integration

A typical generic repository implementation:

public class Repository<T> where T : class
{
    private readonly DbContext _context;
    public Repository(DbContext context) => _context = context;

    public async Task<List<T>> ListAsync(ISpecification<T> spec)
    {
        return await _context.Set<T>()
            .ApplySpecification(spec)
            .ToListAsync();
    }
    
    public async Task<T?> GetAsync(ISpecification<T> spec)
    {
        return await _context.Set<T>()
            .ApplySpecification(spec)
            .FirstOrDefaultAsync();
    }
}

CQRS Integration

For projects using the CQRS pattern (e.g., with MediatR), you can use the ISpecificationRequest<TEntity> interface to bridge your queries and specifications.

Defining a Specification Request

Inherit your query classes from ISpecificationRequest<TEntity> to standardize how specifications are retrieved from requests.

public class GetActiveUsersQuery : ISpecificationRequest<User>
{
    public string SearchTerm { get; set; }

    public ISpecification<User> GetSpecification(bool asNoTracking = false, bool asSplitQuery = false)
    {
        var spec = new Specification<User>
        {
            AsNoTracking = asNoTracking,
            AsSplitQuery = asSplitQuery
        };

        if (!string.IsNullOrWhiteSpace(SearchTerm))
        {
            spec.Criteria = u => u.IsActive && u.Name.Contains(SearchTerm);
        }
        else
        {
            spec.Criteria = u => u.IsActive;
        }

        spec.Include(u => u.Profile);
        spec.OrderBy(u => u.Name);

        return spec;
    }
}

Usage in a Query Handler

In your handler, simply call GetSpecification() to obtain the specification and apply it to your IQueryable.

public class GetActiveUsersHandler
{
    private readonly ApplicationDbContext _context;

    public GetActiveUsersHandler(ApplicationDbContext context) => _context = context;

    public async Task<List<User>> Handle(GetActiveUsersQuery request)
    {
        var spec = request.GetSpecification(asNoTracking: true);
        
        return await _context.Users
            .ApplySpecification(spec)
            .ToListAsync();
    }
}

Full API Reference

Interfaces

ISpecification<TEntity>

The primary interface for defining specifications.

  • Criteria: The filter expression.
  • AsNoTracking: Boolean for tracking behavior.
  • AsSplitQuery: Boolean for query splitting behavior.
  • OrderByExpression: (Obsolete) Primary sort expression. Use OrderBy() method instead.
  • OrderByDescendingExpression: (Obsolete) Primary descending sort expression. Use OrderByDescending() method instead.
  • OrderByExpressions: List of ordering expressions with direction indicators for multi-level sorting.
  • ProjectionExpression: Expression for projecting to a different type.
  • ExecuteUpdateExpression: Action for bulk updates using UpdateSettersBuilder.
  • ExecuteUpdateProperties: List of property selectors for object-based bulk updates.
ISpecificationRequest<TEntity>

Used for bridging Query objects (CQRS) with Specifications.

  • GetSpecification(bool asNoTracking, bool asSplitQuery): Returns an ISpecification<TEntity>.
ISpecificationForPrimaryKey

Base interface for specifications that might use a primary key.

  • PrimaryKey: The primary key value.
IIncludableSpecification<TEntity, TProperty>

Enables fluent ThenInclude chaining.

  • ThenInclude<TNextProperty>(expression): Chains a sub-property load.
IOrderableSpecification<TEntity, TProperty>

Enables fluent ThenBy and ThenByDescending chaining for multi-level sorting.

  • ThenBy<TNextProperty>(keySelector): Chains an ascending sort on a property.
  • ThenByDescending<TNextProperty>(keySelector): Chains a descending sort on a property.

Classes

Specification<TEntity>

The base implementation of ISpecification<TEntity>.

  • Constructors:
    • Specification(): Empty specification.
    • Specification(List<Expression>, CombineType): Combines multiple expressions.
    • Specification(object primaryKey): Specification for a specific record.
  • Methods:
    • Include(expression): Adds an eager load.
    • IncludeString(path): Adds a string-based eager load.
    • OrderBy(keySelector): Sets ascending order and returns an IOrderableSpecification for chaining.
    • OrderByDescending(keySelector): Sets descending order and returns an IOrderableSpecification for chaining.
    • AddOrderBy(expression): (Obsolete) Sets the ascending order. Use OrderBy() instead.
    • AddOrderByDescending(expression): (Obsolete) Sets the descending order. Use OrderByDescending() instead.
    • SetProjection(expression): Sets the selection projection.
    • SetExecuteUpdateExpression(action): Sets the fluent bulk update logic.
    • AddExecuteUpdateProperties(expression): Adds a property for bulk update.
    • ToConditionalExpression(...): Static helper to generate expressions from values/operators.
ExpressionGenerator<TEntity> (Static)

Helper for generating complex expressions dynamically.

  • BuildUpdateExpression(properties, model): Generates an ExecuteUpdate expression from an object and selected properties.
  • BuildUpdateExpression(propertyUpdates): Generates an ExecuteUpdate expression from a dictionary of property paths and values.

Enums

CombineType

Used when combining multiple expressions in the constructor.

  • And: All criteria must be met.
  • Or: At least one criteria must be met.
EqualOperation / CompareOperation

Used in ToConditionalExpression to define comparison logic.

  • Equal, NotEqual, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual.

Extension Methods

SpecificationEvaluator
  • ApplySpecification(queryable, specification): Applies all specification logic to an IQueryable.
  • ApplySpecification(dbSet, specification): Helper for DbSet.
  • ApplySpecification(dbContext, specification): Helper for DbContext.
ExpressionBuilder
  • And(left, right): Combines two expressions with AND.
  • Or(left, right): Combines two expressions with OR.
  • Not(expression): Negates an expression.
  • When(expression, condition): Conditionally applies an expression.
  • CombineWithAnd(expressions): Aggregates multiple expressions with AND.
  • CombineWithOr(expressions): Aggregates multiple expressions with OR.

Best Practices

  1. Encapsulation: Put complex query logic inside named Specification classes instead of building them in your services or repositories.
  2. Reusability: Use small, focused specifications and combine them if necessary.
  3. Read-Only: Always use AsNoTracking = true for specifications intended for read-only display to improve performance.

Compatibility

  • .NET 6.0+
  • Entity Framework Core 6.0+

License

Licensed under the Apache License, Version 2.0. See LICENSE for details.

Support

For issues, please open a GitHub issue in the repository.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.2.0 82 1/20/2026
10.0.0 102 12/31/2025
9.0.0 88 12/31/2025
2.5.1 135 9/27/2025
2.5.0 245 4/11/2025
2.4.2 146 2/16/2025
2.4.1 142 2/16/2025
2.4.0 163 2/16/2025
2.3.3 143 2/16/2025
2.3.2 146 2/16/2025
2.3.1 141 2/16/2025
2.3.0 151 2/16/2025
2.2.0 168 2/10/2025
2.1.1 166 2/10/2025
2.1.0 171 2/10/2025
2.0.4 160 2/9/2025
2.0.3 152 2/9/2025
2.0.2 155 2/9/2025
2.0.1 142 2/9/2025
2.0.0 156 2/9/2025
1.3.1 159 2/9/2025
1.3.0 148 2/9/2025
1.2.1 137 2/9/2025
1.2.0 153 2/8/2025
1.1.0 156 2/8/2025
1.0.0 162 2/8/2025