Esercizio - Configurare una migrazione

Completato

In questa unità vengono create classi di entità C# di cui viene eseguito il mapping a tabelle in un database SQLite locale. La funzionalità delle migrazioni di EF Core produce tabelle da tali entità.

Le migrazioni offrono uno strumento per aggiornare in modo incrementale lo schema del database.

Ottenere i file di progetto

Per iniziare, ottenere i file di progetto. Sono disponibili alcune opzioni per ottenere i file di progetto:

  • Usare GitHub Codespaces
  • Clonare il repository GitHub

Se è installato un runtime di contenitori compatibile, è anche possibile usare l'estensione Contenitori di sviluppo per aprire il repository in un contenitore con gli strumenti preinstallati.

Usare GitHub Codespaces

Un codespace è un ambiente di sviluppo integrato (IDE) ospitato nel cloud. Se si usa GitHub Codespaces, passare al repository nel browser. Selezionare Codice e quindi creare un nuovo spazio di codice nel main ramo.

Clonare il repository GitHub

Se non si usa GitHub Codespaces, è possibile clonare il repository GitHub del progetto e quindi aprire i file come cartella in Visual Studio Code.

  1. Aprire un terminale dei comandi e quindi clonare il progetto da GitHub usando il prompt dei comandi:

    git clone https://github.com/MicrosoftDocs/mslearn-persist-data-ef-core
    
  2. Passare alla cartella mslearn-persist-data-ef-core e quindi aprire il progetto in Visual Studio Code:

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

Esaminare il codice

Ora che sono disponibili i file di progetto da usare, vediamo cosa c'è nel progetto ed esaminiamo il codice.

  • Il progetto, API Web ASP.NET Core, si trova nella directory ContosoPizza. I percorsi di file a cui si fa riferimento in questo modulo sono relativi alla directory ContosoPizza.
  • Services/PizzaService.cs è una classe di servizio che definisce i metodi CRUD (Create, Read, Update e Delete). Tutti i metodi attualmente generano System.NotImplementedException.
  • In Program.csPizzaService viene registrato con il sistema di inserimento delle dipendenze di ASP.NET Core.
  • Controllers/PizzaController.cs è un valore per ApiController che espone un endpoint per i verbi HTTP POST, GET, PUT e DELETE. Questi verbi chiamano i metodi CRUD corrispondenti in PizzaService. PizzaService viene inserito nel costruttore di PizzaController.
  • La cartella Models contiene i modelli usati da PizzaService e PizzaController.
  • I modelli di entità, Pizza.cs, Topping.cs e Sauce.cs hanno le relazioni seguenti:
    • Una pizza può avere uno o più condimenti.
    • Un condimento può essere usato su una o più pizze.
    • Una pizza può avere una salsa, ma una salsa può essere usata su molte pizze.

Creare l'app

Per compilare l'app in Visual Studio Code:

  1. Nel riquadro Explorer fare clic con il pulsante destro del mouse sulla directory ContosoPizza e scegliere Apri nel terminale integrato.

    Verrà aperto un riquadro del terminale con ambito nella directory ContosoPizza.

  2. Compilare l'app con il comando seguente:

    dotnet build
    

    Dalla compilazione del codice non devono risultare avvisi o errori.

Aggiungere i pacchetti NuGet e gli strumenti EF Core

Il motore del database con cui si lavora in questo modulo è SQLite. SQLite è un motore di database leggero basato su file. È una buona scelta per lo sviluppo e il test ed è anche una scelta ottimale per le distribuzioni di produzione su piccola scala.

Nota

Come accennato in precedenza, i provider di database in EF Core sono collegabili. SQLite è una buona scelta per questo modulo perché è leggero e multipiattaforma. È possibile usare lo stesso codice per usare motori di database diversi, ad esempio SQL Server e PostgreSQL. È anche possibile usare più motori di database nella stessa app.

Prima di iniziare, è necessario aggiungere i pacchetti obbligatori:

  1. Nel riquadro del terminale eseguire il comando seguente:

    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    

    Questo comando aggiunge il pacchetto NuGet che contiene il provider di database SQLite di EF Core e tutte le relative dipendenze, inclusi i servizi EF Core comuni.

  2. Eseguire quindi questo comando:

    dotnet add package Microsoft.EntityFrameworkCore.Design
    

    Questo comando aggiunge i pacchetti obbligatori per gli strumenti EF Core.

  3. Per completare, eseguire questo comando:

    dotnet tool install --global dotnet-ef
    

    Questo comando installa dotnet ef, lo strumento che si usa per creare migrazioni e scaffolding.

    Suggerimento

    Se dotnet ef è già installato, è possibile aggiornarlo con dotnet tool update --global dotnet-ef.

Collegare modelli e DbContext

Ora si aggiunge e si configura un'implementazione DbContext. DbContext è un gateway tramite il quale è possibile interagire con il database.

  1. Fare clic con il pulsante destro del mouse sulla directory ContosoPizza e aggiungere una nuova cartella denominata Data.

  2. Nella directory Dati creare un nuovo file denominato PizzaContext.cs. Aggiungere il codice seguente al file vuoto:

    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>();
    }
    

    Nel codice precedente:

    • Il costruttore accetta un parametro di tipo DbContextOptions<PizzaContext>. Ciò consente al codice esterno di passare alla configurazione, pertanto è possibile condividere lo stesso DbContext tra codice di test e di produzione e anche usarlo con provider diversi.
    • Le proprietà DbSet<T> corrispondono alle tabelle da creare nel database.
    • I nomi delle tabelle corrispondono ai nomi di proprietà DbSet<T> nella classe PizzaContext. Se necessario, è possibile eseguire l'override di questo comportamento.
    • Quando viene creata un'istanza, PizzaContext espone le proprietà Pizzas, Toppings e Sauces. Le modifiche apportate alle raccolte esposte da tali proprietà vengono propagate al database.
  3. In Program.cs sostituire // Add the PizzaContext con il codice seguente:

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

    Il codice precedente:

    • Registra PizzaContext con il sistema di inserimento delle dipendenze di ASP.NET Core.
    • Specifica che PizzaContext usa il provider di database SQLite.
    • Definisce una stringa di connessione SQLite che punta a un file locale, ContosoPizza.db.

    Nota

    Per SQLite, che usa i file di database locali, è corretto impostare la stringa di connessione hardcoded. Tuttavia, per i database di rete come PostgreSQL o SQL Server, è consigliabile archiviare sempre le stringhe di connessione in modo sicuro. Per lo sviluppo locale, usare Secret Manager. Per le distribuzioni di produzione, prendere in considerazione un servizio come Azure Key Vault.

  4. In Program.cs sostituire anche // Additional using declarations con il codice seguente.

    using ContosoPizza.Data;
    

    Questo codice risolve le dipendenze del passaggio precedente.

  5. Salvare tutte le modifiche. GitHub Codespaces salva automaticamente le modifiche.

  6. Compilare l'app nel terminale eseguendo dotnet build. La compilazione verrà eseguita correttamente senza visualizzare avvisi.

Creare ed eseguire una migrazione

Creare quindi una migrazione che è possibile usare per creare il database iniziale.

  1. Nel terminale con ambito la cartella del progetto ContosoPizza eseguire il comando seguente per generare una migrazione per la creazione delle tabelle di database:

    dotnet ef migrations add InitialCreate --context PizzaContext
    

    Nel comando precedente:

    • La migrazione è denominata : InitialCreate.
    • L'opzione --context specifica il nome della classe nel progetto ContosoPizza che deriva da DbContext.

    Verrà visualizzata una nuova directory Migrations alla radice del progetto ContosoPizza. La directory contiene un file <timestamp>_InitialCreate.csche descrive le modifiche del database da convertire in uno script delle modifiche DDL (Data Definition Language).

  2. Eseguire il comando seguente per applicare la migrazione InitialCreate:

    dotnet ef database update --context PizzaContext
    

    Questo comando applica la migrazione. Poiché ContosoPizza.db non esiste, questo comando crea la migrazione nella directory del progetto.

    Suggerimento

    Tutte le piattaforme supportano lo strumento dotnet ef. In Visual Studio in Windows è possibile anche usare i cmdlet PowerShell Add-Migration e Update-Database nella finestra integrata Console di Gestione pacchetti.

Esaminare il database

EF Core ha creato un database per l'app. Successivamente, esaminare il database usando l'estensione SQLite.

  1. Nel riquadro Esplora risorse fare clic con il pulsante destro del mouse sul file ContosoPizza.db e scegliere Apri database.

    Screenshot che mostra il menu opzioni di Open Database nel pannello Explorer di Visual Studio Code.

    Viene visualizzata una cartella SQLite Explorer nel riquadro Explorer.

    Screenshot che mostra la cartella SQLite Explorer nel riquadro Explorer.

  2. Selezionare la cartella SQLite Explorer per espandere il nodo e tutti i relativi nodi figlio. Fare clic con il pulsante destro del mouse su ContosoPizza.db e selezionare Mostra tabella 'sqlite_master' per visualizzare lo schema e i vincoli completi del database creati dalla migrazione.

    Screenshot che mostra la cartella espansa SQLite Explorer nel pannello Explorer.

    • Le tabelle corrispondenti a ogni entità sono state create.
    • I nomi delle tabelle sono stati ricavati dai nomi delle proprietà DbSet nell'oggetto PizzaContext.
    • Le proprietà denominate Id sono state dedotte come campi chiave primaria a incremento automatico.
    • Le convenzioni di denominazione per i vincoli di chiave primaria e di chiave esterna in EF Core sono rispettivamente PK_<primary key property> e FK_<dependent entity>_<principal entity>_<foreign key property>. I segnaposto <dependent entity> e <principal entity> corrispondono ai nomi di classe di entità.

    Nota

    Come ASP.NET Core MVC, EF Core usa una convenzione sull'approccio di configurazione. Le convenzioni di EF Core riducono i tempi di sviluppo deducendo le finalità dello sviluppatore. Ef Core, ad esempio, deduce una proprietà denominata Id o <entity name>Id come chiave primaria della tabella generata. Se si sceglie di non adottare la convenzione di denominazione, la proprietà deve essere annotata con l'attributo [Key] o configurata come chiave nel metodo OnModelCreating di DbContext.

Modificare il modello e aggiornare lo schema del database

Il responsabile di Contoso Pizza ha specificato alcuni nuovi requisiti che implicano la modifica dei modelli di entità. Nei passaggi seguenti si modificheranno i modelli usando attributi di mapping, talvolta denominati anche annotazioni dei dati.

  1. In Models\Pizza.cs apportare le modifiche seguenti:

    1. Aggiungere una direttiva using per System.ComponentModel.DataAnnotations.
    2. Aggiungere un attributo [Required] prima della proprietà Name per contrassegnare la proprietà come obbligatoria.
    3. Aggiungere un attributo [MaxLength(100)] prima della proprietà Name per specificare una lunghezza massima della stringa pari a 100.

    Il file Pizza.cs aggiornato sarà simile al seguente codice:

    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. In Models\Sauce.cs apportare le modifiche seguenti:

    1. Aggiungere una direttiva using per System.ComponentModel.DataAnnotations.
    2. Aggiungere un attributo [Required] prima della proprietà Name per contrassegnare la proprietà come obbligatoria.
    3. Aggiungere un attributo [MaxLength(100)] prima della proprietà Name per specificare una lunghezza massima della stringa pari a 100.
    4. Aggiungere una proprietà bool denominata IsVegan.

    Il file Salsa.cs aggiornato sarà simile al seguente codice:

    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. In Models\Toppings.cs apportare le modifiche seguenti:

    1. Aggiungere delle direttive using per System.ComponentModel.DataAnnotations e System.Text.Json.Serialization.

    2. Aggiungere un attributo [Required] prima della proprietà Name per contrassegnare la proprietà come obbligatoria.

    3. Aggiungere un attributo [MaxLength(100)] prima della proprietà Name per specificare una lunghezza massima della stringa pari a 100.

    4. Aggiungere una proprietà decimal denominata Calories immediatamente dopo la proprietà Name.

    5. Aggiungere una Pizzas proprietà di tipo ICollection<Pizza>?. Questa modifica crea una relazione molti-a-molti tra Pizza-Topping.

    6. Aggiungere l'attributo [JsonIgnore] alla proprietà Pizzas.

      Importante

      Questo attributo impedisce alle entità Topping di includere la proprietà Pizzas quando il codice dell'API Web serializza la risposta a JSON. Senza questo passaggio, una raccolta serializzata di condimenti includerebbe una raccolta di ogni pizza che usa il condimento. Ogni pizza in tale raccolta conterrebbe una raccolta di condimenti, che a loro volta conterrebbero ognuno una raccolta di pizze. Questo tipo di ciclo infinito viene chiamato riferimento circolare e non può essere serializzato.

    Il file Condimento.cs aggiornato sarà simile al seguente:

    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. Salvare tutte le modifiche ed eseguire dotnet build.

  5. Eseguire il comando seguente per generare una migrazione per la creazione delle tabelle del database:

    dotnet ef migrations add ModelRevisions --context PizzaContext
    

    Questo comando crea una migrazione denominata: ModelRevisions.

    Nota

    Verrà visualizzato il messaggio seguente: È stato eseguito lo scaffolding di un'operazione che può causare la perdita di dati. Esaminare la migrazione per verificare l'accuratezza. Ciò è dovuto al fatto che la relazione è stata modificata da Pizza a Topping da uno-a-molti a molti-a-molti e ciò richiede la rimozione di una colonna chiave esterna esistente. Poiché non sono ancora presenti dati nel database, questa modifica non è problematica. Tuttavia, è in generale consigliabile controllare la migrazione generata quando viene visualizzato questo avviso per assicurarsi che nessun dato venga eliminato o troncato dalla migrazione.

  6. Eseguire il comando seguente per applicare la migrazione ModelRevisions:

    dotnet ef database update --context PizzaContext
    
  7. Nella barra del titolo del riquadro SQLite Explorer selezionare il pulsante Refresh Databases (Aggiorna database).

    Screenshot che mostra il pulsante Refresh Databases nella barra del tutolo di SQLite Explorer.

  8. Nel riquadro SQLite Explorer fare clic con il pulsante destro del mouse su ContosoPizza.db. Selezionare Show Table 'sqlite_master' (Mostra tabella sqlite_master) per visualizzare lo schema completo e i vincoli del database.

    Importante

    L'estensione SQLite userà nuovamente le schede SQLite aperte.

    • È stata creata una tabella join PizzaTopping per rappresentare la relazione molti-a-molti tra pizza e condimenti.
    • Sono stati aggiunti nuovi campi a Toppings e Sauces.
      • Calories è definito come colonna text perché SQLite non ha un tipo decimal corrispondente.
      • Analogamente, IsVegan viene definito come colonna integer. SQLite non definisce un tipo bool.
      • In entrambi i casi EF Core gestisce la traduzione.
    • La colonna Name di ciascuna tabella è stata contrassegnata come not null, ma SQLite non ha un vincolo MaxLength.

    Suggerimento

    I provider di database EF Core mappano uno schema modello alle funzionalità di un database specifico. SQLite non implementa un vincolo corrispondente per MaxLength, a differenza di altri database come SQL Server e PostgreSQL.

  9. Nel riquadro SQLite Explorer fare clic con il pulsante destro del mouse sulla tabella _EFMigrationsHistory e selezionare Show Table (Mostra tabella). La tabella contiene un elenco di tutte le migrazioni applicate al database. Poiché si eseguono due migrazioni, sono disponibili due voci: una per la migrazione InitialCreate e un'altra per ModelRevisions.

Nota

Questo esercizio usa gli attributi di mapping (annotazioni dati) per eseguire il mapping dei modelli al database. In alternativa ai mapping degli attributi, è possibile usare l'API fluente ModelBuilder per configurare i modelli. Entrambi gli approcci sono validi, ma alcuni sviluppatori preferiscono un approccio rispetto all'altro.

Sono state usate le migrazioni per definire e aggiornare uno schema del database. Nell'unità successiva verranno completati i metodi in PizzaService che modificano i dati.

Verificare le conoscenze

1.

In una classe di entità qual è la convenzione di denominazione delle proprietà per una chiave primaria?