Entity Framework Core · Ejercicio guiado · 45 min
Te damos una aplicación con tres clases de dominio y una página Blazor sencilla, pero sin base de datos. Vas a escribir el DbContext, generar migraciones y ver qué tablas arma Entity Framework a partir de tus clases, sin escribir una línea de SQL.
LabEF-inicial.zip que te pasa el docente.dotnet tool install --global dotnet-ef --version 9.* # si ya la tenés, usá "update" en vez de "install"
docker compose · LabEF.sln · conexión desde DBeaver
Descomprimí LabEF-inicial.zip y abrí LabEF.sln con Rider. En la carpeta de la solución hay un docker-compose.yml con un SQL Server. Abrí la terminal de Rider (queda parada en esa carpeta) y levantalo:
docker compose up -d # la primera vez descarga la imagen y tarda un poco
Ahora conectate a ese servidor con DBeaver, para poder mirar las tablas por dentro durante todo el ejercicio. Creá una conexión nueva (, o el ícono del enchufe con un ), elegí SQL Server y completá:
localhost · Port: 1433 (revisá el número: es fácil escribir 1443)sa · Password: Passw1rd (la del docker-compose.yml)Tocá . Si DBeaver pide descargar el driver, aceptá. Después .
TrustServerCertificate=true en el connection string de la app.
docker ps y que el puerto sea 1433.
LabEF.
qué hay y qué falta
La solución está separada en tres proyectos:
LabEF.Web ──▶ LabEF.DataAccess ──▶ LabEF.Domain (Blazor) (EF Core) (clases de dominio) LabEF.Domain/Vehiculo.cs, Auto.cs, Moto.cs las clases que vamos a guardar LabEF.DataAccess/SqlContext.cs el DbContext, todavía vacío LabEF.DataAccess/VehiculoRepositorio.cs lista y agrega vehículos usando el contexto LabEF.Web/Program.cs falta registrar el contexto LabEF.Web/appsettings.json el connection string ya está LabEF.Web/Components/Pages/Vehiculos.razor la página para probar
Abrí las tres clases de LabEF.Domain. Vehiculo es abstracta y tiene lo común; Auto y Moto heredan de ella y agregan un dato propio cada una. Fijate que son clases comunes de C#: no saben nada de bases de datos.
Eso es code first: vos escribís las clases, y Entity Framework deduce a partir de ellas qué tablas y columnas tiene que tener la base. El DbContext es la clase que une las dos cosas: representa la base de datos dentro de tu código.
Auto y una Moto en tablas, si comparten algunos datos y otros no?
decirle a EF qué guardar y dónde
Abrí LabEF.DataAccess/SqlContext.cs y resolvé el TODO 1:
using LabEF.Domain;
using Microsoft.EntityFrameworkCore;
namespace LabEF.DataAccess;
public class SqlContext : DbContext
{
public SqlContext(DbContextOptions<SqlContext> options) : base(options)
{
}
// TODO 1: agregá acá las propiedades que le dicen a EF qué clases tiene que guardar en la base.
}
public DbSet<MiClase> MisClases { get; set; }. Ojo: EF no sale a buscar subclases por su cuenta, solo conoce las clases que le nombrás.
DbSet se usa como nombre de la tabla.Id (o NombreDeClaseId) es la clave primaria, y si es int se autoincrementa.get y set es una columna, con un tipo de SQL según el tipo de C#.null en C#, la columna admite NULL; si no, es NOT NULL.
Ahora abrí LabEF.Web/Program.cs y resolvé el TODO 2. El connection string ya está en appsettings.json con el nombre DefaultConnection:
// TODO 2: registrá SqlContext para que use SQL Server con el connection string // "DefaultConnection" de appsettings.json builder.Services.AddScoped<VehiculoRepositorio>();
builder.Services.AddDbContext<MiContexto>(options => ...). Adentro le decís qué motor usar (options.UseSqlServer(...)) y le pasás el connection string, que se lee con builder.Configuration.GetConnectionString("Nombre").
Corré LabEF.Web con ▶ Run y entrá a Vehículos desde el menú. Mirá qué pasa y después ■ Stop.
LabEF no existe. Tener las clases y el contexto no alcanza: nadie creó la base todavía.
EF traduce tus clases a un plan de cambios
Una migración es una clase de C# que EF genera comparando tu modelo (las clases y el contexto) con el estado anterior. Describe qué hay que cambiar en la base para que coincida con tu código. Generarla no toca la base: solo escribe archivos.
Click derecho sobre el proyecto LabEF.DataAccess → . En el diálogo:
LabEF.DataAccess, donde está el contexto y donde van a quedar las migraciones.LabEF.Web, el que tiene el Program.cs y el connection string.SqlContext · Migration name: InicialSqlContext o la migración falla, revisá los dos proyectos: el de migraciones tiene que ser el que contiene el contexto, y el de inicio, el que lo registra en Program.cs.
Aparece una carpeta Migrations en LabEF.DataAccess. Antes de abrirla, escriban su predicción. Después abran el archivo que termina en _Inicial.cs, lean el método Up y completen qué pasó.
| Pregunta | Predicción (antes de abrir) | Qué dice la migración |
|---|---|---|
| ¿Cuántas tablas crea para los vehículos? ¿Con qué nombre? | ||
| ¿Qué columnas tiene? ¿Hay alguna que no esté en tus clases? | ||
¿Qué columnas admiten null? ¿Coincide con las propiedades de tus clases? |
Migrations hay tres archivos: la migración _Inicial.cs, su .Designer.cs y un SqlContextModelSnapshot.cs, que es la foto del modelo con la que EF va a comparar la próxima vez.
CantidadPuertas y Cilindrada son int, que en C# no puede ser null. ¿Por qué EF igual las creó como columnas que admiten NULL?
la jerarquía, vista desde SQL
Click derecho sobre LabEF.DataAccess → , con los mismos proyectos de antes. Cuando termine, en DBeaver seleccioná la conexión y apretá F5 para refrescarla.
LabEF después de refrescar, puede que DBeaver esté mostrando solo una base: editá la conexión (click derecho → ) y activá la opción para mostrar todas las bases. Si con eso tampoco aparece, la migración no se aplicó: revisá la salida del .
LabEF, con dos tablas: la de vehículos y __EFMigrationsHistory.
Corré LabEF.Web, entrá a Vehículos y agregá un auto y una moto. Después ■ Stop y abrí un editor de consultas sobre la base LabEF (en DBeaver, click derecho sobre la base → ) y ejecutá:
SELECT * FROM Vehiculos; SELECT * FROM __EFMigrationsHistory;
Discriminator dice Auto o Moto, y cada fila tiene NULL en la columna que no le corresponde. En __EFMigrationsHistory hay una fila con tu migración Inicial.
Discriminator que indica de qué clase es cada fila. Es lo que EF hace por convención. Hay otras dos estrategias, que se eligen configurándolas en el contexto:
Vehiculos guarda lo común, y Autos y Motos guardan lo propio, unidas por el Id. Sin columnas vacías, pero cada consulta necesita juntar tablas.Vehiculos, y los datos comunes se repiten en la estructura de cada tabla.
NULL.
Camion con cinco propiedades propias, ¿cómo queda la tabla con TPH? ¿En qué caso te convendría más TPT o TPC?
el código y la base se desincronizan
Agregá esta propiedad a Vehiculo, debajo de Anio:
public string Color { get; set; } = "";
No crees ninguna migración todavía. Corré LabEF.Web y entrá a Vehículos.
Color inválida. Tu código ya conoce el color, pero la base no.
■ Stop. Ahora probá aplicar sin crear migración y leé el mensaje de EF:
The model for context 'SqlContext' has pending changes. Add a new migration before updating the database.
TODO 3: creá una migración llamada AgregarColor, leé su método Up y aplicala con . Volvé a correr la app.
__EFMigrationsHistory tiene ahora dos filas.
Color no admite NULL, pero ya había filas en la tabla. ¿Qué valor les puso EF a esas filas viejas? Y si un compañero baja tu código con las dos migraciones y tiene su propia base vacía, ¿qué pasa cuando corre ?
para los que terminaron antes
OnModelCreating en SqlContext y limitá Marca a 50 caracteres con HasMaxLength. Creá la migración y mirá cómo cambia el tipo de la columna.OnModelCreating, configurá la jerarquía de Vehiculo con UseTptMappingStrategy() y creá una migración sin aplicarla. Leé qué tablas propone y después borrala con .