Entity Framework Core: tutorial práctico en C#
Aprende Entity Framework Core con .NET 10: DbContext, SQL Server, migraciones, LINQ, CRUD, AsNoTracking y scaffolding.
Actualizado el
Entity Framework Core (EF Core) es un ORM para .NET: traduce las operaciones que haces sobre objetos de C# y expresiones LINQ a consultas del proveedor de base de datos. En lugar de escribir SQL para cada operación, trabajas con clases y dejas que el ORM genere la consulta.
Además del mapeo entre tablas y objetos, EF Core centraliza el seguimiento de cambios, la gestión de la conexión y las migraciones del esquema. Eso no significa que nunca vayas a necesitar SQL ni que las consultas generadas sean óptimas por defecto: conviene revisarlas cuando el rendimiento importa.
Cuándo usar Entity Framework Core
Entity Framework Core encaja bien si quieres:
- Mapear tablas a clases de C#.
- Hacer consultas con LINQ.
- Crear, modificar y eliminar registros sin escribir SQL manual.
- Gestionar cambios de estructura con migraciones.
No siempre es la mejor opción si necesitas exprimir al máximo el rendimiento de consultas muy concretas. En esos casos puedes combinarlo con SQL directo o usar un micro ORM como Dapper. Si necesitas controlar cada consulta SQL y prefieres un micro ORM, compara este enfoque con el tutorial de Dapper en C#.
Instalar Entity Framework Core
Para seguir el tutorial necesitas .NET 10 y EF Core 10.0.12. Crea un proyecto de consola e instala los paquetes:
dotnet new console -n EfCoreTutorial -f net10.0
cd EfCoreTutorial
dotnet add package Microsoft.EntityFrameworkCore --version 10.0.12
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 10.0.12
dotnet add package Microsoft.EntityFrameworkCore.Design --version 10.0.12
dotnet tool install --global dotnet-ef --version 10.0.12Microsoft.EntityFrameworkCore.Tools solo es necesario si vas a usar la consola de paquetes de Visual Studio; para la CLI de dotnet ef basta con Microsoft.EntityFrameworkCore.Design. Todos los paquetes de EF Core deben mantener la misma versión principal y de parche para evitar incompatibilidades.
EF Core 10 requiere .NET 10: no funciona sobre .NET 6 ni sobre versiones anteriores.
Si prefieres crear el proyecto desde Visual Studio, puedes hacerlo desde la interfaz gráfica. Las capturas pueden mostrar una interfaz anterior, pero la plataforma de destino que debes seleccionar es net10.0:

Escoge el nombre y la ubicación del proyecto:

Selecciona la plataforma de destino (.NET 10):

Crear un modelo
Un modelo representa una tabla.
public class Producto
{
public int Id { get; set; }
public string Nombre { get; set; } = string.Empty;
public decimal Precio { get; set; }
public int Stock { get; set; }
}Crear el DbContext
El DbContext representa la conexión con la base de datos y expone las tablas como DbSet.
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext()
{
}
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connectionString = Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING");
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException(
"Define la variable de entorno SQLSERVER_CONNECTION_STRING antes de usar AppDbContext.");
}
optionsBuilder.UseSqlServer(connectionString);
}
}
public DbSet<Producto> Productos => Set<Producto>();
}Configurar la conexión
En la aplicación de consola del tutorial
Define SQLSERVER_CONNECTION_STRING en la terminal antes de ejecutar los comandos de migración. Por ejemplo, en Bash:
export SQLSERVER_CONNECTION_STRING="Server=localhost;Database=Tienda;Trusted_Connection=True;TrustServerCertificate=True;"
dotnet ef migrations add CrearTablaProductos
dotnet ef database updateTrusted_Connection=True usa autenticación integrada con la identidad del proceso que ejecuta la aplicación o la CLI (dotnet ef); esa identidad necesita permisos en SQL Server y el servidor debe ser accesible.
El constructor sin parámetros y OnConfiguring permiten que la CLI cree AppDbContext y use esa variable de entorno. Para trabajar con el contexto desde la consola:
using var context = new AppDbContext();En una aplicación ASP.NET Core
En appsettings.json puedes guardar la cadena de conexión:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=Tienda;Trusted_Connection=True;TrustServerCertificate=True;"
}
}Registra el contexto mediante inyección de dependencias:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));Esta configuración por petición encaja en una API ASP.NET Core como la utilizada en el CRUD con Blazor WebAssembly; los componentes Blazor interactivos de servidor requieren considerar IDbContextFactory. appsettings.json y builder.Services no se configuran automáticamente en una aplicación de consola.
Crear migraciones
Una migración genera los cambios necesarios en la base de datos a partir de tus modelos.
dotnet ef migrations add genera la migración y dotnet ef database update la aplica a la base de datos. Para despliegues, es preferible revisar el SQL y generar un script con dotnet ef migrations script --idempotent antes que ejecutar migraciones a ciegas en producción.
Consultar datos
A partir de aquí, los ejemplos de CRUD asumen que ya tienes un AppDbContext context creado (como en la consola) o recibido por inyección de dependencias en ASP.NET Core, además del using Microsoft.EntityFrameworkCore; necesario para los métodos asíncronos.
var productos = await context.Productos
.OrderBy(p => p.Nombre)
.ToListAsync();
foreach (var producto in productos)
{
Console.WriteLine(producto.Nombre);
}Con filtro:
var productosConStock = await context.Productos
.Where(p => p.Stock > 0)
.OrderBy(p => p.Nombre)
.ToListAsync();Insertar un registro
var producto = new Producto
{
Nombre = "Teclado",
Precio = 39.99m,
Stock = 10
};
context.Productos.Add(producto);
await context.SaveChangesAsync();SaveChangesAsync es lo que envía realmente el cambio a la base de datos.
Actualizar un registro
var producto = await context.Productos.FirstOrDefaultAsync(p => p.Id == 1);
if (producto != null)
{
producto.Precio = 34.99m;
await context.SaveChangesAsync();
}Entity Framework detecta que el objeto cambió y genera el UPDATE correspondiente.
Eliminar un registro
var producto = await context.Productos.FirstOrDefaultAsync(p => p.Id == 1);
if (producto != null)
{
context.Productos.Remove(producto);
await context.SaveChangesAsync();
}Obtener datos sin seguimiento
Si solo vas a leer datos y no vas a modificarlos con ese contexto, usa AsNoTracking para reducir el trabajo interno.
var productos = await context.Productos
.AsNoTracking()
.Where(p => p.Stock > 0)
.ToListAsync();Scaffolding desde una base de datos existente
Si ya tienes una base de datos creada, puedes generar modelos y contexto con dotnet ef dbcontext scaffold:
dotnet ef dbcontext scaffold \
"Server=localhost;Database=AdventureWorks;Trusted_Connection=True;TrustServerCertificate=True;" \
Microsoft.EntityFrameworkCore.SqlServer \
-o Models \
-t Producto \
--context-dir Context \
-c AppDbContext-o Models: carpeta de salida para las clases generadas.-t Producto: genera solo la tabla indicada; puedes repetir-tpara varias tablas u omitirlo para generar todas.--context-dir Context: carpeta donde se guarda elDbContext.-c AppDbContext: nombre de la clase de contexto.
Si trabajas desde la consola de paquetes de Visual Studio, dispones del comando equivalente Scaffold-DbContext.
Recomendaciones
- No guardes cadenas de conexión ni credenciales en el código fuente ni en el repositorio.
- Usa migraciones para cambios de estructura si el proyecto lo permite.
- Usa
AsNoTrackingen consultas de solo lectura. - Proyecta solo las columnas que necesitas en lugar de cargar entidades completas.
- Revisa el SQL generado y los logs si una consulta va lenta.
- Si recurres a SQL directo, usa consultas parametrizadas.
Entity Framework Core simplifica mucho el CRUD habitual, pero conviene entender qué consulta genera y cuándo merece la pena optimizarla.