Prakrishta.Data 1.2.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package Prakrishta.Data --version 1.2.2
                    
NuGet\Install-Package Prakrishta.Data -Version 1.2.2
                    
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="Prakrishta.Data" Version="1.2.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Prakrishta.Data" Version="1.2.2" />
                    
Directory.Packages.props
<PackageReference Include="Prakrishta.Data" />
                    
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 Prakrishta.Data --version 1.2.2
                    
#r "nuget: Prakrishta.Data, 1.2.2"
                    
#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 Prakrishta.Data@1.2.2
                    
#: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=Prakrishta.Data&version=1.2.2
                    
Install as a Cake Addin
#tool nuget:?package=Prakrishta.Data&version=1.2.2
                    
Install as a Cake Tool

Prakrishta.Data

Overview

Prakrishta.Data is a lightweight, extensible data access library for Entity Framework Core that provides:

  • Unit of Work pattern for transactional consistency.
  • Generic Query Repository for read-only operations.
  • Generic Persistence Repository for full CRUD operations.
  • Specification Pattern for reusable, composable query logic.
  • Support for:
    • Synchronous and asynchronous methods.
    • Pagination.
    • Sorting.
    • Change tracking control.
    • Eager loading via Include expressions or string paths.
  • First-class DI integration with .NET Core.

Build status: Build Status

Package installation:

Install-Package Prakrishta.Data

Core concepts

Unit of work

The Unit of Work coordinates repositories and manages the underlying DbContext lifetime and transactions.

IUnitOfWorkV2<DatabaseContext> unitOfWork = new UnitOfWorkV2<DatabaseContext>(databaseContext);
var readRepository = unitOfWork.GetQueryRepository<User>();

From the unit of work, you can obtain:

  • Query repository (read-only):
var readRepository = unitOfWork.GetQueryRepository<User>();
  • Persistence repository (CRUD):
var readRepository = unitOfWork.GetPersistenceRepository<User>();

Generic overloads with key type are also supported:

var readRepository = unitOfWork.GetQueryRepository<User, Guid>();
var persistenceRepository = unitOfWork.GetPersistenceRepository<User, Guid>();

Repository types

Query repository The query repository is optimized for read operations:

  • Get all entities.
  • Filtered queries.
  • Sorting.
  • Pagination.
  • Eager loading of related entities.
  • Optional change tracking.

Example:

var users = _uow
    .GetQueryRepository<User, Guid>()
    .GetAll();

With includes:

var usersWithAddress = _uow
    .GetQueryRepository<User, Guid>()
    .GetAll("Address");

Persistence repository

The persistence repository supports full CRUD:

  • Add / AddRange
  • Update / UpdateRange
  • Delete / DeleteRange

Example:

var repo = _uow.GetPersistenceRepository<User, Guid>();

repo.Add(newUser);
_uow.SaveChanges();

Async:

await repo.AddAsync(newUser);
await _uow.SaveChangesAsync();

Dependency injection and middleware integration

You can register the unit of work in your .NET Core application using the provided extension:

services.AddUnitOfWork<UserContext>();

Once registered, inject IUnitOfWorkV2<TContext> wherever needed. Example: constructor injection in a controller:

public class UserController : ControllerBase
{
    private readonly IUnitOfWorkV2<UserContext> _uow;

    public UserController(IUnitOfWorkV2<UserContext> uow)
    {
        _uow = uow;
    }

    [HttpGet]
    public ActionResult<IEnumerable<User>> Get()
    {
        var userList = _uow
            .GetQueryRepository<User, Guid>()
            .GetAll();

        return Ok(userList);
    }
}

With includes:

[HttpGet]
public ActionResult<IEnumerable<User>> Get()
{
    var userList = _uow
        .GetQueryRepository<User, Guid>()
        .GetAll("Address");

    return Ok(userList);
}

Query capabilities

Filtering

All read methods support filtering via predicates:

var repo = _uow.GetQueryRepository<User, Guid>();

var activeUsers = repo.Get(u => u.IsActive);

Async:

var activeUsers = await repo.GetAsync(u => u.IsActive);

Sorting

Sorting can be applied via parameters or overloads (depending on your API shape). For example:

var sortedUsers = repo.Get(
    filter: u => u.IsActive,
    orderBy: q => q.OrderBy(u => u.LastName).ThenBy(u => u.FirstName));

Pagination

Pagination is supported out of the box:

var pagedUsers = repo.Get(
    filter: u => u.IsActive,
    orderBy: q => q.OrderBy(u => u.LastName),
    skip: 1,
    take: 20    
    );

Async:

var pagedUsers = repo.GetAsync(
    filter: u => u.IsActive,
    orderBy: q => q.OrderBy(u => u.LastName),
    skip: 1,
    take: 20    
    );

Change tracking

You can control whether EF Core tracks entities:

var trackedUsers = repo.GetAll(asNoTracking: true);
var untrackedUsers = repo.GetAll(asNoTracking: false);

Specification pattern integration

To avoid repository method explosion and centralize query logic, Prakrishta.Data supports the Specification Pattern on top of the existing Unit of Work and repositories.

Specification abstractions

A simple base specification interface and implementation:

public interface ISpecification<T>
{
    Expression<Func<T, bool>>? Criteria { get; }
    Func<IQueryable<T>, IOrderedQueryable<T>>? OrderBy { get; }
    IList<Expression<Func<T, object>>>? IncludeProperties { get; }
    int? PageNumber { get; }
    int? PageSize { get; }
    bool IsPagingEnabled { get; }
    bool AsNoTracking { get; }
}

public abstract class Specification<T> : ISpecification<T>
{
    public Expression<Func<T, bool>>? Criteria { get; protected set; }
    public Func<IQueryable<T>, IOrderedQueryable<T>>? OrderBy { get; protected set; }
    public IList<Expression<Func<T, object>>>? IncludeProperties { get; protected set; } = []
    public int? PageNumber { get; protected set; }
    public int? PageSize { get; protected set; }
    public bool IsPagingEnabled { get; protected set; }
    public bool AsNoTracking { get; protected set; } = true;


    protected void AddInclude(Expression<Func<T, object>> includeProperty)
    {
        if (IncludeProperties is null)
        {
            throw new InvalidOperationException("IncludeProperties collection is null.");
        }

        IncludeProperties.Add(includeProperty);
    }

    protected void ApplyPaging(int pageNumber, int pageSize)
    {
        PageNumber = pageNumber;
        PageSize = pageSize;
        IsPagingEnabled = true;
    }

    protected void ApplyOrderBy(Func<IQueryable<T>, IOrderedQueryable<T>> orderByExpression) => OrderBy = orderByExpression;

    protected void TrackChanges() => AsNoTracking = false;

}

Specification evaluator

The evaluator translates a specification into an IQueryable<T>:

public static IQueryable<T> EvaluateQuery<T>(IQueryable<T> inputQuery,
    ISpecification<T> spec) where T : class
{
    IQueryable<T> query = inputQuery;

    if (spec.Criteria is not null)
        query = query.Where(spec.Criteria);

    foreach (var include in spec.IncludeProperties??[])
        query = query.Include(include);

    if (spec.PageNumber.HasValue && spec.PageSize.HasValue)
    {
        var skip = (spec.PageNumber.Value - 1) * spec.PageSize.Value;
        query = query.Skip(skip).Take(spec.PageSize.Value);
    }

    if (spec.AsNoTracking)
      query = query.AsNoTracking();

    return query;
}

Specification-enabled repositories

You can extend your existing repositories to support specification-based methods. Query repository with specifications

public interface IQueryRepository<TEntity, TId> where TEntity : class, IAuditableBaseEntity<TId>
{
    Task<IReadOnlyList<TEntity?>> GetAllAsync(ISpecification<TEntity> specification, CancellationToken cancellationToken = default);
    Task<TEntity?> GetFirstAsync(ISpecification<TEntity> specification, CancellationToken cancellationToken);
}

Implementation:

public class QueryRepository<TEntity, TId>(DbContext dbContext) : RepositoryBase<TEntity>(dbContext)
    , IQueryRepository<TEntity, TId> where TEntity : class, IAuditableBaseEntity<TId>
{
    /// <inheritdoc />
    public async Task<IReadOnlyList<TEntity?>> GetAllAsync(ISpecification<TEntity> specification, CancellationToken cancellationToken = default)
    {
        var query = SpecificationExecutor.EvaluateQuery<TEntity>(this.DbSet.AsQueryable(), specification);
        return await query.ToListAsync(cancellationToken);
    
    }

    /// <inheritdoc />
    public async Task<TEntity?> GetFirstAsync(ISpecification<TEntity> specification, CancellationToken cancellationToken)
    {
        var query = SpecificationExecutor.EvaluateQuery<TEntity>(this.DbSet.AsQueryable(), specification);
        return await query.FirstOrDefaultAsync(cancellationToken: cancellationToken);
    }
}

These methods are then naturally exposed via IUnitOfWorkV2:

var repo = _uow.GetQueryRepository<User, Guid>();
var users = await repo.ListAsync(specification);

Example specifications

List: active users with paging and includes

public class ActiveUsersWithAddressSpecification : Specification<User>
{
    public ActiveUsersWithAddressSpecification(int pageIndex, int pageSize)
    {
        Criteria = u => u.IsActive;

        AddInclude(u => u.Address);

        ApplyOrderBy(q => q.OrderBy(u => u.LastName).ThenBy(u => u.FirstName));

        ApplyPaging(pageIndex, pageSize);
    }
}

Usage:

[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetActiveUsers(
    int pageIndex = 1,
    int pageSize = 20)
{
    var spec = new ActiveUsersWithAddressSpecification(pageIndex, pageSize);

    var users = await _uow
        .GetQueryRepository<User, Guid>()
        .ListAsync(spec);

    return Ok(users);
}

Single record: user by id with includes:

public class UserByIdWithAddressSpecification : Specification<User>
{
    public UserByIdWithAddressSpecification(Guid userId)
    {
        Criteria = u => u.Id == userId;

        AddInclude(u => u.Address);

        TrackChanges(); // if you want tracked entity for updates
    }
}

Usage:

[HttpGet("{id:guid}")]
public async Task<ActionResult<User>> GetById(Guid id)
{
    var spec = new UserByIdWithAddressSpecification(id);

    var user = await _uow
        .GetQueryRepository<User, Guid>()
        .FirstOrDefaultAsync(spec);

    if (user is null)
        return NotFound();

    return Ok(user);
}

Generic “by id” specification

public class EntityByIdSpecification<T, TKey> : Specification<T>
    where T : class
{
    public EntityByIdSpecification(TKey id)
    {
        var parameter = Expression.Parameter(typeof(T), "e");
        var property = Expression.Property(parameter, "Id");
        var constant = Expression.Constant(id);
        var equal = Expression.Equal(property, constant);

        Criteria = Expression.Lambda<Func<T, bool>>(equal, parameter);
    }
}

Usage:

var spec = new EntityByIdSpecification<User, Guid>(id);
var user = await _uow
    .GetQueryRepository<User, Guid>()
    .FirstOrDefaultAsync(spec);

Putting it all together

With Prakrishta.Data, your data access story becomes:

  • Unit of Work for transaction boundaries and repository orchestration.
  • Query and Persistence Repositories for clean separation of read and write concerns.
  • Specification Pattern for expressive, reusable query logic that keeps repositories small and stable.
  • DI integration for clean, testable application code.

The code base is licensed under open source, please feel free to use it in your project or edit as per your need.

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
1.2.3 106 1/24/2026
1.2.2 95 1/20/2026
1.2.1 101 1/18/2026
1.2.0 103 1/8/2026
1.0.0 941 2/7/2019