Ejercicio: Configuración de una migración

Completado

En esta unidad, se crean clases de entidad de C# que se asignarán a las tablas de una base de datos SQLite local. La característica de migraciones de EF Core genera tablas a partir de esas entidades.

Una migración permite actualizar incrementalmente el esquema de la base de datos.

Obtención de los archivos del proyecto

Para empezar, obtenga los archivos del proyecto. Tiene algunas opciones para obtener los archivos del proyecto:

  • Uso de GitHub Codespaces
  • Clonación del repositorio de GitHub

Si tiene instalado un entorno de ejecución de contenedor compatible, también puede usar la extensión Dev Containers para abrir el repositorio en un contenedor con las herramientas preinstaladas.

Uso de GitHub Codespaces

Un codespace es un IDE hospedado en la nube. Si usa GitHub Codespaces, vaya al repositorio en el explorador. Seleccione Código y, a continuación, cree un codespace en la rama main.

Clonación del repositorio de GitHub

Si no usa GitHub Codespaces, puede clonar el repositorio de GitHub del proyecto y, a continuación, abrir los archivos como una carpeta en Visual Studio Code.

  1. Abra un terminal de comandos y clone el proyecto desde GitHub mediante el símbolo del sistema:

    git clone https://github.com/MicrosoftDocs/mslearn-persist-data-ef-core
    
  2. Vaya a la carpeta mslearn-persist-data-ef-core y abra el proyecto en Visual Studio Code:

    cd mslearn-persist-data-ef-core
    code .
    

Revisión del código

Ahora tiene los archivos del proyecto para trabajar con ellos. Vea lo que hay en el proyecto y revise el código.

  • El proyecto, una API web de ASP.NET Core, se encuentra en el directorio ContosoPizza. Las rutas de acceso de archivo a las que se hace referencia en este módulo están relacionadas con el directorio ContosoPizza.
  • Services/PizzaService.cs es una clase de servicio que define los métodos de creación, lectura, actualización y eliminación (CRUD). Actualmente, todos los métodos inician System.NotImplementedException.
  • En Program.cs, PizzaService se registra en el sistema de inserción de dependencias de ASP.NET Core.
  • Controllers/PizzaController.cs es un valor de ApiController que expone un punto de conexión para los verbos HTTP POST, GET, PUT y DELETE. Estos verbos llaman a los métodos CRUD correspondientes en PizzaService. PizzaService se inserta en el constructor PizzaController.
  • La carpeta Models contiene los modelos usados por PizzaService y PizzaController.
  • Los modelos de entidad, Pizza.cs, Topping.csy Sauce.cs, tienen las relaciones siguientes:
    • Una pizza puede tener uno o varios ingredientes.
    • Se puede usar un ingrediente en una o muchas pizzas.
    • Una pizza puede llevar una salsa, pero una salsa se puede utilizar en muchas pizzas.

Compilar la aplicación

Para compilar la aplicación en Visual Studio Code:

  1. En el panel del Explorador, haga clic con el botón derecho en el directorio ContosoPizza y seleccione Abrir en terminal integrado.

    Se abre un panel de terminal con ámbito en el directorio ContosoPizza.

  2. Compile la aplicación mediante el comando siguiente:

    dotnet build
    

    El código debe compilarse sin advertencias ni errores.

Adición de paquetes NuGet y herramientas de EF Core

El motor de base de datos con el que trabajará en este módulo es SQLite. SQLite es un motor de base de datos ligero basado en archivos. Es una buena opción para el desarrollo y las pruebas, y también es una buena opción para las implementaciones de producción a pequeña escala.

Nota

Como se mencionó anteriormente, los proveedores de bases de datos de EF Core se pueden conectar. SQLite es una buena opción para este módulo porque es ligero y multiplataforma. Puede usar el mismo código para trabajar con diferentes motores de base de datos, como SQL Server y PostgreSQL. Incluso puede usar varios motores de base de datos en la misma aplicación.

Antes de empezar, agregue los paquetes necesarios:

  1. En el panel del terminal, ejecute el comando siguiente:

    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    

    Este comando agrega el paquete NuGet que contiene el proveedor de bases de datos SQLite de EF Core y todas sus dependencias, incluidos los servicios de EF Core comunes.

  2. Ejecute este comando:

    dotnet add package Microsoft.EntityFrameworkCore.Design
    

    Este comando agrega los paquetes necesarios para las herramientas de EF Core.

  3. Para finalizar, ejecute este comando:

    dotnet tool install --global dotnet-ef
    

    Este comando instala dotnet ef, la herramienta que usará para crear migraciones y scaffolding.

    Sugerencia

    Si dotnet ef ya está instalado, puede actualizarlo mediante la ejecución de dotnet tool update --global dotnet-ef.

Scaffolding de modelos y DbContext

Ahora agregará y configurará una implementación de DbContext. DbContext es una puerta de enlace a través de la cual puede interactuar con la base de datos.

  1. Haga clic con el botón derecho en el directorio ContosoPizza y agregue una nueva carpeta denominada Data.

  2. En la carpeta Data, cree un archivo llamado PizzaContext.cs. Agregue el código siguiente al archivo vacío:

    using Microsoft.EntityFrameworkCore;
    using ContosoPizza.Models;
    
    namespace ContosoPizza.Data;
    
    public class PizzaContext : DbContext
    {
        public PizzaContext (DbContextOptions<PizzaContext> options)
            : base(options)
        {
        }
    
        public DbSet<Pizza> Pizzas => Set<Pizza>();
        public DbSet<Topping> Toppings => Set<Topping>();
        public DbSet<Sauce> Sauces => Set<Sauce>();
    }
    

    En el código anterior:

    • El constructor acepta un parámetro de tipo DbContextOptions<PizzaContext>. El constructor permite que el código externo pase la configuración, para que se pueda compartir el mismo DbContext entre el código de prueba y de producción e incluso usarse con distintos proveedores.
    • Las propiedades DbSet<T> se corresponden con las tablas que se crean en la base de datos.
    • Los nombres de tabla coincidirán con los nombres de propiedad DbSet<T> de la clase PizzaContext. Puede invalidar este comportamiento si es necesario.
    • Cuando se crea una instancia, PizzaContext expondrá las propiedades Pizzas, Toppings y Sauces. Los cambios realizados en las colecciones expuestas por esas propiedades se propagarán a la base de datos.
  3. En Program.cs, reemplace // Add the PizzaContext por el código siguiente:

    builder.Services.AddSqlite<PizzaContext>("Data Source=ContosoPizza.db");
    

    El código anterior:

    • Registra PizzaContext en el sistema de inserción de dependencias de ASP.NET Core.
    • Especifica que PizzaContext usará el proveedor de bases de datos SQLite.
    • Define una cadena de conexión SQLite que apunta a un archivo local, ContosoPizza.db.

    Nota

    SQLite usa archivos de bases de datos locales, por lo que es probable que sea conveniente codificar de forma rígida la cadena de conexión. Para bases de datos de red como PostgreSQL y SQL Server, siempre debe almacenar las cadenas de conexión de forma segura. Para el desarrollo local, use el administrador de secretos. En el caso de las implementaciones de producción, considere la posibilidad de usar un servicio como Azure Key Vault.

  4. En Program.cs, reemplace // Additional using declarations por el código siguiente.

    using ContosoPizza.Data;
    

    Este código resuelve las dependencias del paso anterior.

  5. Guarde todos los cambios. Github Codespaces guarda automáticamente los cambios.

  6. Compile la aplicación en el terminal mediante la ejecución de dotnet build. La compilación debe realizarse correctamente sin advertencias ni errores.

Creación y ejecución de una migración

Después, cree una migración que puede usar para crear la base de datos inicial.

  1. Ejecute el siguiente comando para generar una migración a fin de crear las tablas de base de datos:

    dotnet ef migrations add InitialCreate --context PizzaContext
    

    En el comando anterior:

    • Se le asigna el nombre InitialCreate a la migración.
    • La opción --context especifica el nombre de la clase en el proyecto ContosoPizza, que se deriva de DbContext.

    Aparece un nuevo directorio Migrations en la raíz del proyecto ContosoPizza. El directorio contiene un archivo <timestamp>_InitialCreate.cs en el que se describen los cambios de la base de datos que se van a traducir en un script de cambios de lenguaje de definición de datos (DDL).

  2. Ejecute el comando siguiente para aplicar la migración InitialCreate:

    dotnet ef database update --context PizzaContext
    

    Este comando aplica la migración. ContosoPizza.db no existe, por lo que la migración se crea en el directorio del proyecto.

    Sugerencia

    La herramienta dotnet ef es compatible con todas las plataformas. En Visual Studio en Windows, también puede usar los cmdlets Add-Migration y Update-Database de PowerShell en la ventana Consola del Administrador de paquetes integrada.

Inspección de la base de datos

EF Core creó una base de datos para la aplicación. A continuación, eche un vistazo a la base de datos mediante la extensión SQLite.

  1. En el panel Explorador, haga clic con el botón derecho en el archivo ContosoPizza.db y seleccione Abrir base de datos.

    Captura de pantalla en la que se muestra la opción de menú Abrir base de datos en el panel Explorador de Visual Studio Code.

    Aparece una carpeta Explorador de SQLite en el panel Explorador.

    Captura de pantalla en la que se muestra la carpeta del explorador de SQLite en el panel Explorador.

  2. Seleccione la carpeta Explorador de SQLite para expandir el nodo y todos sus nodos secundarios. Haga clic con el botón derecho en ContosoPizza.db y seleccione Mostrar tabla "sqlite_master" para ver el esquema de la base de datos completo y las restricciones que creó la migración.

    Captura de pantalla en la que se muestra la carpeta del explorador de SQLite en el panel Explorador.

    • Se crearon tablas que corresponden a cada entidad.
    • Los nombres de tabla se tomaron de los nombres de las propiedades DbSet en PizzaContext.
    • Las propiedades denominadas Id se infirieron como campos de clave principal de incremento automático.
    • Las convenciones de nomenclatura de la clave principal y la clave externa de EF Core son PK_<primary key property> y FK_<dependent entity>_<principal entity>_<foreign key property>, respectivamente. Los marcadores de posición <dependent entity> y <principal entity> se corresponden con los nombres de clase de la entidad.

    Nota:

    Al igual que ASP.NET Core MVC, EF Core usa un enfoque de convención sobre configuración. Las convenciones de EF Core acortan el tiempo de desarrollo al deducir la intención del desarrollador. Por ejemplo, una propiedad denominada Id o <entity name>Id se infiere para ser la clave principal de la tabla generada. Si decide no adoptar la convención de nomenclatura, la propiedad debe anotarse con el atributo [Key] o configurado como clave en el método OnModelCreating de DbContext.

Cambio del modelo y actualización del esquema de base de datos

El administrador de Contoso Pizza le proporciona algunos requisitos nuevos, por lo que debe cambiar los modelos de entidad. En los pasos siguientes, va a modificar los modelos mediante atributos de asignación (a veces denominados anotaciones de datos).

  1. En Models\Pizza.cs, realice los cambios siguientes:

    1. Agregue una directiva using para System.ComponentModel.DataAnnotations.
    2. Agregue un atributo [Required] antes de la propiedad Name para marcar la propiedad según sea necesario.
    3. Agregue un atributo [MaxLength(100)] antes de la propiedad Name para especificar una longitud de cadena máxima de 100.
    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Pizza
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public Sauce? Sauce { get; set; }
    
        public ICollection<Topping>? Toppings { get; set; }
    }
    
  2. En Models\Sauce.cs, realice los cambios siguientes:

    1. Agregue una directiva using para System.ComponentModel.DataAnnotations.
    2. Agregue un atributo [Required] antes de la propiedad Name para marcar la propiedad según sea necesario.
    3. Agregue un atributo [MaxLength(100)] antes de la propiedad Name para especificar una longitud de cadena máxima de 100.
    4. Agregue una propiedad bool llamada IsVegan.
    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Sauce
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public bool IsVegan { get; set; }
    }
    
  3. En Models\Topping.cs, realice los cambios siguientes:

    1. Agregue directivas using para System.ComponentModel.DataAnnotations y System.Text.Json.Serialization.
    2. Agregue un atributo [Required] antes de la propiedad Name para marcar la propiedad según sea necesario.
    3. Agregue un atributo [MaxLength(100)] antes de la propiedad Name para especificar una longitud de cadena máxima de 100.
    4. Agregue una propiedad decimal llamada Calories inmediatamente después de la propiedad Name.
    5. Agregue una propiedad Pizzas de tipo ICollection<Pizza>? para hacer que Pizza-Topping sea una relación de varios a varios.
    6. Agregue el atributo [JsonIgnore] a la propiedad Pizzas.

    Importante

    Estos pasos evitan que las entidades Topping incluyan la propiedad Pizzas cuando el código de la API web serializa la respuesta a JSON. Sin este cambio, una colección serializada de ingredientes incluiría una colección de cada pizza que usa el ingrediente. Cada pizza de esa colección contendría una colección de ingredientes, y cada uno de ellos, a su vez, contendría una colección de pizzas. Este tipo de bucle infinito se denomina referencia circular y no se puede serializar.

    using System.ComponentModel.DataAnnotations;
    using System.Text.Json.Serialization;
    
    namespace ContosoPizza.Models;
    
    public class Topping
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public decimal Calories { get; set; }
    
        [JsonIgnore]
        public ICollection<Pizza>? Pizzas { get; set; }
    }
    
  4. Guarde todos los cambios y ejecute dotnet build.

  5. Ejecute el siguiente comando para generar una migración a fin de crear las tablas de base de datos:

    dotnet ef migrations add ModelRevisions --context PizzaContext
    

    Se crea una migración llamada ModelRevisions.

    Nota

    Aparece este mensaje: Se ha aplicado scaffolding a una operación que puede provocar la pérdida de datos. Revise la migración para ver la precisión. Este mensaje aparece porque cambió la relación de Pizza a Topping (antes era de uno a varios y ahora es de varios a varios), lo que requiere que se elimine una columna de clave externa existente. Dado que aún no tiene ningún dato en la base de datos, este cambio no es problemático. Pero, por lo general, es una buena idea comprobar la migración generada cuando aparece esta advertencia para asegurarse de que la migración no elimina ni trunca datos.

  6. Ejecute el comando siguiente para aplicar la migración ModelRevisions:

    dotnet ef database update --context PizzaContext
    
  7. En la barra de título de la carpeta Explorador de SQLite, seleccione el botón Actualizar la base de datos.

    Captura de pantalla en la que se muestra el botón Actualizar base de datos en la barra de título del explorador de SQLite.

  8. En la carpeta Explorador de SQLite, haga clic con el botón derecho en ContosoPizza.db. Seleccione Mostrar tabla "sqlite_master" para ver el esquema completo de la base de datos y las restricciones.

    Importante

    La extensión SQLite vuelve a usar las pestañas abiertas de SQLite.

    • Se creó una tabla de combinación PizzaTopping para representar la relación de varios a varios entre pizzas e ingredientes.
    • Se han agregado campos nuevos a Toppings y Sauces.
      • Calories se define como una columna text porque SQLite no tiene un tipo decimal coincidente.
      • De forma similar, IsVegan se define como una columna integer. SQLite no define un tipo bool.
      • En ambos casos, EF Core administra la traducción.
    • La columna Name de cada tabla se ha marcado como not null, pero SQLite no tiene ninguna restricción MaxLength.

    Sugerencia

    Los proveedores de bases de datos de EF Core asignan un esquema de modelo a las características de una base de datos específica. Aunque SQLite no implementa una restricción correspondiente para MaxLength, otras bases de datos como SQL Server y PostgreSQL sí lo hacen.

  9. En la carpeta Explorador de SQLite, haga clic con el botón derecho en la tabla _EFMigrationsHistory y seleccione Mostrar tabla. La tabla contiene una lista de todas las migraciones aplicadas a la base de datos. Como ha ejecutado dos migraciones, hay dos entradas: una para la migración InitialCreate y otra para ModelRevisions.

Nota

En este ejercicio se usan atributos de asignación (anotaciones de datos) para asignar modelos a la base de datos. Como alternativa a los atributos de asignación, puede usar la API fluida ModelBuilder para configurar modelos. Ambos enfoques son válidos, pero algunos desarrolladores prefieren un enfoque sobre el otro.

Ha usado migraciones para definir y actualizar un esquema de base de datos. En la siguiente unidad, finalizará los métodos de PizzaService que manipulan los datos.

Comprobación de conocimientos

1.

En una clase de entidad, ¿cuál es la convención de nomenclatura de propiedad de una clave principal?