CVAMF.Repository
1.0.0
There is a newer version of this package available.
See the version list below for details.
See the version list below for details.
dotnet add package CVAMF.Repository --version 1.0.0
NuGet\Install-Package CVAMF.Repository -Version 1.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="CVAMF.Repository" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CVAMF.Repository" Version="1.0.0" />
<PackageReference Include="CVAMF.Repository" />
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 CVAMF.Repository --version 1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: CVAMF.Repository, 1.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 CVAMF.Repository@1.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=CVAMF.Repository&version=1.0.0
#tool nuget:?package=CVAMF.Repository&version=1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
CVAMF.Repository
Generic Repository Pattern implementation for Entity Framework Core with support for filters, pagination, and multiple primary key types.
Features
- ✅ Generic Repository Pattern for EF Core
- ✅ Support for Guid and Int primary keys
- ✅ Filtering with Expression Functions
- ✅ Optional pagination
- ✅ Full CRUD operations
- ✅ Async/await support
- ✅ Easy dependency injection integration
- ✅ .NET 10 compatible
Installation
dotnet add package CVAMF.Repository
Or via NuGet Package Manager:
Install-Package CVAMF.Repository
Quick Start
1. Create your entities
For Guid primary key (recommended):
using CVAMF.Repository.Entities;
public class Product : EntityBase
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
public bool IsActive { get; set; }
public string Category { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
}
For Int primary key:
using CVAMF.Repository.Entities;
public class Category : EntityBaseInt
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsActive { get; set; }
}
Custom entity implementing IEntity<T>:
using CVAMF.Repository.Entities;
public class Order : IEntity<Guid>
{
public Guid Id { get; set; }
public string OrderNumber { get; set; } = string.Empty;
public decimal TotalAmount { get; set; }
public DateTime OrderDate { get; set; }
public string Status { get; set; } = string.Empty;
}
2. Configure your DbContext
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Your entity configurations here
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Price).HasPrecision(18, 2);
});
}
}
3. Register services in Program.cs
using CVAMF.Repository.Extensions;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add repositories - This registers IRepository<,> for all entities!
builder.Services.AddRepositories();
// Add your services
builder.Services.AddScoped<ProductService>();
builder.Services.AddScoped<CategoryService>();
var app = builder.Build();
app.Run();
4. Use in your services
using CVAMF.Repository.Interfaces;
using CVAMF.Repository.Models;
public class ProductService
{
private readonly IRepository<Product, Guid> _productRepository;
private readonly ILogger<ProductService> _logger;
public ProductService(
IRepository<Product, Guid> productRepository,
ILogger<ProductService> logger)
{
_productRepository = productRepository;
_logger = logger;
}
// CRUD operations examples below...
}
Complete Usage Examples
📖 Read Operations
Get by ID
public async Task<Product?> GetProductById(Guid productId)
{
var product = await _productRepository.GetByIdAsync(productId);
if (product == null)
{
_logger.LogWarning("Product {ProductId} not found", productId);
return null;
}
return product;
}
Get All
public async Task<IEnumerable<Product>> GetAllProducts()
{
return await _productRepository.GetAllAsync();
}
Simple Filter
public async Task<IEnumerable<Product>> GetActiveProducts()
{
return await _productRepository.GetAsync(
filter: p => p.IsActive);
}
Filter with Ordering
public async Task<IEnumerable<Product>> GetProductsByCategory(string category)
{
return await _productRepository.GetAsync(
filter: p => p.Category == category && p.IsActive,
orderBy: q => q.OrderBy(p => p.Name));
}
Complex Filters
public async Task<IEnumerable<Product>> GetProductsInPriceRange(
decimal minPrice,
decimal maxPrice,
string? searchTerm = null)
{
return await _productRepository.GetAsync(
filter: p => p.IsActive
&& p.Price >= minPrice
&& p.Price <= maxPrice
&& (searchTerm == null || p.Name.Contains(searchTerm) || p.Description.Contains(searchTerm)),
orderBy: q => q.OrderBy(p => p.Price).ThenBy(p => p.Name));
}
Multiple Sorting
public async Task<IEnumerable<Product>> GetProductsSorted()
{
return await _productRepository.GetAsync(
filter: p => p.Stock > 0,
orderBy: q => q.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price)
.ThenBy(p => p.Name));
}
📄 Pagination Examples
Basic Pagination
public async Task<PagedResult<Product>> GetProductsPaged(int pageNumber, int pageSize)
{
return await _productRepository.GetPagedAsync(
pageNumber: pageNumber,
pageSize: pageSize,
filter: p => p.IsActive,
orderBy: q => q.OrderBy(p => p.Name));
}
Advanced Pagination with Search
public async Task<PagedResult<Product>> SearchProducts(
string searchTerm,
int page = 1,
int pageSize = 10,
string sortBy = "name",
bool descending = false)
{
var pagedResult = await _productRepository.GetPagedAsync(
pageNumber: page,
pageSize: pageSize,
filter: p => p.IsActive && (
p.Name.Contains(searchTerm) ||
p.Description.Contains(searchTerm) ||
p.Category.Contains(searchTerm)
),
orderBy: sortBy.ToLower() switch
{
"price" => descending
? q => q.OrderByDescending(p => p.Price)
: q => q.OrderBy(p => p.Price),
"date" => descending
? q => q.OrderByDescending(p => p.CreatedAt)
: q => q.OrderBy(p => p.CreatedAt),
_ => descending
? q => q.OrderByDescending(p => p.Name)
: q => q.OrderBy(p => p.Name)
});
_logger.LogInformation(
"Found {TotalCount} products. Showing page {Page} of {TotalPages}",
pagedResult.TotalCount,
pagedResult.PageNumber,
pagedResult.TotalPages);
return pagedResult;
}
Display Pagination Info
public async Task DisplayProductsPaged()
{
var pagedResult = await _productRepository.GetPagedAsync(
pageNumber: 1,
pageSize: 10,
filter: p => p.IsActive);
Console.WriteLine($"Page {pagedResult.PageNumber} of {pagedResult.TotalPages}");
Console.WriteLine($"Total items: {pagedResult.TotalCount}");
Console.WriteLine($"Items per page: {pagedResult.PageSize}");
Console.WriteLine($"Has previous: {pagedResult.HasPreviousPage}");
Console.WriteLine($"Has next: {pagedResult.HasNextPage}");
Console.WriteLine();
foreach (var product in pagedResult.Items)
{
Console.WriteLine($"- {product.Name} (${product.Price})");
}
}
✏️ Create Operations
Add Single Entity
public async Task<Product> CreateProduct(string name, decimal price, string category)
{
var product = new Product
{
Id = Guid.NewGuid(),
Name = name,
Price = price,
Category = category,
IsActive = true,
Stock = 0,
CreatedAt = DateTime.UtcNow
};
await _productRepository.AddAsync(product);
await _productRepository.SaveChangesAsync();
_logger.LogInformation("Product {ProductName} created with ID {ProductId}",
product.Name, product.Id);
return product;
}
Add Multiple Entities
public async Task<int> ImportProducts(List<ProductDto> productDtos)
{
var products = productDtos.Select(dto => new Product
{
Id = Guid.NewGuid(),
Name = dto.Name,
Price = dto.Price,
Category = dto.Category,
IsActive = true,
Stock = dto.Stock,
CreatedAt = DateTime.UtcNow
}).ToList();
await _productRepository.AddRangeAsync(products);
var savedCount = await _productRepository.SaveChangesAsync();
_logger.LogInformation("Imported {Count} products", savedCount);
return savedCount;
}
🔄 Update Operations
Update Single Entity
public async Task<bool> UpdateProductPrice(Guid productId, decimal newPrice)
{
var product = await _productRepository.GetByIdAsync(productId);
if (product == null)
{
_logger.LogWarning("Product {ProductId} not found", productId);
return false;
}
product.Price = newPrice;
await _productRepository.UpdateAsync(product);
await _productRepository.SaveChangesAsync();
_logger.LogInformation("Product {ProductId} price updated to {Price}",
productId, newPrice);
return true;
}
Update Multiple Entities
public async Task<int> ApplyDiscountToCategory(string category, decimal discountPercent)
{
var products = await _productRepository.GetAsync(
filter: p => p.Category == category && p.IsActive);
foreach (var product in products)
{
product.Price = product.Price * (1 - discountPercent / 100);
}
await _productRepository.UpdateRangeAsync(products);
var updatedCount = await _productRepository.SaveChangesAsync();
_logger.LogInformation(
"Applied {Discount}% discount to {Count} products in category {Category}",
discountPercent, updatedCount, category);
return updatedCount;
}
Conditional Update
public async Task<int> DeactivateLowStockProducts()
{
var lowStockProducts = await _productRepository.GetAsync(
filter: p => p.Stock < 5 && p.IsActive);
foreach (var product in lowStockProducts)
{
product.IsActive = false;
}
await _productRepository.UpdateRangeAsync(lowStockProducts);
return await _productRepository.SaveChangesAsync();
}
🗑️ Delete Operations
Delete by ID
public async Task<bool> DeleteProduct(Guid productId)
{
var product = await _productRepository.GetByIdAsync(productId);
if (product == null)
{
return false;
}
await _productRepository.DeleteAsync(productId);
await _productRepository.SaveChangesAsync();
_logger.LogInformation("Product {ProductId} deleted", productId);
return true;
}
Delete by Entity
public async Task DeleteInactiveProduct(string productName)
{
var product = await _productRepository.GetFirstOrDefaultAsync(
filter: p => p.Name == productName && !p.IsActive);
if (product != null)
{
await _productRepository.DeleteAsync(product);
await _productRepository.SaveChangesAsync();
}
}
Delete Multiple Entities
public async Task<int> CleanupOldInactiveProducts(int daysOld)
{
var cutoffDate = DateTime.UtcNow.AddDays(-daysOld);
var oldProducts = await _productRepository.GetAsync(
filter: p => !p.IsActive && p.CreatedAt < cutoffDate);
if (oldProducts.Any())
{
await _productRepository.DeleteRangeAsync(oldProducts);
var deletedCount = await _productRepository.SaveChangesAsync();
_logger.LogInformation("Deleted {Count} old inactive products", deletedCount);
return deletedCount;
}
return 0;
}
🔍 Query Helper Methods
Check Existence
public async Task<bool> ProductExists(string name)
{
return await _productRepository.AnyAsync(
filter: p => p.Name == name);
}
public async Task<bool> HasProductsInCategory(string category)
{
return await _productRepository.AnyAsync(
filter: p => p.Category == category && p.IsActive);
}
Count
public async Task<int> GetTotalProducts()
{
return await _productRepository.CountAsync();
}
public async Task<int> GetActiveProductCount()
{
return await _productRepository.CountAsync(
filter: p => p.IsActive);
}
public async Task<Dictionary<string, int>> GetProductCountByCategory()
{
var categories = await _productRepository.GetAsync(
filter: p => p.IsActive);
return categories
.GroupBy(p => p.Category)
.ToDictionary(g => g.Key, g => g.Count());
}
First or Default
public async Task<Product?> GetMostExpensiveProduct()
{
var products = await _productRepository.GetAsync(
filter: p => p.IsActive,
orderBy: q => q.OrderByDescending(p => p.Price));
return products.FirstOrDefault();
}
public async Task<Product?> FindProductByName(string name)
{
return await _productRepository.GetFirstOrDefaultAsync(
filter: p => p.Name == name && p.IsActive);
}
Using with Int Primary Keys
public class CategoryService
{
private readonly IRepository<Category, int> _categoryRepository;
public CategoryService(IRepository<Category, int> categoryRepository)
{
_categoryRepository = categoryRepository;
}
public async Task<Category> CreateCategory(string name)
{
var category = new Category
{
// No need to set Id for int (auto-increment)
Name = name,
IsActive = true
};
await _categoryRepository.AddAsync(category);
await _categoryRepository.SaveChangesAsync();
return category;
}
public async Task<Category?> GetCategoryById(int categoryId)
{
return await _categoryRepository.GetByIdAsync(categoryId);
}
}
Advanced Scenarios
Transaction Example
public async Task<bool> TransferStock(Guid fromProductId, Guid toProductId, int quantity)
{
var fromProduct = await _productRepository.GetByIdAsync(fromProductId);
var toProduct = await _productRepository.GetByIdAsync(toProductId);
if (fromProduct == null || toProduct == null || fromProduct.Stock < quantity)
{
return false;
}
try
{
fromProduct.Stock -= quantity;
toProduct.Stock += quantity;
await _productRepository.UpdateAsync(fromProduct);
await _productRepository.UpdateAsync(toProduct);
// Both updates are saved in a single transaction
await _productRepository.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error transferring stock");
return false;
}
}
Working with DTOs
public async Task<List<ProductListDto>> GetProductList(string category)
{
var products = await _productRepository.GetAsync(
filter: p => p.Category == category && p.IsActive,
orderBy: q => q.OrderBy(p => p.Name));
return products.Select(p => new ProductListDto
{
Id = p.Id,
Name = p.Name,
Price = p.Price,
InStock = p.Stock > 0
}).ToList();
}
Soft Delete Pattern
// Add DeletedAt property to your entity
public class Product : EntityBase
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public bool IsActive { get; set; }
public DateTime? DeletedAt { get; set; }
}
// Implement soft delete
public async Task SoftDeleteProduct(Guid productId)
{
var product = await _productRepository.GetByIdAsync(productId);
if (product != null)
{
product.DeletedAt = DateTime.UtcNow;
product.IsActive = false;
await _productRepository.UpdateAsync(product);
await _productRepository.SaveChangesAsync();
}
}
// Get only non-deleted products
public async Task<IEnumerable<Product>> GetActiveNonDeletedProducts()
{
return await _productRepository.GetAsync(
filter: p => p.IsActive && p.DeletedAt == null);
}
Best Practices
✅ DO
- Always call
SaveChangesAsync()after Add, Update, or Delete operations - Use pagination for large datasets
- Use specific filters to reduce database load
- Handle null returns from
GetByIdAsyncandGetFirstOrDefaultAsync - Use
CancellationTokenfor long-running operations - Inject
IRepository<TEntity, TKey>instead of concrete implementations
❌ DON'T
- Don't forget to call
SaveChangesAsync()- changes won't persist! - Don't load all data without filters unless necessary
- Don't use magic strings - use constants or enums
- Don't ignore exceptions - always log and handle errors
API Reference
Query Methods
| Method | Description | Returns |
|---|---|---|
GetByIdAsync(id) |
Get entity by primary key | TEntity? |
GetAllAsync() |
Get all entities | IEnumerable<TEntity> |
GetAsync(filter, orderBy) |
Get filtered/ordered entities | IEnumerable<TEntity> |
GetPagedAsync(page, size, filter, orderBy) |
Get paginated results | PagedResult<TEntity> |
GetFirstOrDefaultAsync(filter) |
Get first matching entity | TEntity? |
AnyAsync(filter) |
Check if any matches | bool |
CountAsync(filter) |
Count matching entities | int |
Command Methods
| Method | Description | Returns |
|---|---|---|
AddAsync(entity) |
Add new entity | TEntity |
AddRangeAsync(entities) |
Add multiple entities | Task |
UpdateAsync(entity) |
Update entity | Task |
UpdateRangeAsync(entities) |
Update multiple entities | Task |
DeleteAsync(entity) |
Delete entity | Task |
DeleteAsync(id) |
Delete by ID | Task |
DeleteRangeAsync(entities) |
Delete multiple entities | Task |
SaveChangesAsync() |
Persist changes to database | int (affected rows) |
Requirements
- .NET 10.0 or higher
- Entity Framework Core 10.0 or higher
License
MIT
Author
Carlos Filho
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
| 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. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0)
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.8.0 | 94 | 6/1/2026 |
| 1.7.2 | 105 | 5/22/2026 |
| 1.7.1 | 94 | 5/18/2026 |
| 1.7.0 | 93 | 5/16/2026 |
| 1.6.0 | 91 | 5/16/2026 |
| 1.5.0 | 95 | 5/15/2026 |
| 1.4.3 | 99 | 5/14/2026 |
| 1.4.2 | 90 | 5/14/2026 |
| 1.4.1 | 98 | 5/13/2026 |
| 1.4.0 | 89 | 5/13/2026 |
| 1.3.0 | 91 | 5/13/2026 |
| 1.2.1 | 100 | 5/13/2026 |
| 1.2.0 | 94 | 5/13/2026 |
| 1.1.2 | 111 | 5/13/2026 |
| 1.1.1 | 89 | 5/13/2026 |
| 1.1.0 | 85 | 5/13/2026 |
| 1.0.0 | 92 | 5/13/2026 |