Módulo 19: 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
dotnet add package Microsoft.EntityFrameworkCore.Tools

# 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…

Para este módulo instala como mínimo Microsoft.EntityFrameworkCore.Sqlite, Microsoft.EntityFrameworkCore.Design y Microsoft.EntityFrameworkCore.Tools.

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

Ejemplo de referencias en el archivo .csproj

Al instalar paquetes NuGet, Visual Studio o la CLI modifican el archivo .csproj. Un ejemplo completo para este módulo podría ser:

<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />

  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.8">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>

  <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>

  <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.8" />
  <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" />
  <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
</ItemGroup>

💡 Consejo: No es obligatorio escribir estas líneas a mano. Normalmente se generan al instalar paquetes desde NuGet o con dotnet add package. Revísalas si algo falla o si quieres confirmar qué paquetes tiene instalado el proyecto.


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" }
        );
        modelBuilder.Entity<Producto>().HasData(
            new Producto { Id = 1, Nombre = "Smartphone", Descripcion = "Teléfono inteligente de última generación.", Precio = 699.99m, Stock = 50, FechaCreacion = new DateTime(2026, 5, 26), CategoriaId = 1 },
            new Producto { Id = 2, Nombre = "Laptop", Descripcion = "Portátil potente para trabajo y entretenimiento.", Precio = 1299.99m, Stock = 30, FechaCreacion = new DateTime(2026, 5, 26), CategoriaId = 1 },
            new Producto { Id = 3, Nombre = "Camiseta", Descripcion = "Camiseta de algodón para uso diario.", Precio = 19.99m, Stock = 100, FechaCreacion = new DateTime(2026, 5, 26), CategoriaId = 2 },
            new Producto { Id = 4, Nombre = "Sofá", Descripcion = "Sofá cómodo para sala de estar.", Precio = 499.99m, Stock = 20, FechaCreacion = new DateTime(2026, 5, 26), CategoriaId = 3 },
            new Producto { Id = 5, Nombre = "Lámpara de mesa", Descripcion = "Lámpara de mesa moderna para iluminación ambiental.", Precio = 89.99m, Stock = 40, FechaCreacion = new DateTime(2026, 5, 26), CategoriaId = 3 },
            new Producto { Id = 6, Nombre = "Auriculares", Descripcion = "Auriculares inalámbricos con cancelación de ruido.", Precio = 199.99m, Stock = 25, FechaCreacion = new DateTime(2026, 5, 26), CategoriaId = 1 }
        );
    }
}

5. Crear y aplicar migraciones

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

Opción 1: Terminal con .NET CLI

# 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

Opción 2: Consola del Administrador de paquetes (PMC)

La Consola del Administrador de paquetes es la herramienta más tradicional dentro de Visual Studio para interactuar con Entity Framework. Utiliza comandos basados en PowerShell.

Para abrirla en Visual Studio Community: Ver → Otras ventanas → Consola del Administrador de paquetes. Si tu IDE está en inglés: View → Other Windows → Package Manager Console.

Consola del Administrador de paquetes para migraciones EF Core Package Manager Console: selecciona el proyecto correcto y ejecuta los comandos de migración

# Crear una nueva migración
Add-Migration Inicial

# Aplicar los cambios a la base de datos
Update-Database

# Borrar la última migración antes de aplicarla a la base de datos
Remove-Migration

⚠️ Importante: En la parte superior de esta consola verás un desplegable llamado Proyecto predeterminado. Selecciona el proyecto donde está configurado tu DbContext, o los comandos pueden fallar.

Tabla comparativa rápida

Acción Consola (PMC) Terminal (.NET CLI)
Crear migración Add-Migration <Nombre> dotnet ef migrations add <Nombre>
Actualizar BD Update-Database dotnet ef database update
Deshacer migración Remove-Migration dotnet ef migrations remove
Generar script SQL Script-Migration dotnet ef migrations script

💡 Consejo: Si trabajas dentro de Visual Studio Community, PMC es cómoda porque ya conoce la solución. Si trabajas con VS Code, Rider o terminal, la .NET CLI suele ser la opción más portable.

Explorar SQLite con SQLite/SQL Server Compact Toolbox

Para ver la base de datos SQLite desde Visual Studio puedes instalar la extensión SQLite/SQL Server Compact Toolbox.

Pasos:

  1. Abre Extensiones → Administrar extensiones.
  2. Busca SQLite/SQL Server Compact Toolbox.
  3. Instala la extensión y reinicia Visual Studio si lo solicita.
  4. Abre Ver → Otras ventanas → SQLite/SQL Server Compact Toolbox.
  5. Pulsa Add SQLite Connection… y selecciona el archivo tienda.db.

SQLite SQL Server Compact Toolbox en Visual Studio SQLite/SQL Server Compact Toolbox permite explorar tablas, datos y scripts SQL desde Visual Studio

Ejemplos de manejo:

Acción Cómo hacerlo
Ver tablas Expande la conexión tienda.db y abre Tables
Ver datos Clic derecho sobre una tabla → Edit Top 200 Rows o Browse Data
Ver estructura Expande la tabla para revisar columnas, claves e índices
Ejecutar SQL Abre una ventana de consulta y escribe un SELECT
Generar script Clic derecho sobre tabla → opciones de scripting

Consulta rápida:

SELECT Id, Nombre, Precio, Stock
FROM Productos
ORDER BY Nombre;

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