Resumen de contenidos
Si después de leer esta entrada estas interesado en el Entity Framework Core, ya esta disponible la versión 3.0 y trae muchas novedades.
Venimos con la segunda parte de «Haciendo fácil el acceso a datos con Entity Framework Core«. Esta vez, vamos a hacer el mismo ejemplo, pero con la opción «Code First». Utilizando esta opción, vamos a generar nuestra base de datos mediante clases que relacionaremos entre ellas, así veremos las dos caras de la moneda.
Modelo
Tengamos el objetivo claro, mediante la opción «Code First» vamos a buscar conseguir un modelo como este:
En el tenemos 2 relaciones 1 a muchos:
- Un profesor podrá impartir varios cursos.
- Cada curso puede tener varios alumnos.
Creando la solución en Visual Studio
En primer lugar, vamos a crear el proyecto, solo que esta vez, será un proyecto de consola Net Core:
Paquetes
Como la vez anterior, instalamos los paquetes de Entity Framework Core vía NuGet:
En caso de querer hacerlo vía consola:
PM->Install-Package Microsoft.EntityFrameworkCore -Version 2.1.3
PM->Install-Package Microsoft.EntityFrameworkCore.Tools -Version 2.1.3
Y lo mismo para el proveedor de MySql
O como siempre, por consola:
PM->Install-Package Pomelo.EntityFrameworkCore.MySql -Version 2.1.2
Creando la solución en DotNet CLI
Una de las ventajas de Net Core es que nos permite ejecutar sus acciones desde línea de comandos, con eso, conseguimos toda la funcionalidad en plataformas como Linux. En este caso, utilizaremos la Powershell. Para crear un proyecto, crearemos una carpeta y navegaremos a ella con:
– mkdir PostEntityCore
– cd PostEntityCore
Una vez dentro de la carpeta, crearemos el proyecto y añadiremos los paquetes con:
– dotnet new console
– dotnet add package Microsoft.EntityFrameworkCore -v 2.1.3
– dotnet add package Microsoft.EntityFrameworkCore.Tools -v 2.1.3
– dotnet add package Pomelo.EntityFrameworkCore.MySql -v 2.1.2
Generando el DbContext
En este caso, el modelo lo vamos a generar mediante clases en nuestro proyecto, a fin de organizar el proyecto, añadiremos una carpeta «Data» a la solución, donde añadiremos las siguientes clases:
Vamos a desgranar las clases:
//Alumnos.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace PostCore.Data
{
public class Alumnos
{
[Key]
public int IdAlumno { get; set; } //Clave primaria
public string Nombre { get; set; }
public DateTime Nacimiento { get; set; }
public int IdCurso { get; set; } //Campo clave foranea
//Entity Framewrok Core
public Cursos Curso { get; set; } //Objeto de navegación virtual EFC
}
}
//Cursos.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace PostCore.Data
{
public class Cursos
{
//Inicializamos el objeto de navegacion virtual de Entity Framework Core
public Cursos()
{
Alumnos = new HashSet<Alumnos>();
}
[Key]
public int IdCurso { get; set; } //Clave primaria
public string Nombre { get; set; }
public string Ciudad { get; set; }
public int IdProfesor { get; set; } //Campo clave foranea
//Entity Framewrok Core
public Profesores Profesor { get; set; } //Objeto de navegación virtual EFC
public ICollection<Alumnos> Alumnos { get; set; } //Objeto de navegación virtual EFC
}
}
//Profesores.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace PostCore.Data
{
public class Profesores
{
//Inicializamos el objeto de navegacion virtual de Entity Framework Core
public Profesores()
{
Cursos = new HashSet<Cursos>();
}
[Key]
public int IdProfesor { get; set; } //Clave primaria
public string Nombre { get; set; }
//Entity Framewrok Core
public ICollection<Cursos> Cursos { get; set; } //Objeto de navegación virtual EFC
}
}
//PostDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace PostCore.Data
{
//Heredamos de DbContext nuestro contexto
class PostDbContext : DbContext
{
//Constructor sin parametros
public PostDbContext()
{
}
//Constructor con parametros para la configuracion
public PostDbContext(DbContextOptions options)
: base(options)
{
}
//Sobreescribimos el metodo OnConfiguring para hacer los ajustes que queramos en caso de
//llamar al constructor sin parametros
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//En caso de que el contexto no este configurado, lo configuramos mediante la cadena de conexion
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseMySql("Server=localhost;Database=postefcore;Uid=root;Pwd=root;");
}
}
//Tablas de datos
public virtual DbSet<Alumnos> Alumnos { get; set; }
public virtual DbSet<Cursos> Cursos { get; set; }
public virtual DbSet<Profesores> Profesores { get; set; }
}
}
Entity Framework Core «Code First» en Visual Studio
Una vez tenemos el contexto creado en nuestro proyecto, iremos a la «Consola del Administrador de paquetes», y ahí dispondremos de estos comandos:
- add-migration {nombre} -Context {contexto}
- remove-migration
- update-database {nombre} -Context {contexto}
- drop-database -Context {contexto}
Vamos a explicar los comandos:
add-migration: Con este comando, generaremos la migración que lanzaremos a la base de datos. Este comando tiene 2 parámetros:
- {nombre}: Con este parámetro indicaremos el nombre que queremos indicarle a la migración, ademas, es obligatorio.
- -Context {contexto}: En caso de tener más de un contexto de datos en nuestro programa, indicaremos cual de los contextos es mediante este parámetro. Si solo tenemos un contexto de datos, no es obligatorio usarlo.
remove-migration: Con este comando, eliminaremos la ultima migración que hemos generado. Se puede utilizar varias veces consecutivamente para ir eliminando migraciones desde la última a la primera.
update-database: Con este comando, enviaremos a la base de datos los cambios de la migración, haciéndola efectiva:
- {nombre}: Con este parámetro indicaremos el nombre de la migración que queremos aplicar.
- -Context {contexto}: En caso de tener más de un contexto de datos en nuestro programa, indicaremos cual de los contextos es mediante este parámetro. Si solo tenemos un contexto de datos, no es obligatorio usarlo.
drop-database: Con este comando, eliminaremos la base de datos. Este comando tiene 1 parámetro:
- -Context {contexto}: En caso de tener más de un contexto de datos en nuestro programa, indicaremos cual de los contextos es mediante este parámetro. Si solo tenemos un contexto de datos, no es obligatorio usarlo.
Una vez que conocemos los comandos, vamos a ponernos en faena… Para empezar, utilizamos el comando:
add-migration init -Context PostDbContext
Esto genera una carpeta «Migrations» donde va a ir guardando cada una de las migraciones. Conviene no borrar el contenido, ya que almacena las «versiones» del contexto de datos, de modo que podemos revertir los cambios en la base de datos. Ademas, el hecho de almacenar estas migraciones, permite al sistema aplicar cambios de manera incremental en las próximas migraciones, y es requisito para poder aplicar cambios a la base de datos sin que se produzcan errores por tablas ya existentes.
Con eso, ejecutamos:
update-database -Context PostDbContext
Entity Framework Core «Code First» en DotNet CLI
Igualmente, podemos hacer toda la labor desde el cli de dotnet con los comandos equivalentes de «dotnet ef»:
- migrations add {nombre} -c {contexto}
- migrations remove
- database update {nombre} -c {contexto}
- database drop -c {contexto}
Vamos a explicar los comandos:
migrations add: Con este comando, generaremos la migración que lanzaremos a la base de datos. Este comando tiene 2 parámetros:
- {nombre}: Con este parámetro indicaremos el nombre que queremos indicarle a la migración, ademas, es obligatorio.
- -c {contexto}: En caso de tener más de un contexto de datos en nuestro programa, indicaremos cual de los contextos es mediante este parámetro. Si solo tenemos un contexto de datos, no es obligatorio usarlo.
migrations remove: Con este comando, eliminaremos la ultima migración que hemos generado. Se puede utilizar varias veces consecutivamente para ir eliminando migraciones desde la última a la primera.
database update: Con este comando, enviaremos a la base de datos los cambios de la migración, haciéndola efectiva:
- {nombre}: Con este parámetro indicaremos el nombre de la migración que queremos aplicar.
- -c {contexto}: En caso de tener más de un contexto de datos en nuestro programa, indicaremos cual de los contextos es mediante este parámetro. Si solo tenemos un contexto de datos, no es obligatorio usarlo.
database drop: Con este comando, eliminaremos la base de datos. Este comando tiene 1 parámetro:
- -c {contexto}: En caso de tener más de un contexto de datos en nuestro programa, indicaremos cual de los contextos es mediante este parámetro. Si solo tenemos un contexto de datos, no es obligatorio usarlo.
Una vez que conocemos los comandos del CLI, vamos a ello
dotnet ef migrations add init -c PostDbContext
Con eso, ejecutamos:
dotnet ef database update -c PostDbContext
Resultado
Una vez hecho esto de cualquiera de las 2 maneras, desde el gestor de nuestra base de datos, veremos que se ha creado la base de datos, y si aplicamos la ingeniería inversa para reconstruir el modelo (desde el MySql Workbench), veremos algo así::
Vemos que hemos conseguido generar el modelo que queríamos, pero tenemos una tabla más, «__efmigrationshistory», en esta tabla es donde Entity Framework Core registra las versiones del contexto que están aplicadas en la base de datos.
Ejemplo
En mi perfil de GitHub he dejado un ejemplo del proyecto, el cual una vez aplicadas las migraciones sobre la base de datos, podemos ver que funciona correctamente ante la misma lógica de programa de la primera parte:
using (postefcoreContext context = new postefcoreContext())
{
//Creamos el profesor
var profesor = new Profesores() { Nombre = "Pedro" };
context.Add(profesor);
context.SaveChanges();
profesor = context.Profesores.Last();
//Creamos los cursos
var curso1 = new Cursos() { Nombre = "Matematicas", IdProfesor = profesor.IdProfesor };
context.Add(curso1);
var curso2 = new Cursos() { Nombre = "Lenguaje", IdProfesor =
profesor.IdProfesor };
context.Add(curso2);
context.SaveChanges();
curso1 = context.Cursos.First(x => x.Nombre == curso1.Nombre);
curso2 = context.Cursos.First(x => x.Nombre == curso2.Nombre);
//Creamos los alumnos
var alumno1 = new Alumnos() { Nombre = "Jorge", IdCurso =
curso1.IdCurso };
context.Add(alumno1);
var alumno2 = new Alumnos() { Nombre = "Juan", IdCurso = curso1.IdCurso };
context.Add(alumno2);
var alumno3 = new Alumnos() { Nombre = "Andrea", IdCurso = curso2.IdCurso };
context.Add(alumno3);
var alumno4 = new Alumnos() { Nombre = "Sandra", IdCurso = curso2.IdCurso };
context.Add(alumno4);
//Guardamos los cambios
context.SaveChanges();
}
using (postefcoreContext context = new postefcoreContext())
{
var profesor = context.Profesores //Indicamos la tabla
.Include(x => x.Cursos) //Incluimos los resultados coincidentes de la tabla cursos (inner join)
.ThenInclude(x => x.Alumnos) //Incluimos los resultados coincidentes de la tabla alumnos (inner join)
.First(); //Seleccionamos el primero
foreach (var curso in profesor.Cursos)
foreach (var alumno in curso.Alumnos)
Console.WriteLine($"El alumno {alumno.Nombre} recibe el curso de {curso.Nombre},impartido por {profesor.Nombre}");
}
Nos devolverá:
El alumno Juan recibe el curso de Matematicas,impartido por Pedro
El alumno Jorge recibe el curso de Matematicas,impartido por Pedro
El alumno Sandra recibe el curso de Lenguaje,impartido por Pedro
El alumno Andrea recibe el curso de Lenguaje,impartido por Pedro
En caso de no tener Visual Studio, lanzaremos el proyecto desde el CLI de dotnet con :
dotnet run –proyect PostCore
Aviso a navegantes
En caso de que no queramos almacenar las migraciones, se pueden borrar del proyecto eliminándolas como archivo, o simplemente con:
PM -> remove-migration
CLI -> dotnet ef migrations remove
Pero cada vez que queramos crear una nueva migración y aplicarla, no existirá la migración previa, de manera que tendremos que hacer un drop a la base de datos mediante
PM-> drop-database -Context PostDbContext
CLI-> dotnet ef database dtop -c PostDbContext
asumiendo con ello la consiguiente perdida de datos. En caso contrario, el update lanzará un error indicando que las tablas ya existen en la base de datos.
En entornos de desarrollo, no es necesario revisar la migración ya que presumiblemente no nos importa la posible perdida de datos, pero si vamos a aplicar migraciones en entornos de producción, conviene que revisemos los ficheros generados en la migración, donde veremos que se definen 2 métodos:
protected override void Up(MigrationBuilder migrationBuilder)
protected override void Down(MigrationBuilder migrationBuilder)
Estos dos métodos serán ejecutados durante las actualizaciones de la base de datos («Up» al aplicar un migración y «Down» al quitarla) y conviene que estemos seguros de que si por ejemplo queremos cambiar como almacenamos los datos de un nombre, unificando dos columnas, hacemos que se genere una nueva columna, se migren los datos de las previas a la nueva, y entonces se borren la previas, de modo que no tengamos una perdida de datos. En cualquier caso, siempre que la migración tenga posibilidades de que se produzca una perdida de información, Entity Framework Core nos lo notificara con el mensaje:
En este enlace, dejo un ejemplo de la documentación oficial detallando más sobre ese tema.
Si después de leer esta entrada estas interesado en el Entity Framework Core, ya esta disponible la versión 3.0 y trae muchas novedades.
Este método, ¿no limita al usuario a solo conocer C# o cualquier otro lenguaje?, usando el método tradicional, select, insert, update….etc de paso se reforzaba el aprendizaje de manejo de una base de datos. ¿o no?
Buenos días Luis,
Gracias por tu comentario!!!
La respuesta es sí y no… Precisamente, los ORM como Entity Framework se crearon para que los desarrolladores tengamos un lenguaje de acceso a datos fuertemente tipado, y no tener que poner consultas directamente en SQL en el código. Si que es cierto que es importante conocer SQL y saber que es lo que estas haciendo, uses o no un ORM, pero por otro lado, utilizar directamente lenguaje SQL en tu código, añade mucha propensión a errores.
Si escribes directamente las sentencias SQL como string en tu código, estas añadiendo «magic strings», y solo vas a detectar problemas en tiempo de ejecución. Si en cambio utilizas un ORM, al convertir la base de datos en una entidad fuertemente tipada, los errores van a ser en tiempo de compilación.
La verdad es que usar o no un ORM depende un poco de las necesidades (hay que tener en cuenta que añaden una carga de trabajo extra respecto a tirar consultas «a pelo»), pero para mi personalmente en la gran mayoría de los casos, es aconsejable porque ahorra mucho tiempo de desarrollo y detección de problemas. Además, en el caso de Entity Framework (y prácticamente todos), puedes loguear la consulta SQL que se esta ejecutando realmente en la base de datos para poder depurar posibles problemas, así que tampoco me atrevería a decir que utilizar un ORM te libra de saber SQL, porque si las cosas fallan vas a tener que acabar comprobando SQL, simplemente en vez de generarlo tu, lo va a generar el ORM que utilices.
Un saludo!!
supongo yo que para la vieja escuela no es ningun problema usar entity, ya que crecimos usando SQl y nos hicimos masters usandolo, pero sin embargo para la nueva, aunque es recomendable saber SQl, lo estan dejando de lado, es acertado tu comentario, al decir que tienen que saber si, o si sql, pero con esta maravillosa herramienta, creo yo que es un problema ya que solo accederas a sql si algo sale mas y solo si sale mal, pero eso no implica que realmente conoceran todo el potencial que tiene SQl, pero en fin que se le va a hacer.. ciertamente entre mas alto nivel sube la programacion, mas conocimiento se pierde, eso es mi pensar..
un Saludo
–utilizar directamente lenguaje SQL en tu código
–Si escribes directamente las sentencias SQL como string en tu código
En serio piensas que de esta manera se programan aplicacines profesionales???
No sabes de la existencia de stored procedures y que estos se invocan desde la capa de datos de la aplicacion??
Tu comentario denota mucho desconocimiento real y completo de las cosas
Buenas Carlos,
Lo siento pero no acabo de entender tu comentario 🙁
¿Por qué estas pensando que propongo hacer ese tipo de cosas? Precisamente lo que estoy diciendo es lo contrario, que hay que evitar magic strings siempre.
Siento si has entendido mal mi comentario y tienes la sensación de que tengo un desconocimiento real y completo de las cosas.
Tal vez seas tan amable de decirnos como harías tu las cosas desde tu conocimiento real y completo de modo que todos podamos aprender. A fin de cuentas, el objetivo de este blog es transmitir conocimiento 🙂
Un saludo
Carlos, no entiendo muy bien dónde ves lo que has reprochado… Creo que lo escrito es correcto y la pedagogía pasa por contraponer puntos, quizás eso te haya despistado.
En cualquier caso creo que siempre es más provechoso ser constructivo que destructivo. Corregir en público y además sin aportar mejor solución es constraproducente. Terminando el apunte, soy de la opinión de que el reconocimiento debe ser público y reproche en privado; y si tiene que ser en público, por lo menos ser constructivo y buscando un tono neutro.
En cualquier caso creo que has descontextualizado y no has hecho el esfuerzo de entenderlo. Aunque quizás sí y estés en tu derecho de opinar, pero en tu forma de comentar tras leerte me quedo igual. 😅
Tras tu comentario tengo ganas de leerte. ¿Dónde escribes?
Aprovecho a felicitar a Jorge por el curro que lleva publicar, aportar al Open Source y su actividad como evangelizador tecnológico. 😉
Hola, a mi no me aparece pomelo en visual studio