Esercizio - Configurare una migrazione
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.
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
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.cs
PizzaService
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 inPizzaService
.PizzaService
viene inserito nel costruttore diPizzaController
. - La cartella Models contiene i modelli usati da
PizzaService
ePizzaController
. - 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:
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.
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:
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.
Eseguire quindi questo comando:
dotnet add package Microsoft.EntityFrameworkCore.Design
Questo comando aggiunge i pacchetti obbligatori per gli strumenti EF Core.
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 condotnet 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.
Fare clic con il pulsante destro del mouse sulla directory ContosoPizza e aggiungere una nuova cartella denominata Data.
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 stessoDbContext
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 classePizzaContext
. Se necessario, è possibile eseguire l'override di questo comportamento. - Quando viene creata un'istanza,
PizzaContext
espone le proprietàPizzas
,Toppings
eSauces
. Le modifiche apportate alle raccolte esposte da tali proprietà vengono propagate al database.
- Il costruttore accetta un parametro di tipo
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.
- Registra
In Program.cs sostituire anche
// Additional using declarations
con il codice seguente.using ContosoPizza.Data;
Questo codice risolve le dipendenze del passaggio precedente.
Salvare tutte le modifiche. GitHub Codespaces salva automaticamente le modifiche.
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.
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 daDbContext
.
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).
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 PowerShellAdd-Migration
eUpdate-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.
Nel riquadro Esplora risorse fare clic con il pulsante destro del mouse sul file ContosoPizza.db e scegliere Apri database.
Viene visualizzata una cartella SQLite Explorer nel riquadro Explorer.
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.
- Le tabelle corrispondenti a ogni entità sono state create.
- I nomi delle tabelle sono stati ricavati dai nomi delle proprietà
DbSet
nell'oggettoPizzaContext
. - 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>
eFK_<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 metodoOnModelCreating
diDbContext
.
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.
In Models\Pizza.cs apportare le modifiche seguenti:
- Aggiungere una direttiva
using
perSystem.ComponentModel.DataAnnotations
. - Aggiungere un attributo
[Required]
prima della proprietàName
per contrassegnare la proprietà come obbligatoria. - 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; } }
- Aggiungere una direttiva
In Models\Sauce.cs apportare le modifiche seguenti:
- Aggiungere una direttiva
using
perSystem.ComponentModel.DataAnnotations
. - Aggiungere un attributo
[Required]
prima della proprietàName
per contrassegnare la proprietà come obbligatoria. - Aggiungere un attributo
[MaxLength(100)]
prima della proprietàName
per specificare una lunghezza massima della stringa pari a 100. - Aggiungere una proprietà
bool
denominataIsVegan
.
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; } }
- Aggiungere una direttiva
In Models\Toppings.cs apportare le modifiche seguenti:
Aggiungere delle direttive
using
perSystem.ComponentModel.DataAnnotations
eSystem.Text.Json.Serialization
.Aggiungere un attributo
[Required]
prima della proprietàName
per contrassegnare la proprietà come obbligatoria.Aggiungere un attributo
[MaxLength(100)]
prima della proprietàName
per specificare una lunghezza massima della stringa pari a 100.Aggiungere una proprietà
decimal
denominataCalories
immediatamente dopo la proprietàName
.Aggiungere una
Pizzas
proprietà di tipoICollection<Pizza>?
. Questa modifica crea una relazione molti-a-molti traPizza
-Topping
.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; } }
Salvare tutte le modifiche ed eseguire
dotnet build
.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
aTopping
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.Eseguire il comando seguente per applicare la migrazione ModelRevisions:
dotnet ef database update --context PizzaContext
Nella barra del titolo del riquadro SQLite Explorer selezionare il pulsante Refresh Databases (Aggiorna database).
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
eSauces
.Calories
è definito come colonnatext
perché SQLite non ha un tipodecimal
corrispondente.- Analogamente,
IsVegan
viene definito come colonnainteger
. SQLite non definisce un tipobool
. - In entrambi i casi EF Core gestisce la traduzione.
- La colonna
Name
di ciascuna tabella è stata contrassegnata comenot null
, ma SQLite non ha un vincoloMaxLength
.
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.- È stata creata una tabella join
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.