GenericRepository.EFCore 3.0.1

dotnet add package GenericRepository.EFCore --version 3.0.1
                    
NuGet\Install-Package GenericRepository.EFCore -Version 3.0.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="GenericRepository.EFCore" Version="3.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GenericRepository.EFCore" Version="3.0.1" />
                    
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.1
                    
#r "nuget: GenericRepository.EFCore, 3.0.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 GenericRepository.EFCore@3.0.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=GenericRepository.EFCore&version=3.0.1
                    
Install as a Cake Addin
#tool nuget:?package=GenericRepository.EFCore&version=3.0.1
                    
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. AsQueryable() returns a tracked query, matching EF Core's own default.
  • Built-in paging via GetPagedAsync, returning a PagedList<T> with count, page count, and has-next/has-previous. Also available as an IQueryable<T> extension, so it works on a query you've already shaped with .AsNoTracking() or other custom LINQ, not just on the repository directly.
  • 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 tracked results, same as EF Core does by default — no extra behavior to remember when deciding whether you can update what comes back.
  • 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.

Read-only browsing with AsNoTracking

AsQueryable() returns a tracked query, same as EF Core itself. For pure display/browse scenarios where you never call UpdateAsync on the results, opt into no-tracking by chaining it yourself; GetPagedAsync is also available as an IQueryable<T> extension so the fluent chain continues naturally:

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

This is the same GetPagedAsync the repository uses internally — IRepository<TEntity>.GetPagedAsync(...) is just AsQueryable().GetPagedAsync(...) under the hood — so behavior is identical for a plain, unmodified query. For a plain filtered/eager-loaded list without paging, drop down to AsQueryable().AsNoTracking() followed by your own .Where(...)/.Include(...)/.ToListAsync().

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 103 9/9/2026
3.0.0 101 9/7/2026
2.0.9 101 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