GenericRepository.EFCore 3.0.0

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

GenericRepository.EFCore

A generic repository and Unit of Work implementation for EF Core. It gives you consistent async CRUD, filtering, paging, soft delete, and audit-field tracking across every entity, without writing the same boilerplate repository over and over.

Install

dotnet add package GenericRepository.EFCore

Key Features

  • Generic async CRUD (AddAsync, UpdateAsync, DeleteAsync) for any entity — no per-entity repository required.
  • UpdateAsync is tracking-state aware: attaches detached entities or merges into an already-tracked instance, whichever is correct.
  • LINQ querying via AsQueryable(), plus GetAllAsync/FindAsync overloads with predicate and Include support.
  • Built-in paging via GetPagedAsync, returning a PagedList<T> with count, page count, and has-next/has-previous.
  • Soft delete and restore (SoftDeleteAsync/RestoreAsync) for IAuditable entities — driven entirely by DeletedAt, with no separate flag to drift out of sync. Calling either out of state is a safe no-op.
  • GetSoftDeletedAsync is the only place soft-deleted rows show up; every other query excludes them automatically.
  • GetAllAsync, GetPagedAsync, and GetSoftDeletedAsync return AsNoTracking results — call UpdateAsync to persist any change to them.
  • Auditable fields (CreatedAt, UpdatedAt, DeletedAt) are stamped automatically on save.
  • Unit of Work pattern via IUnitOfWork.Of<TEntity>() — one context, one SaveChangesAsync() across entity types.
  • Transaction support (BeginTransactionAsync) for multi-step operations.
  • DatabaseExistsAsync() for a quick connectivity check.

How to Use

1. Register it

The package's UnitOfWork<TDataContext> already implements IUnitOfWork — point it at your DbContext and you're done:

services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
services.AddScoped<IUnitOfWork, UnitOfWork<AppDbContext>>();

If you'd rather inject a plain IUnitOfWork without the generic type showing up everywhere, wrap it once:

public class UnitOfWork(AppDbContext context) : UnitOfWork<AppDbContext>(context)
{
    public IRepository<User> Users => Of<User>();
}
services.AddScoped<IUnitOfWork, UnitOfWork>();

2. Basic CRUD

Everything goes through Of<TEntity>(), and nothing is persisted until you call SaveChangesAsync() — this is what stamps the auditable fields, so don't skip it in favor of the plain SaveChange() method (that one just forwards to EF Core without touching CreatedAt/UpdatedAt).

app.MapGet("/api/products", async (IUnitOfWork uow) =>
    Results.Ok(await uow.Of<Product>().GetAllAsync()));

app.MapGet("/api/products/{id:int}", async (int id, IUnitOfWork uow) =>
{
    var product = await uow.Of<Product>().GetByIdAsync(id);
    return product is null ? Results.NotFound() : Results.Ok(product);
});

app.MapPost("/api/products", async (IUnitOfWork uow, Product product) =>
{
    await uow.Of<Product>().AddAsync(product);
    await uow.SaveChangesAsync();
    return Results.Created($"/api/products/{product.Id}", product);
});

app.MapPut("/api/products/{id:int}", async (int id, Product updated, IUnitOfWork uow) =>
{
    await uow.Of<Product>().UpdateAsync(updated);
    await uow.SaveChangesAsync();
    return Results.NoContent();
});

app.MapDelete("/api/products/{id:int}", async (int id, IUnitOfWork uow) =>
{
    await uow.Of<Product>().DeleteAsync(id);
    await uow.SaveChangesAsync();
    return Results.NoContent();
});

3. Querying with filters and includes

// predicate + eager loading, in one call
var products = await uow.Of<Product>().GetAllAsync(
    p => p.CategoryId == categoryId,
    includes: [p => p.Category]);

// a single match
var product = await uow.Of<Product>().FindAsync(p => p.Sku == sku);

// or drop down to full LINQ when the above isn't enough
var expensiveProducts = uow.Of<Product>().AsQueryable()
    .Include(p => p.Category)
    .Where(p => p.Price > 100)
    .OrderBy(p => p.Name);

GetAllAsync, FindAsync, and AsQueryable all exclude soft-deleted rows for entities implementing IAuditable, unconditionally — there's no flag to opt out of that per call. If you need to see soft-deleted rows, use GetSoftDeletedAsync (below) instead.

4. Paging

var page = await uow.Of<Product>().GetPagedAsync(
    pageNumber: 1,
    pageSize: 20,
    predicate: p => p.IsActive,
    orderBy: q => q.OrderBy(p => p.Name));

OR

var page = await uow.Of<Product>().GetPagedAsync(
    pageNumber: 1,
    pageSize: 20,
    predicate: p => p.IsActive,
    orderBy: q => q.OrderBy(p => p.Name),
    includes: r => r.Supplier);


Results.Ok(new { page.Items, page.TotalItemCount, page.TotalPages, page.HasNextPage });

5. Soft delete and restore

await uow.Of<Product>().SoftDeleteAsync(product); // stamps DeletedAt
await uow.SaveChangesAsync();

// or by id, without fetching it first
var wasDeleted = await uow.Of<Product>().SoftDeleteAsync(productId);
await uow.SaveChangesAsync();

await uow.Of<Product>().RestoreAsync(product); // clears DeletedAt
await uow.SaveChangesAsync();

// look at what's in the trash
var deletedProducts = await uow.Of<Product>().GetSoftDeletedAsync();

Both methods return a bool: true if they actually changed something, false if the entity wasn't found, doesn't implement IAuditable, or was already in the target state (soft-deleting an already-deleted row, or restoring one that isn't deleted).

6. Transactions

await using var transaction = await uow.BeginTransactionAsync();

try
{
    await uow.Of<Order>().AddAsync(order);
    await uow.Of<Product>().UpdateAsync(product); // e.g. stock decremented earlier
    await uow.SaveChangesAsync();
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

7. Adding entity-specific methods

Of<TEntity>() always gives you back the plain generic repository. When an entity needs its own queries, write a small repository for it and register that alongside IUnitOfWork for everything else:

public interface IProductRepository : IRepository<Product>
{
    Task<IEnumerable<Product>> GetLowStockAsync(int threshold);
}

public class ProductRepository(AppDbContext context)
    : Repository<Product, AppDbContext>(context), IProductRepository
{
    public async Task<IEnumerable<Product>> GetLowStockAsync(int threshold)
        => await AsQueryable().Where(p => p.Stock < threshold).ToListAsync();
}
services.AddScoped<IProductRepository, ProductRepository>();
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
3.0.1 102 9/9/2026
3.0.0 100 9/7/2026
2.0.9 100 9/5/2026
2.0.8 391 11/13/2025
2.0.7 248 7/28/2025
2.0.6 199 7/27/2025
2.0.5 341 7/26/2025
2.0.4 190 6/7/2025
2.0.2 257 9/7/2024
2.0.1 236 9/7/2024
2.0.0 279 3/28/2024
1.0.6 242 3/26/2024
1.0.5 230 3/26/2024
1.0.2 281 3/19/2024
1.0.1 243 3/18/2024
1.0.0 267 3/18/2024