Extensiones de inyección de dependencias para UnitOfWorkContextCore. Proporciona métodos de extensión para IServiceCollection que facilitan el registro de UnitOfWork, múltiples contextos con claves únicas, y Factory Pattern para resolución dinámica. Incluye AddUnitOfWork, AddUnitOfWorkFactory y GetRegisteredContexts.
$ dotnet add package UnitOfWorkContext.DependencyInjectionLibrería .NET para implementar el patrón Unit of Work y Repository con Entity Framework Core.
Abstrae las transacciones de base de datos y proporciona una interfaz limpia para trabajar con múltiples contextos y esquemas en proyectos modulares.
✅ Patrón Unit of Work - Gestión centralizada de transacciones ✅ Patrón Repository - Operaciones CRUD genéricas ✅ Múltiples Contextos - Soporte para múltiples DbContext en el mismo proyecto ✅ Múltiples Esquemas - Trabaja con diferentes esquemas de base de datos (account, catalog, payment, etc.) ✅ Factory Pattern - Resolución dinámica de contextos ✅ Paginación - Sistema integrado de paginación con IPaginate ✅ LINQ Support - Expresiones lambda para filtros, ordenamiento e includes ✅ Transacciones - Manejo automático de commit/rollback ✅ Inyección de Dependencias - Integración nativa con DI de .NET ✅ Type-Safe - Fuertemente tipado con genéricos
Install-Package UnitOfWorkContext.Core
Install-Package UnitOfWorkContext.DependencyInjection
dotnet add package UnitOfWorkContext.Core
dotnet add package UnitOfWorkContext.DependencyInjection
<PackageReference Include="UnitOfWorkContext.Core" Version="1.9.0" />
<PackageReference Include="UnitOfWorkContext.DependencyInjection" Version="1.9.0" />
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
using UnitOfWorkContextCore.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Registrar DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// Registrar UnitOfWork
builder.Services.AddUnitOfWork<AppDbContext>();
var app = builder.Build();
public class ProductService
{
private readonly IUnitOfWork<AppDbContext> _unitOfWork;
public ProductService(IUnitOfWork<AppDbContext> unitOfWork)
{
_unitOfWork = unitOfWork;
}
public Product CreateProduct(Product product)
{
_unitOfWork.OpenTransaction();
try
{
var repo = _unitOfWork.GetRepository<Product>();
repo.Insert(product);
_unitOfWork.Commit(); // SaveChanges + Commit
return product;
}
catch
{
_unitOfWork.Dispose(); // Rollback automático
throw;
}
}
public IPaginate<Product> GetProducts(int page = 1)
{
var repo = _unitOfWork.GetRepository<Product>();
return repo.Get(
predicate: p => p.IsActive,
orderBy: q => q.OrderBy(p => p.Name),
index: page - 1,
size: 20
);
}
}
¿Necesitas trabajar con múltiples esquemas en la misma base de datos? Usa el Factory Pattern.
MiBaseDatos
├── [account] → Usuarios, Roles
├── [catalog] → Productos, Categorías
└── [payment] → Facturas, Pagos
public class AccountContext : DbContext
{
public AccountContext(DbContextOptions<AccountContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("account"); // ← Esquema específico
base.OnModelCreating(modelBuilder);
}
}
public class CatalogContext : DbContext
{
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("catalog"); // ← Esquema específico
base.OnModelCreating(modelBuilder);
}
}
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// Registrar DbContext
builder.Services.AddDbContext<AccountContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDbContext<CatalogContext>(options =>
options.UseSqlServer(connectionString));
// Registrar UnitOfWork con CLAVES
builder.Services.AddUnitOfWork<AccountContext>("account");
builder.Services.AddUnitOfWork<CatalogContext>("catalog");
// Registrar la Factory
builder.Services.AddUnitOfWorkFactory();
public class MultiSchemaService
{
private readonly IUnitOfWorkFactory _factory;
public MultiSchemaService(IUnitOfWorkFactory factory)
{
_factory = factory;
}
public void ProcessBySchema(string schemaName)
{
// Resolver dinámicamente: "account", "catalog", etc.
var uow = _factory.GetUnitOfWork(schemaName);
uow.OpenTransaction();
try
{
// Trabajar con el esquema...
uow.Commit();
}
catch
{
uow.Dispose();
throw;
}
}
}
public class OrderService
{
private readonly IUnitOfWorkFactory _factory;
public OrderService(IUnitOfWorkFactory factory)
{
_factory = factory;
}
public void CreateOrder(int userId, int productId)
{
// Obtener UnitOfWork tipados
var accountUoW = _factory.GetUnitOfWork<AccountContext>();
var catalogUoW = _factory.GetUnitOfWork<CatalogContext>();
// Validar usuario (esquema account)
var userRepo = accountUoW.GetRepository<User>();
var user = userRepo.Find(u => u.Id == userId);
// Validar producto (esquema catalog)
var productRepo = catalogUoW.GetRepository<Product>();
var product = productRepo.Find(p => p.Id == productId);
// Procesar orden...
}
}
📚 Ver Guía Completa de Múltiples Esquemas → 🚀 Ver Inicio Rápido con Ejemplos →
var repo = _unitOfWork.GetRepository<Product>();
// CREATE
var product = new Product { Name = "Laptop", Price = 999.99m };
repo.Insert(product);
// READ
var product = repo.Find(p => p.Id == 1);
var products = repo.Get(
predicate: p => p.Price > 100,
orderBy: q => q.OrderBy(p => p.Name),
include: q => q.Include(p => p.Category)
);
// UPDATE
product.Price = 899.99m;
repo.Update(product);
// DELETE
repo.Remove(product);
// Commit cambios
_unitOfWork.Commit();
var repo = _unitOfWork.GetRepository<Product>();
// Insertar múltiples
var products = new List<Product>
{
new Product { Name = "Product 1" },
new Product { Name = "Product 2" }
};
repo.InsertRange(products);
// Actualizar múltiples
repo.UpdateRange(updatedProducts);
// Eliminar múltiples
repo.RemoveRange(productsToDelete);
_unitOfWork.Commit();
var repo = _unitOfWork.GetRepository<Product>();
var result = repo.Get(
predicate: p => p.IsActive,
orderBy: q => q.OrderByDescending(p => p.CreatedAt),
index: 0, // Página 0 (primera página)
size: 20 // 20 items por página
);
Console.WriteLine($"Total: {result.Count}");
Console.WriteLine($"Páginas: {result.Pages}");
Console.WriteLine($"Tiene anterior: {result.HasPrevious}");
Console.WriteLine($"Tiene siguiente: {result.HasNext}");
foreach (var product in result.Items)
{
Console.WriteLine(product.Name);
}
var repo = _unitOfWork.GetRepository<Product>();
// Eager Loading
var products = repo.Get(
include: q => q.Include(p => p.Category)
.ThenInclude(c => c.ParentCategory)
);
// Proyecciones (Select)
var productDtos = repo.Get(
selector: p => new ProductDto
{
Id = p.Id,
Name = p.Name,
CategoryName = p.Category.Name
}
);
var repo = _unitOfWork.GetRepository<Product>();
// Sin tracking (mejor rendimiento para consultas)
var products = repo.Get(enableTracking: false);
// Con tracking (necesario para updates)
var product = repo.Find(p => p.Id == 1, enableTracking: true);
product.Price = 999.99m;
repo.Update(product);
_unitOfWork.OpenTransaction();
try
{
var productRepo = _unitOfWork.GetRepository<Product>();
var categoryRepo = _unitOfWork.GetRepository<Category>();
// Múltiples operaciones
productRepo.Insert(newProduct);
categoryRepo.Update(category);
// Commit todo junto
_unitOfWork.Commit();
}
catch (Exception)
{
// Rollback automático
_unitOfWork.Dispose();
throw;
}
Soporte nativo para jQuery DataTables:
[HttpPost("datatable")]
public IActionResult GetDataTable([FromBody] DataTableRequest request)
{
var repo = _unitOfWork.GetRepository<Product>();
var result = repo.Get(
predicate: p => p.Name.Contains(request.Search),
orderBy: q => q.OrderBy(p => p.Name),
index: request.Start / request.Length,
size: request.Length
);
return Ok(result.ToDataTableResponse(request.Draw));
}
| Método | Descripción |
|---|---|
GetRepository<T>() | Obtiene un repositorio para la entidad T |
OpenTransaction() | Inicia una transacción de base de datos |
Commit() | Guarda cambios y confirma la transacción |
Dispose() | Libera recursos y hace rollback si es necesario |
Context | Acceso al DbContext subyacente |
| Método | Descripción |
|---|---|
Insert(T entity) | Inserta una entidad |
Update(T entity) | Actualiza una entidad |
Remove(T entity) | Elimina una entidad |
InsertRange(ICollection<T>) | Inserta múltiples entidades |
UpdateRange(ICollection<T>) | Actualiza múltiples entidades |
RemoveRange(ICollection<T>) | Elimina múltiples entidades |
| Método | Descripción |
|---|---|
Find(Expression<Func<T, bool>>) | Busca una entidad por predicado |
Get(...) | Obtiene colección paginada con filtros |
Parámetros de Get():
predicate - Filtro LINQ (Where)orderBy - Ordenamiento (OrderBy/ThenBy)include - Eager loading (Include/ThenInclude)selector - Proyección (Select)index - Índice de página (0-based)size - Tamaño de páginaenableTracking - Habilitar tracking de EF Core| Método | Descripción |
|---|---|
GetUnitOfWork(string key) | Obtiene UnitOfWork por clave |
GetUnitOfWork<TContext>() | Obtiene UnitOfWork tipado |
HasContext(string key) | Verifica si existe un contexto |
| Método | Descripción |
|---|---|
AddUnitOfWork<TContext>() | Registra UnitOfWork tradicional |
AddUnitOfWork<TContext>(string key) | Registra UnitOfWork con clave |
AddUnitOfWorkFactory() | Registra la factory de contextos |
GetRegisteredContexts() | Obtiene contextos registrados |
UnitOfWorkContextCore/
├── UnitOfWork.cs - Implementación del patrón
├── UnitOfWorkFactory.cs - Factory para múltiples contextos
├── Repository.cs - Repositorio de escritura
├── ReadRepository.cs - Repositorio de lectura
├── Interfaces/
│ ├── IUnitOfWork.cs - Interfaz principal
│ ├── IUnitOfWorkFactory.cs - Interfaz de factory
│ ├── IRepository.cs - Interfaz de repositorio
│ └── IReadRepository.cs - Interfaz de lectura
├── Paging/
│ ├── IPaginate.cs - Interfaz de paginación
│ ├── Paginate.cs - Implementación de paginación
│ └── PaginateExtensions.cs - Extensiones de paginación
└── Helpers/
├── PredicateBuilder.cs - Constructor de predicados LINQ
└── OrderingHelper.cs - Helper de ordenamiento
UnitOfWorkContextCore.DependencyInjection/
└── InjectUnitOfWorkExtension.cs - Extensiones para DI
public class CategoryService
{
private readonly IUnitOfWork<AppDbContext> _unitOfWork;
public CategoryService(IUnitOfWork<AppDbContext> unitOfWork)
{
_unitOfWork = unitOfWork;
}
public Category Create(string name)
{
_unitOfWork.OpenTransaction();
try
{
var repo = _unitOfWork.GetRepository<Category>();
var category = new Category { Name = name };
repo.Insert(category);
_unitOfWork.Commit();
return category;
}
catch
{
_unitOfWork.Dispose();
throw;
}
}
public List<Category> GetAll()
{
var repo = _unitOfWork.GetRepository<Category>();
return repo.Get(orderBy: q => q.OrderBy(c => c.Name))
.Items.ToList();
}
}
public class ProductService
{
private readonly IUnitOfWork<AppDbContext> _unitOfWork;
public ProductService(IUnitOfWork<AppDbContext> unitOfWork)
{
_unitOfWork = unitOfWork;
}
public Product CreateWithCategory(string productName, int categoryId)
{
_unitOfWork.OpenTransaction();
try
{
// Validar categoría existe
var categoryRepo = _unitOfWork.GetRepository<Category>();
var category = categoryRepo.Find(c => c.Id == categoryId);
if (category == null)
throw new Exception("Categoría no encontrada");
// Crear producto
var productRepo = _unitOfWork.GetRepository<Product>();
var product = new Product
{
Name = productName,
CategoryId = categoryId
};
productRepo.Insert(product);
_unitOfWork.Commit();
return product;
}
catch
{
_unitOfWork.Dispose();
throw;
}
}
public IPaginate<ProductDto> SearchProducts(string searchTerm, int page)
{
var repo = _unitOfWork.GetRepository<Product>();
return repo.Get(
selector: p => new ProductDto
{
Id = p.Id,
Name = p.Name,
Price = p.Price,
CategoryName = p.Category.Name
},
predicate: p => p.Name.Contains(searchTerm),
orderBy: q => q.OrderBy(p => p.Name),
include: q => q.Include(p => p.Category),
index: page - 1,
size: 20
);
}
}
IUnitOfWorkFactory para resolución dinámica de contextosAddUnitOfWork(string key) y AddUnitOfWorkFactory()HasContext()GetRegisteredContexts() para inspección y debuggingLas contribuciones son bienvenidas. Por favor:
git checkout -b feature/AmazingFeature)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)Este proyecto está bajo licencia MIT. Ver archivo LICENSE para más detalles.
Si encuentras algún problema o tienes sugerencias:
Basado en el patrón Unit of Work descrito por Martin Fowler y las mejores prácticas de arquitectura de software .NET.
Hecho con ❤️ para la comunidad .NET