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: 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
DbContextes la clase principal de EF Core. Representa la sesión con la base de datos y contieneDbSet<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.
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:
- Abre Extensiones → Administrar extensiones.
- Busca
SQLite/SQL Server Compact Toolbox. - Instala la extensión y reinicia Visual Studio si lo solicita.
- Abre Ver → Otras ventanas → SQLite/SQL Server Compact Toolbox.
- Pulsa Add SQLite Connection… y selecciona el archivo
tienda.db.
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 |