RepoStack 0.0.1-alpha-1

This is a prerelease version of RepoStack.
dotnet add package RepoStack --version 0.0.1-alpha-1
                    
NuGet\Install-Package RepoStack -Version 0.0.1-alpha-1
                    
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="RepoStack" Version="0.0.1-alpha-1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RepoStack" Version="0.0.1-alpha-1" />
                    
Directory.Packages.props
<PackageReference Include="RepoStack" />
                    
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 RepoStack --version 0.0.1-alpha-1
                    
#r "nuget: RepoStack, 0.0.1-alpha-1"
                    
#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 RepoStack@0.0.1-alpha-1
                    
#: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=RepoStack&version=0.0.1-alpha-1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=RepoStack&version=0.0.1-alpha-1&prerelease
                    
Install as a Cake Tool

RepoStack

Build and Test NuGet .NET 10 License

Reusable repository and query abstractions for .NET applications.

RepoStack keeps application code independent from a concrete data-access implementation. The core package contains provider, repository, query, ordering, and transaction contracts. Database-specific integrations live in separate packages; Entity Framework Core is currently provided by RepoStack.EntityFrameworkCore.

Contents

Features

  • Generic read providers through IProvider<TEntity>
  • Generic repositories through IRepository<TEntity>
  • Filtered, ordered, and paged queries with Query<TEntity>
  • Server-side projections with Query<TEntity, TResult>
  • ToArrayAsync, FirstOrDefaultAsync, SingleOrDefaultAsync, SingleAsync, CountAsync, and AnyAsync
  • Expression-based provider overloads without constructing Query<TEntity>
  • Single-item and batch add, update, and delete operations
  • Optional automatic transaction start for repository mutations
  • Cancellation support on asynchronous database operations
  • No-tracking reads by default in the EF Core provider

Installation

Install the core abstractions package:

dotnet add package RepoStack --version 0.0.1-alpha-1

Install the Entity Framework Core integration when using EF Core:

dotnet add package RepoStack.EntityFrameworkCore --version 0.0.1-alpha-1

Install the PureSpec integration when the application uses PureSpec specifications:

dotnet add package RepoStack.PureSpecExtension --version 0.0.1-alpha-1

Queries and projections

Query<TEntity> describes an entity query. Its optional predicate, orderings, offset, and limit are applied in that order. Query<TEntity, TResult> adds a LINQ expression for server-side projection:

var query = new Query<Book, BookListItem>(
	predicate: book => book.Genre == Genre.Fiction,
	selector: book => new BookListItem(book.Id, book.Title),
	orderings: [
		new QueryOrder<Book, string>(book => book.Title),
		new QueryOrder<Book, int>(book => book.Id)
	],
	offset: 20,
	limit: 20);

BookListItem[] page = await books.ToArrayAsync(query, cancellationToken);

Use descending: true for descending order. Multiple QueryOrder instances become a primary ordering followed by ThenBy / ThenByDescending orderings.

For common entity queries, IProvider<TEntity> also provides overloads that accept the predicate, an array of orderings, limit, and offset directly. This avoids creating a Query<TEntity>:

Book[] page = await books.ToArrayAsync(
	book => book.Genre == Genre.Fiction,
	[new QueryOrder<Book, string>(book => book.Title)],
	limit: 20,
	offset: 0,
	cancellationToken);

FirstOrDefaultAsync and SingleOrDefaultAsync accept the same parameters and also have expression-only overloads. SingleAsync, CountAsync, and AnyAsync provide expression-only overloads as well:

Book? book = await books.SingleOrDefaultAsync(
	book => book.Isbn == isbn, [], null, null, cancellationToken);
long count = await books.CountAsync(book => book.IsAvailable, cancellationToken);
bool exists = await books.AnyAsync(book => book.Isbn == isbn, cancellationToken);

Write operations

Use IRepository<TEntity> when the service needs mutations as well as reads:

public sealed class BookService(IRepository<Book> books)
{
	public async ValueTask AddAsync(Book book, CancellationToken cancellationToken)
	{
		await books.AddAsync(book, cancellationToken);
	}

	public async ValueTask DeleteArchivedAsync(CancellationToken cancellationToken)
	{
		await books.DeleteManyAsync(
			new Query<Book>(book => book.IsArchived),
			cancellationToken);
	}
}

AddAsync, AddManyAsync, UpdateAsync, UpdateManyAsync, and DeleteAsync save changes through the current DbContext. DeleteManyAsync uses EF Core's set-based ExecuteDeleteAsync and returns the number of deleted rows.

Transactions

By default, repository mutations use the current EF Core transaction when one exists. To automatically start a transaction before a mutation, enable the option during registration:

services.AddRepoStackEntityFrameworkCore(
	startTransactionsBeforeAnyModification: true);

When this option is enabled, commit or roll back through the injected ITransactionManager:

public sealed class ImportService(
	IRepository<Book> books,
	ITransactionManager transactions)
{
	public async ValueTask ImportAsync(
		Book[] importedBooks,
		CancellationToken cancellationToken)
	{
		await books.AddManyAsync(importedBooks, cancellationToken);
		await transactions.CommitTransactionIfExistsAsync(cancellationToken);
	}
}

The manager also exposes BeginTransactionIfNotExistsAsync and RollbackTransactionIsExistsAsync for explicit transaction control. The transaction is tied to the registered DbContext scope.

Quick start with Entity Framework Core

Register the application's DbContext and RepoStack in the service collection:

using Microsoft.EntityFrameworkCore;
using RepoStack.EntityFrameworkCore;

services
	.AddDbContext<DbContext, BooksDbContext>(options =>
		options.UseSqlServer(connectionString))
	.AddRepoStackEntityFrameworkCore();

AddRepoStackEntityFrameworkCore() registers:

Abstraction Default implementation Lifetime
IProvider<TEntity> BaseProvider<TEntity> Transient
IRepository<TEntity> BaseRepository<TEntity> Transient
ITransactionManager TransactionManager Scoped

Inject the abstraction you need into an application service or handler:

using RepoStack;

public sealed class BookService(IProvider<Book> books)
{
	public ValueTask<Book[]> GetAvailableBooksAsync(CancellationToken cancellationToken) =>
		books.ToArrayAsync(
			new Query<Book>(
				predicate: book => book.IsAvailable,
				orderings: [new QueryOrder<Book, string>(book => book.Title)],
				limit: 20),
			cancellationToken);
}

PureSpec integration

RepoStack.PureSpecExtension bridges PureSpec specifications and RepoStack queries. It adds ToQuery(...) extension methods for both ISpecification<TEntity> and IProjectedSpecification<TEntity, TResult>.

The extensions preserve the specification predicate and, for projected specifications, the selector. They can also add:

  • no ordering or a custom IQueryOrder<TEntity> sequence;
  • ascending or descending ordering from a key selector;
  • optional limit and offset paging.
using PureSpec;
using RepoStack;
using RepoStack.PureSpecExtension;

public sealed class BookService(IProvider<Book> books)
{
	public ValueTask<Book[]> FindAsync(
		ISpecification<Book> specification,
		CancellationToken cancellationToken) =>
		books.ToArrayAsync(
			specification.ToQuery(
				ordering: book => book.Title,
				limit: 20,
				offset: 0),
			cancellationToken);
}

For a projected PureSpec specification, the same API returns IQuery<TEntity, TResult> and keeps the projection server-side:

IProjectedSpecification<Book, BookListItem> specification = GetBookListSpecification();

BookListItem[] result = await books.ToArrayAsync(
	specification.ToQuery(book => book.Title, descending: false, limit: 20),
	cancellationToken);

Use the overload accepting IEnumerable<IQueryOrder<TEntity>> when multiple ordering criteria are needed. RepoStack.PureSpecExtension does not provide a database provider; it only adapts PureSpec specifications for use with RepoStack providers and repositories.

License

MIT License.

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.
  • net10.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on RepoStack:

Package Downloads
RepoStack.EntityFrameworkCore

Package Description

RepoStack.PureSpecExtension

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1-alpha-1 76 9/3/2026
0.0.1-alpha 79 8/30/2026

Add expression-based IProvider overloads for common entity queries.