EV.Infrastructure.EntityFramework 1.0.0

dotnet add package EV.Infrastructure.EntityFramework --version 1.0.0
                    
NuGet\Install-Package EV.Infrastructure.EntityFramework -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="EV.Infrastructure.EntityFramework" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EV.Infrastructure.EntityFramework" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EV.Infrastructure.EntityFramework" />
                    
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 EV.Infrastructure.EntityFramework --version 1.0.0
                    
#r "nuget: EV.Infrastructure.EntityFramework, 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 EV.Infrastructure.EntityFramework@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=EV.Infrastructure.EntityFramework&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EV.Infrastructure.EntityFramework&version=1.0.0
                    
Install as a Cake Tool

EV.Infrastructure.EntityFramework

Arquitectura Domain-Driven Design: Implementación completa de patrones Repository y Unit of Work con soporte multi-database y ASP.NET Core Identity.


✨ Características

  • 🏗️ Clean Architecture: Implementación completa de patrones Repository y Unit of Work
  • 🗃️ Multi-Database: Soporte para SQL Server, PostgreSQL, MySQL y más
  • 🔐 ASP.NET Core Identity: Integración completa con configuración flexible
  • 📄 Soft Delete: Soporte para eliminación lógica con auditoría completa
  • 🔍 Auditoría Automática: Seguimiento automático de creación, modificación y eliminación
  • 🎯 Entidades Base Genéricas: BaseEntity con propiedades de auditoría predefinidas
  • 🗂️ Entidades de Dominio Opcionales: DomainType/DomainValue para gestión de catálogos
  • ⚙️ Configuración Flexible: Contextos modulares según necesidades
  • 🚀 Operaciones Asíncronas: Todas las operaciones optimizadas para async/await
  • 📄 Paginación Incluida: Métodos de paginación listos para usar
  • 🔍 Búsquedas Avanzadas: Soporte para predicados y includes
  • 🎭 Dependency Injection: Configuración lista para inyección de dependencias

📦 Instalación

Package Manager Console
Install-Package EV.Infrastructure.EntityFramework
.NET CLI
dotnet add package EV.Infrastructure.EntityFramework

🚀 Inicio Rápido

1. Configuración en appsettings.json

{
  "DatabaseConfiguration": {
    "ConnectionString": "Data Source=.\\SQLExpress,1433;Initial Catalog=MyDatabase;Persist Security Info=True;User ID=sa;Password=P@ssw0rd;Encrypt=True;TrustServerCertificate=true;",
    "Provider": "SqlServer",
    "CommandTimeout": 30,
    "EnableSensitiveDataLogging": false,
    "EnableDetailedErrors": false,
    "EnableMigration": true
  },
  "IdentityConfiguration": {
    "RequiredDigit": true,
    "RequiredLength": 10,
    "RequireLowercase": true,
    "RequiredUniqueChars": 3,
    "RequireUppercase": true,
    "MaxFailedAttempts": 3,
    "LockoutTimeSpanInDays": 1,
    "RequireNonAlphanumeric": true,
    "AllowedForNewUsers": true,
    "IterationCount": 18000,
    "RequireConfirmedEmail": true
  }
}

2. Configuración en Program.cs

var builder = WebApplication.CreateBuilder(args);

// Obtener configuraciones
var databaseConfig = builder.Configuration.GetSection("DatabaseConfiguration").Get<DatabaseConfiguration>()!;

var identityConfig = builder.Configuration.GetSection("IdentityConfiguration").Get<IdentityConfiguration>()!;

// Opción A: Solo DataAccess (sin Identity)
builder.Services.AddDataAccessServices<MyAppDbContext>(databaseConfig);

// Opción B: DataAccess + Identity (RECOMENDADO)
builder.Services.AddDataAccessWithIdentityServices<IdentityUserApp, IdentityRole, MyAppDbContext>(databaseConfig, identityConfig);

var app = builder.Build();

3. Crear tu DbContext

// ApplicationDbContext.cs
using EV.Infrastructure.EntityFramework.Contexts;

// Opción 1: Solo tus entidades personalizadas
public class MyAppDbContext : BaseDbContext
{
    public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }
}

// Opción 2: Con entidades de dominio (DomainType/DomainValue)
public class MyAppDbContext : BaseDbContextWithDomainEntities
{
    public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
    // DomainTypes y DomainValues incluidos automáticamente
}

// Opción 3: Con Identity (sin dominio)
public class MyAppDbContext : BaseDbContextWithIdentityEntities
{
    public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
    // Entidades de Identity incluidas automáticamente
}

// Opción 4: Completo (Identity + Domain)
public class MyAppDbContext : BaseDbContextWithDomainAndIdentityEntities
{
    public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }
    // DomainTypes, DomainValues e Identity incluidos automáticamente
}

🗃️ Contextos Disponibles

Contexto Domain Entities Identity Uso Recomendado
BaseDbContext Testing, contextos minimalistas
BaseDbContextWithDomainEntities Apps sin autenticación
BaseDbContextWithIdentityEntities Microservicios de autenticación
BaseDbContextWithDomainAndIdentityEntities Aplicaciones completas

🌐 Soporte Multi-Database

SQL Server (por defecto)

{
  "DatabaseConfiguration": {
    "Provider": "SqlServer",
    "ConnectionString": "Data Source=.\\SQLExpress;Initial Catalog=MyDB;Integrated Security=true;"
  }
}

PostgreSQL

{
  "DatabaseConfiguration": {
    "Provider": "PostgreSql",
    "ConnectionString": "Host=localhost;Database=mydb;Username=postgres;Password=pass"
  }
}

MySQL

{
  "DatabaseConfiguration": {
    "Provider": "MySql",
    "ConnectionString": "Server=localhost;Database=mydb;Uid=root;Pwd=password;"
  }
}

🎯 Entidades Base

BaseEntity<TKey>

Todas las entidades heredan de BaseEntity<TKey>:

public abstract class BaseEntity<TKey>
{
    public TKey Id { get; set; }                    // Clave primaria
    public DateTime CreatedAt { get; set; }         // Fecha de creación
    public DateTime? UpdatedAt { get; set; }        // Fecha de actualización
    public string CreatedBy { get; set; }           // Usuario que creó
    public string? UpdatedBy { get; set; }          // Usuario que actualizó
    public string? DeletedBy { get; set; }          // Usuario que eliminó
    public DateTime? DeletedAt { get; set; }        // Fecha de eliminación
    public bool IsDeleted { get; set; }             // Marcador de eliminación

    // Métodos para gestión de eliminación lógica
    public virtual void SoftDelete(string deletedBy);
    public virtual void Restore(string restoredBy);
}

Entidad de Usuario Personalizada

// Tu entidad personalizada que extiende IdentityUser
public class IdentityUserApp : IdentityUser
{
    public string FullName { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string DocumentType { get; set; } = string.Empty;
    public string DocumentNumber { get; set; } = string.Empty;
    public string Token { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime? UpdatedAt { get; set; }
    public DateTime? DeletedAt { get; set; }
    public string CreatedBy { get; set; } = string.Empty;
    public string? UpdatedBy { get; set; }
    public string? DeletedBy { get; set; }
    public bool IsDeleted { get; set; }
}

Proveedor de Base de Datos

public enum DatabaseProvider
{
    SqlServer,
    PostgreSql,
    MySql,
    Oracle, (T.O.D.O.)
    SQLite (T.O.D.O.)
}

🗂️ Entidades de Dominio

Cuando usas contextos con entidades de dominio, obtienes:

// Para gestión de catálogos y configuraciones
public class DomainType : BaseEntity<int>
{
    public string Code { get; set; }              // Código único
    public string Name { get; set; }              // Nombre descriptivo
    public string? Description { get; set; }      // Descripción opcional
    public virtual ICollection<DomainValue> DomainValues { get; set; }
}

public class DomainValue : BaseEntity<int>
{
    public string Code { get; set; }              // Código del valor
    public string Name { get; set; }              // Nombre del valor
    public string? Description { get; set; }      // Descripción opcional
    public int DomainTypeId { get; set; }         // FK a DomainType
    public virtual DomainType DomainType { get; set; }
}

📋 API Reference

IRepository<T, TKey>

Método Descripción Parámetros
GetByIdAsync() Obtiene entidad por ID id, cancellationToken?
GetByIdAsync() Obtiene entidad con includes id, includes[]
GetByIdIncludingDeletedAsync() Obtiene incluyendo eliminados id, cancellationToken?
GetAllAsync() Obtiene todas las entidades cancellationToken?
GetAllAsync() Obtiene todas con includes includes[]
GetPagedAsync() Paginación básica pageNumber, pageSize, cancellationToken?
GetPagedAsync() Paginación con filtro pageNumber, pageSize, filter?, includes[]
FindAsync() Buscar con predicado predicate, cancellationToken?
FirstOrDefaultAsync() Primer elemento o nulo predicate, cancellationToken?
AddAsync() Agregar entidad entity, cancellationToken?
AddRangeAsync() Agregar múltiples entities, cancellationToken?
UpdateAsync() Actualizar entidad entity, cancellationToken?
UpdateRangeAsync() Actualizar múltiples entities, cancellationToken?
SoftDeleteAsync() Eliminación lógica id/entity, deletedBy, cancellationToken?
HardDeleteAsync() Eliminación física id/entity, cancellationToken?
RestoreAsync() Restaurar eliminado id, restoredBy, cancellationToken?
ExistsAsync() Verificar existencia id, cancellationToken?
CountAsync() Contar registros predicate?, cancellationToken?

IUnitOfWork

Método Descripción Parámetros
CreateRepository<T, TKey>() Crear repositorio Tipos genéricos
SaveChangesAsync() Guardar cambios cancellationToken?
BeginTransactionAsync() Iniciar transacción cancellationToken?
CommitTransactionAsync() Confirmar transacción cancellationToken?
RollbackTransactionAsync() Revertir transacción cancellationToken?

💻 Ejemplos de Uso

Crear una Entidad Personalizada

using EV.Infrastructure.EntityFramework.Entities;

public class Product : BaseEntity<int>
{
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
    public string SKU { get; set; } = string.Empty;
    
    // Propiedades de navegación
    public virtual Category Category { get; set; } = null!;
    public virtual ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();
}

Operaciones Básicas en Servicios

public class ProductService
{
    private readonly IUnitOfWork _unitOfWork;

    public ProductService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task<Product> CreateProductAsync(CreateProductDto dto, string userId)
    {
        var repository = _unitOfWork.CreateRepository<Product, int>();

        var product = new Product
        {
            Name = dto.Name,
            Description = dto.Description,
            Price = dto.Price,
            CategoryId = dto.CategoryId,
            SKU = dto.SKU,
            CreatedBy = userId
        };

        await repository.AddAsync(product);
        await _unitOfWork.SaveChangesAsync();

        return product;
    }

    public async Task<(IEnumerable<Product> Items, int TotalCount)> GetProductsPagedAsync(
        int pageNumber, int pageSize, string? searchTerm = null)
    {
        var repository = _unitOfWork.CreateRepository<Product, int>();

        if (string.IsNullOrEmpty(searchTerm))
        {
            return await repository.GetPagedAsync(pageNumber, pageSize);
        }

        return await repository.GetPagedAsync(
            pageNumber, 
            pageSize, 
            filter: p => p.Name.Contains(searchTerm) || p.Description.Contains(searchTerm),
            includes: p => p.Category);
    }

    public async Task<bool> SoftDeleteProductAsync(int productId, string deletedBy)
    {
        var repository = _unitOfWork.CreateRepository<Product, int>();
        
        var exists = await repository.ExistsAsync(productId);
        if (!exists) return false;

        await repository.SoftDeleteAsync(productId, deletedBy);
        await _unitOfWork.SaveChangesAsync();

        return true;
    }
}

Uso en Controladores

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;

    public ProductsController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    [HttpGet]
    public async Task<IActionResult> GetProducts(
        [FromQuery] int pageNumber = 1,
        [FromQuery] int pageSize = 10,
        [FromQuery] string? search = null)
    {
        var repository = _unitOfWork.CreateRepository<Product, int>();
        
        var (items, totalCount) = string.IsNullOrEmpty(search)
            ? await repository.GetPagedAsync(pageNumber, pageSize)
            : await repository.GetPagedAsync(
                pageNumber, 
                pageSize, 
                filter: p => p.Name.Contains(search),
                includes: p => p.Category);

        return Ok(new { items, totalCount, pageNumber, pageSize });
    }

    [HttpPost]
    public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
    {
        var repository = _unitOfWork.CreateRepository<Product, int>();

        var product = new Product
        {
            Name = dto.Name,
            Description = dto.Description,
            Price = dto.Price,
            CategoryId = dto.CategoryId,
            SKU = dto.SKU,
            CreatedBy = User.Identity?.Name ?? "System"
        };

        await repository.AddAsync(product);
        await _unitOfWork.SaveChangesAsync();

        return CreatedAtAction(nameof(GetProductById), new { id = product.Id }, product);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
        var repository = _unitOfWork.CreateRepository<Product, int>();
        
        var exists = await repository.ExistsAsync(id);
        if (!exists)
            return NotFound();

        await repository.SoftDeleteAsync(id, User.Identity?.Name ?? "System");
        await _unitOfWork.SaveChangesAsync();

        return NoContent();
    }
}

🔐 ASP.NET Core Identity

Configuración con Identity

// Usar tu entidad personalizada de usuario
builder.Services.AddIdentityWithDataAccess<IdentityUserApp, IdentityRole, ApplicationDbContext>(
    databaseConfig, 
    identityConfig
);

Tu Usuario Personalizado

// La librería incluye IdentityUserApp con campos adicionales
public class IdentityUserApp : IdentityUser
{
    public string FullName { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string DocumentType { get; set; } = string.Empty;
    public string DocumentNumber { get; set; } = string.Empty;
    public string Token { get; set; } = string.Empty;
    // + Propiedades de auditoría estándar
}

🗂️ Gestión de Entidades de Dominio

public async Task SetupDomainDataAsync()
{
    var domainTypeRepo = _unitOfWork.CreateRepository<DomainType, int>();
    var domainValueRepo = _unitOfWork.CreateRepository<DomainValue, int>();
    
    // Crear tipo de dominio
    var productStatusType = new DomainType
    {
        Code = "PRODUCT_STATUS",
        Name = "Estados de Producto",
        Description = "Estados disponibles para productos",
        CreatedBy = "System"
    };
    
    await domainTypeRepo.AddAsync(productStatusType);
    await _unitOfWork.SaveChangesAsync();
    
    // Crear valores de dominio
    var statusValues = new[]
    {
        new DomainValue { Code = "ACTIVE", Name = "Activo", DomainTypeId = productStatusType.Id, CreatedBy = "System" },
        new DomainValue { Code = "INACTIVE", Name = "Inactivo", DomainTypeId = productStatusType.Id, CreatedBy = "System" }
    };
    
    await domainValueRepo.AddRangeAsync(statusValues);
    await _unitOfWork.SaveChangesAsync();
}

🔄 Transacciones

public async Task TransferProductsAsync(int fromCategoryId, int toCategoryId, string userId)
{
    await _unitOfWork.BeginTransactionAsync();
    
    try
    {
        var productRepo = _unitOfWork.CreateRepository<Product, int>();
        
        var products = await productRepo.FindAsync(p => p.CategoryId == fromCategoryId);
        
        foreach (var product in products)
        {
            product.CategoryId = toCategoryId;
            product.UpdatedBy = userId;
            await productRepo.UpdateAsync(product);
        }
        
        await _unitOfWork.SaveChangesAsync();
        await _unitOfWork.CommitTransactionAsync();
    }
    catch
    {
        await _unitOfWork.RollbackTransactionAsync();
        throw;
    }
}

📊 Esquemas de Base de Datos

La librería organiza las tablas en esquemas lógicos:

  • auth: Todas las tablas de Identity (Users, Roles, UserRoles, etc.)
  • core: Entidades de dominio (DomainTypes, DomainValues)
  • dbo: Tus entidades personalizadas (por defecto)

⚙️ Configuración Avanzada

Configuración Personalizada de Entidades

public class ApplicationDbContext : BaseDbContextWithDomainAndIdentity
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
    
    protected override void ApplyConfigurations(ModelBuilder modelBuilder)
    {
        // Tus configuraciones específicas
        modelBuilder.ApplyConfiguration(new ProductConfiguration());
        
        base.ApplyConfigurations(modelBuilder);
    }
}

Deshabilitar Query Filters (para ver eliminados)

public async Task<IEnumerable<Product>> GetDeletedProductsAsync()
{
    var repository = _unitOfWork.CreateRepository<Product, int>();
    
    // Temporalmente deshabilitar filtros
    _unitOfWork.DisableGlobalQueryFilters();
    
    try
    {
        return await repository.FindAsync(p => p.IsDeleted);
    }
    finally
    {
        _unitOfWork.EnableGlobalQueryFilters();
    }
}

🛠 Troubleshooting

Problemas Comunes

  • Migraciones: Ejecuta Add-Migration y Update-Database después de cambios
  • Contexto: Verifica que heredas del contexto correcto según tus necesidades
  • Registro de servicios: Confirma que AddDataAccessServices<T>() esté registrado
  • Auditoría: Siempre establece CreatedBy y UpdatedBy en operaciones
  • Provider: Asegúrate de que el DatabaseProvider en configuración sea correcto

Validación de Configuración

// Validar configuración en startup
public static void ValidateConfiguration(IServiceProvider services)
{
    using var scope = services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
    
    if (context.Database.CanConnect())
        Console.WriteLine("✅ Conexión a base de datos exitosa");
    else
        throw new InvalidOperationException("❌ No se puede conectar a la base de datos");
}

📋 Requisitos

  • .NET 8.0 o superior
  • Microsoft.EntityFrameworkCore 8.0+
  • Microsoft.AspNetCore.Identity.EntityFrameworkCore 8.0+ (si usas Identity)
  • Microsoft.EntityFrameworkCore.SqlServer 8.0+ (para SQL Server)
  • Npgsql.EntityFrameworkCore.PostgreSQL 8.0+ (para PostgreSQL)
  • Pomelo.EntityFrameworkCore.MySql 8.0+ (para MySQL)

📄 License

Copyright © 2025 EVI. Todos los derechos reservados.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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
1.0.0 182 8/24/2025

🎉 Lanzamiento Inicial 1.0.0

✨ Características:
• Soporte multi-base de datos (SQL Server, PostgreSQL, MySQL)
• Implementación completa de patrones Repository y Unit of Work
• Integración ASP.NET Core Identity con entidad de usuario personalizada
• Eliminación lógica con funcionalidad de auditoría completa
• Entidades de dominio (DomainType/DomainValue) para gestión de catálogos
• Jerarquía de contextos flexible (Base, Dominio, Identity, Completo)
• Operaciones asíncronas comprehensivas
• Paginación integrada y consultas avanzadas
• Soporte de transacciones
• Configurable a través de appsettings.json

🏗️ Arquitectura:
• Cumplimiento Clean Architecture
• Patrones Domain-Driven Design
• Listo para Inyección de Dependencias
• Optimizado para .NET 8+

📖 Documentación completa con ejemplos y mejores prácticas