GenericRepository.EFCore
3.0.0
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
<PackageReference Include="GenericRepository.EFCore" Version="3.0.0" />
<PackageVersion Include="GenericRepository.EFCore" Version="3.0.0" />
<PackageReference Include="GenericRepository.EFCore" />
paket add GenericRepository.EFCore --version 3.0.0
#r "nuget: GenericRepository.EFCore, 3.0.0"
#:package GenericRepository.EFCore@3.0.0
#addin nuget:?package=GenericRepository.EFCore&version=3.0.0
#tool nuget:?package=GenericRepository.EFCore&version=3.0.0
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. UpdateAsyncis tracking-state aware: attaches detached entities or merges into an already-tracked instance, whichever is correct.- LINQ querying via
AsQueryable(), plusGetAllAsync/FindAsyncoverloads with predicate andIncludesupport. - Built-in paging via
GetPagedAsync, returning aPagedList<T>with count, page count, and has-next/has-previous. - Soft delete and restore (
SoftDeleteAsync/RestoreAsync) forIAuditableentities — driven entirely byDeletedAt, with no separate flag to drift out of sync. Calling either out of state is a safe no-op. GetSoftDeletedAsyncis the only place soft-deleted rows show up; every other query excludes them automatically.GetAllAsync,GetPagedAsync, andGetSoftDeletedAsyncreturnAsNoTrackingresults — callUpdateAsyncto 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, oneSaveChangesAsync()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 | Versions 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. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.11)
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 |