![]() |
VOOZH | about |
dotnet add package HybridRepoNet --version 5.0.0
NuGet\Install-Package HybridRepoNet -Version 5.0.0
<PackageReference Include="HybridRepoNet" Version="5.0.0" />
<PackageVersion Include="HybridRepoNet" Version="5.0.0" />Directory.Packages.props
<PackageReference Include="HybridRepoNet" />Project file
paket add HybridRepoNet --version 5.0.0
#r "nuget: HybridRepoNet, 5.0.0"
#:package HybridRepoNet@5.0.0
#addin nuget:?package=HybridRepoNet&version=5.0.0Install as a Cake Addin
#tool nuget:?package=HybridRepoNet&version=5.0.0Install as a Cake Tool
A generic repository with a Unit of Work pattern and domain event handling for PostgreSQL and Sql Server using .NET.
HybridRepoNet is a robust and extensible repository implementation for .NET applications using PostgreSQL and Sql Server. It simplifies Create, Read, Update, and Delete (CRUD) operations while maintaining a clean architecture through the Unit of Work (UoW) pattern and Domain Events.
With this package, you can:
This approach enhances maintainability and testability, following best practices in DDD (Domain-Driven Design).
You can install the package via NuGet Package Manager or the CLI:
Using NuGet Package Manager: <pre> Install-Package HybridRepoNet </pre>
🛠️ Configuration
{
"ConnectionStrings": {
"PostgresConnection": "Host=localhost;Database=yourDB;Username=postgres;Password=yourpassword;",
"SqlConnection": "Server=(localdb)\\mssqllocaldb;Database=Cars;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Configuring the DbContext:
using Microsoft.EntityFrameworkCore;
namespace YourNamespace
{
public class ProductPostgreSqlContext : DbContext
{
public ProductPgDbContext(DbContextOptions<ProductPgDbContext> options) : base(options) { }
// Add DbSets for your entities
public DbSet<Product> Products { get; set; }
}
}
namespace YourNamespace
{
public class CarSqlServerContext : DbContext
{
public CarSqlServerContext(DbContextOptions<CarSqlServerContext> options) : base(options) { }
// Add DbSets for your entities
public DbSet<Car> Cars { get; set; }
}
}
In your Program.cs:
using Microsoft.EntityFrameworkCore;
using YourNamespace;
var builder = WebApplication.CreateBuilder(args);
// Registering the repository and configuring the DbContext
builder.Services.AddHybridRepoNet<ProductPostgreSqlContext>(builder.Configuration, DbType.PostgreSQL);
builder.Services.AddHybridRepoNet<CarSqlServerContext>(builder.Configuration, DbType.SQLServer);
builder.Services.AddHostedService<HybridRepoHealthCheckService<ProductDbContext>>(); //Optional if you want use Polly for retry policy
services.AddMediatR(cfg => {
//Register MediatR handlers
cfg.RegisterServicesFromAssemblies(Assembly.GetExecutingAssembly());
});
var app = builder.Build();
🎯 Usage
Creating an Entity
Define an entity in your project, this entity inherit BaseEntity that is a base class wich contains all domain events implementantion:
public class Product : BaseEntity
{
public string? Name { get; set; }
public decimal Price { get; set; }
public bool Active { get; set; }
public string? ImageUri { get; set; }
}
Using the Repository
Example of usage:
public class ProductService : IProductService
{
private readonly IUnitOfWork<ProductContext> _unitOfWork;
private readonly IProductRepository _productRepository;
private readonly IMapper _mapper;
public ProductService(IUnitOfWork<ProductContext> unitOfWork,
IMapper mapper,
IProductRepository productRepository)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_productRepository = productRepository;
}
public async Task<IEnumerable<ProductDto>> GetAll()
{
return await _unitOfWork.Repository<Product>()
.Entities.ProjectTo<ProductDto>(_mapper.ConfigurationProvider).ToListAsync();
}
public async Task<ProductDto> Get(Guid id)
{
var product = _unitOfWork.Repository<Product>().FindOne(x => x.Id == id);
return _mapper.Map<ProductDto>(product);
}
public async Task<ProductDto> GetByName(string name)
{
return await _productRepository.GetByName(name);
}
public async Task Add(ProductCreateDto productDto)
{
var product = _mapper.Map<Product>(productDto);
await _unitOfWork.Repository<Product>().AddAsync(product);
// send a event
product.AddDomainEvent(new ProductCreatedEvent(product));
var resul = await _unitOfWork.Commit();
}
public async Task Delete(Guid id)
{
var product = _unitOfWork.Repository<Product>().FindOne(x => x.Id == id && !x.IsDeleted);
if (product is null)
throw new Exception("Product not found or deleted");
product.Active = false;
//Using Soft Delete update to delete instead of hard delete
_unitOfWork.Repository<Product>().SoftDeleteAsync(product);
product.AddDomainEvent(new ProductDeletedEvent(product));
await _unitOfWork.Commit();
}
public async Task Update(ProductUpdateDto productDto)
{
var product = _unitOfWork.Repository<Product>().FindOne(x => x.Id == productDto.Id);
if (product is null)
throw new Exception("Product not found");
product.Name = productDto.Name;
product.Price = productDto.Price;
product.Active = productDto.Active;
product.ImageUri = productDto.ImageUri;
_unitOfWork.Repository<Product>().UpdateAsync(product);
product.AddDomainEvent(new ProductUpdatedEvent(product));
await _unitOfWork.Commit();
}
}
Assuming that you have MediatR installed in your project, you can create your Handler for a created Product. Here a example
public class ProductCreatedEvent : BaseEvent
{
public Product Product { get;}
public ProductCreatedEvent(Product product)
{
Product = product;
}
}
public class ProductCreatedEventHandler : INotificationHandler<ProductCreatedEvent>
{
public async Task Handle(ProductCreatedEvent notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Product {notification.Product.Name} created at {DateTime.Now}");
await Task.CompletedTask;
}
}
The default
Deleteoperation in Entity Framework does not trigger domain events because deleted entities are not tracked in theChangeTrackerafter removal.To ensure that domain events are properly dispatched, you must use the Soft Delete approach provided in this repository.
When using
SoftDeleteAsync, the entity remains in theChangeTrackerasModified, allowing event dispatching via theSaveChangesAsync()method.If you use
DeleteAsync, domain events will not be triggered! 🚨
await _repository.DeleteAsync(product);
_unitOfWork.Repository<Product>().DeleteAsync(product); // No event will be fired!
await _repository.SoftDeleteAsync(product);
_unitOfWork.Repository<Product>().SoftDeleteAsync(product); // Events will be fired!
Performance:
Efficient use of database connections.
Generic:
Can be used with any entity class that has an identifier.
🧩 Requirements
🗂️ Package Structure
IRepository<TEntity>)This interface provides an abstraction for a generic repository pattern, allowing operations on any entity type. Below is a description of each available method:
IQueryable<TEntity> Entities
Gets the entities of the repository. Can be used with AutoMapper's ProjectTo for projections.
IQueryable<TEntity> GetAll(FindOptions? findOptions = null)
Retrieves all entities with optional find options.
IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null)
Retrieves all entities that match the specified predicate with optional find options.
IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null)
Finds all entities that match the specified predicate with optional find options.
TEntity FindOne(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null)
Finds a single entity that matches the specified predicate with optional find options.
Task<IEnumerable<TEntity>> GetAllAsync(int pageNumber, int pageSize)
Retrieves a paginated list of entities asynchronously.
Task<IEnumerable<TEntity>> GetAllAsync(int pageNumber, int pageSize, params Expression<Func<TEntity, object>>[] includes)
Retrieves a paginated list of entities asynchronously, with optional includes.
Task<IEnumerable<TEntity>> GetAllAsync(params Expression<Func<TEntity, object>>[] includes)
Retrieves a list of entities asynchronously, with optional includes.
Task<IEnumerable<TEntity>> GetAllAsync()
Retrieves all entities asynchronously.
Task<TEntity> FindAsync(Expression<Func<TEntity, bool>> predicate)
Finds a single entity asynchronously that matches the specified predicate.
Task AddAsync(TEntity entity)
Adds a single entity to the repository asynchronously.
Task AddAsync(IEnumerable<TEntity> entities)
Adds multiple entities to the repository asynchronously.
void UpdateAsync(TEntity entity)void DeleteAsync(TEntity entity)
Deletes a single entity from the repository asynchronously.
void DeleteAsync(Expression<Func<TEntity, bool>> predicate)
Deletes entities that match the specified predicate asynchronously.
Task SoftDeleteAsync(Expression<Func<TEntity, bool>> predicate)void SoftDeleteAsync(TEntity entity)Soft Delete is a technique that marks an entity as deleted instead of physically removing it from the database.
This allows data recovery and audit tracking.
This repository abstraction helps simplify database operations by providing a structured way to interact with entity data.
bool Any(Expression<Func<TEntity, bool>> predicate)
Checks if any entities match the specified predicate.
int Count(Expression<Func<TEntity, bool>> predicate)
Counts the number of entities that match the specified predicate.
🤝 Contribution
Contributions are welcome!
⭐ Give it a Star!
If you found this package useful, don't forget to give it a ⭐ on GitHub!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.0 | 99 | 1/30/2026 |
| 4.0.0 | 245 | 4/16/2025 |
| 3.1.7 | 624 | 3/13/2025 |
| 3.1.6 | 176 | 3/1/2025 |
| 3.1.5 | 192 | 2/22/2025 |
| 3.1.4 | 144 | 2/22/2025 |
| 3.1.3 | 141 | 2/22/2025 |
| 3.1.2 | 143 | 2/22/2025 |
| 3.1.1 | 140 | 2/22/2025 |
| 3.1.0 | 147 | 2/22/2025 |
| 3.0.8 | 147 | 2/22/2025 |
| 3.0.7 | 143 | 2/20/2025 |
| 3.0.6 | 468 | 2/18/2025 |
| 3.0.5 | 205 | 2/15/2025 |
| 3.0.4 | 146 | 2/10/2025 |
| 3.0.3 | 478 | 2/3/2025 |
| 3.0.2 | 146 | 2/1/2025 |
| 3.0.1 | 154 | 2/1/2025 |
| 3.0.0 | 142 | 2/1/2025 |