Módulo 18: Entity Framework Core

Objetivos del módulo

  • Comprender qué es un ORM y qué problema resuelve
  • Configurar Entity Framework Core con SQLite
  • Crear modelos, DbContext y migraciones
  • Realizar operaciones CRUD completas
  • Aplicar relaciones entre entidades

1. ¿Qué es Entity Framework Core?

📘 Concepto: Entity Framework Core (EF Core) es un ORM (Object-Relational Mapper) para .NET. Permite trabajar con bases de datos usando clases de C# en lugar de escribir SQL directamente. Tus clases se convierten en tablas, y los objetos en filas.

Sin ORM (SQL directo) Con EF Core (C#)
INSERT INTO Alumnos VALUES (...) db.Alumnos.Add(alumno)
SELECT * FROM Alumnos WHERE Nota > 5 db.Alumnos.Where(a => a.Nota > 5)
UPDATE Alumnos SET Nota = 10 WHERE Id = 1 alumno.Nota = 10; db.SaveChanges()
DELETE FROM Alumnos WHERE Id = 1 db.Alumnos.Remove(alumno)

2. Crear el proyecto

# Crear proyecto de consola
dotnet new console -n TiendaEF
cd TiendaEF

# Añadir paquetes de EF Core + SQLite
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design

# Instalar herramienta de migraciones (una sola vez)
dotnet tool install --global dotnet-ef

Instalar paquetes NuGet desde Visual Studio Community

Si usas Visual Studio Community, también puedes instalar los paquetes desde la interfaz gráfica: Herramientas → Administrador de paquetes NuGet → Administrar paquetes NuGet para la solución…

Administrador de paquetes NuGet en Visual Studio Administrador de paquetes NuGet: busca e instala los paquetes de Entity Framework Core


3. Crear los modelos (entidades)

Los modelos son clases normales que representan las tablas de la base de datos:

// Models/Producto.cs
class Producto
{
    public int Id { get; set; }              // Clave primaria (convención: "Id")
    public string Nombre { get; set; } = "";
    public string Descripcion { get; set; } = "";
    public decimal Precio { get; set; }
    public int Stock { get; set; }
    public DateTime FechaCreacion { get; set; } = DateTime.Now;

    // Relación: un producto pertenece a una categoría
    public int CategoriaId { get; set; }          // Foreign key
    public Categoria Categoria { get; set; } = null!;  // Navegación
}
// Models/Categoria.cs
class Categoria
{
    public int Id { get; set; }
    public string Nombre { get; set; } = "";
    public string Descripcion { get; set; } = "";

    // Relación inversa: una categoría tiene muchos productos
    public List<Producto> Productos { get; set; } = new();
}

4. Crear el DbContext

📘 Concepto: El DbContext es la clase principal de EF Core. Representa la sesión con la base de datos y contiene DbSet<T> para cada tabla.

// Data/TiendaContext.cs
using Microsoft.EntityFrameworkCore;

class TiendaContext : DbContext
{
    // Cada DbSet<T> representa una tabla
    public DbSet<Producto> Productos { get; set; }
    public DbSet<Categoria> Categorias { get; set; }

    // Configurar la conexión a SQLite
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlite("Data Source=tienda.db");
    }

    // Configuraciones adicionales (opcional)
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Datos iniciales (seed)
        modelBuilder.Entity<Categoria>().HasData(
            new Categoria { Id = 1, Nombre = "Electrónica", Descripcion = "Dispositivos electrónicos" },
            new Categoria { Id = 2, Nombre = "Ropa", Descripcion = "Prendas de vestir" },
            new Categoria { Id = 3, Nombre = "Hogar", Descripcion = "Artículos para el hogar" }
        );
    }
}

5. Crear y aplicar migraciones

Las migraciones son la forma de crear y actualizar la estructura de la base de datos:

# Crear la primera migración
dotnet ef migrations add Inicial

# Aplicar la migración (crea la base de datos y las tablas)
dotnet ef database update

# Si cambias los modelos, crea una nueva migración
dotnet ef migrations add AgregarCampoDescripcion
dotnet ef database update

6. Operaciones CRUD

Create (Crear)

using var db = new TiendaContext();

// Crear un producto
var producto = new Producto
{
    Nombre = "Teclado mecánico",
    Descripcion = "Teclado Cherry MX Blue",
    Precio = 89.99m,
    Stock = 50,
    CategoriaId = 1  // Electrónica
};

db.Productos.Add(producto);
await db.SaveChangesAsync();  // Guarda en la base de datos

Console.WriteLine($"Producto creado con Id: {producto.Id}");

Read (Leer)

using var db = new TiendaContext();

// Obtener todos
List<Producto> todos = await db.Productos.ToListAsync();

// Obtener por Id
Producto? producto = await db.Productos.FindAsync(1);

// Filtrar con LINQ
var baratos = await db.Productos
    .Where(p => p.Precio < 50)
    .OrderBy(p => p.Nombre)
    .ToListAsync();

// Incluir datos relacionados (JOIN automático)
var conCategoria = await db.Productos
    .Include(p => p.Categoria)  // Carga la categoría asociada
    .Where(p => p.Stock > 0)
    .ToListAsync();

foreach (var p in conCategoria)
{
    Console.WriteLine($"{p.Nombre} ({p.Categoria.Nombre}): {p.Precio:C2}");
}

// Primer elemento / existe
var primero = await db.Productos.FirstOrDefaultAsync(p => p.Nombre.Contains("Teclado"));
bool existe = await db.Productos.AnyAsync(p => p.Precio > 100);
int total = await db.Productos.CountAsync();

Update (Actualizar)

using var db = new TiendaContext();

var producto = await db.Productos.FindAsync(1);
if (producto is not null)
{
    producto.Precio = 79.99m;
    producto.Stock += 20;

    await db.SaveChangesAsync();  // EF detecta los cambios automáticamente
    Console.WriteLine("Producto actualizado");
}

Delete (Eliminar)

using var db = new TiendaContext();

var producto = await db.Productos.FindAsync(1);
if (producto is not null)
{
    db.Productos.Remove(producto);
    await db.SaveChangesAsync();
    Console.WriteLine("Producto eliminado");
}

7. Relaciones

Uno a muchos (1:N)

Ya lo vimos: una Categoría tiene muchos Productos.

// Crear categoría con productos
var categoria = new Categoria
{
    Nombre = "Periféricos",
    Productos = new List<Producto>
    {
        new() { Nombre = "Ratón", Precio = 29.99m, Stock = 100 },
        new() { Nombre = "Auriculares", Precio = 59.99m, Stock = 75 }
    }
};

db.Categorias.Add(categoria);
await db.SaveChangesAsync();  // Crea la categoría Y los 2 productos

// Consultar con Include
var cats = await db.Categorias
    .Include(c => c.Productos)
    .ToListAsync();

foreach (var c in cats)
{
    Console.WriteLine($"\n{c.Nombre}:");
    foreach (var p in c.Productos)
        Console.WriteLine($"  - {p.Nombre}: {p.Precio:C2}");
}

Muchos a muchos (N:M)

class Pedido
{
    public int Id { get; set; }
    public DateTime Fecha { get; set; } = DateTime.Now;
    public string Cliente { get; set; } = "";

    // Relación N:M con Producto a través de DetallePedido
    public List<DetallePedido> Detalles { get; set; } = new();
}

class DetallePedido
{
    public int Id { get; set; }
    public int Cantidad { get; set; }
    public decimal PrecioUnitario { get; set; }

    public int PedidoId { get; set; }
    public Pedido Pedido { get; set; } = null!;

    public int ProductoId { get; set; }
    public Producto Producto { get; set; } = null!;

    public decimal Total => Cantidad * PrecioUnitario;
}

8. Consultas avanzadas con LINQ + EF Core

using var db = new TiendaContext();

// Agrupar productos por categoría con estadísticas
var resumen = await db.Productos
    .Include(p => p.Categoria)
    .GroupBy(p => p.Categoria.Nombre)
    .Select(g => new
    {
        Categoria = g.Key,
        TotalProductos = g.Count(),
        PrecioMedio = g.Average(p => p.Precio),
        StockTotal = g.Sum(p => p.Stock),
        ProductoMasCaro = g.Max(p => p.Precio)
    })
    .OrderByDescending(r => r.TotalProductos)
    .ToListAsync();

foreach (var r in resumen)
{
    Console.WriteLine($"{r.Categoria}: {r.TotalProductos} productos, " +
                      $"precio medio {r.PrecioMedio:C2}, stock total {r.StockTotal}");
}

// Búsqueda con paginación
int pagina = 1;
int tamano = 10;

var paginaProductos = await db.Productos
    .OrderBy(p => p.Nombre)
    .Skip((pagina - 1) * tamano)
    .Take(tamano)
    .ToListAsync();

9. Ejercicios

Ejercicio 1: Biblioteca con EF Core

Crea una base de datos para una biblioteca con: Libro (Id, Titulo, ISBN, Año), Autor (Id, Nombre, Pais), relación N:M entre libros y autores. Implementa CRUD completo + búsquedas (por título, por autor, por año).

Ejercicio 2: Gestión de notas

Crea un sistema con: Alumno (Id, Nombre, Email), Asignatura (Id, Nombre, Creditos), Nota (AlumnoId, AsignaturaId, Calificacion, Convocatoria). Permite: matricular, poner notas, ver expediente, calcular media ponderada.

Ejercicio 3: Tienda online completa

Expande el ejemplo de la tienda con: Cliente, Pedido, DetallePedido. Implementa: crear pedido, listar pedidos de un cliente, informes de ventas (por producto, por mes, por cliente), stock automático.


Resumen

Operación Código EF Core
Crear db.Items.Add(item); await db.SaveChangesAsync();
Leer todos await db.Items.ToListAsync();
Leer por Id await db.Items.FindAsync(id);
Filtrar await db.Items.Where(i => ...).ToListAsync();
Incluir relación .Include(i => i.Relacion)
Actualizar Modificar propiedades + SaveChangesAsync()
Eliminar db.Items.Remove(item); SaveChangesAsync()
Crear migración dotnet ef migrations add Nombre
Aplicar migración dotnet ef database update