Condividi tramite


Parte 4, Razor Pagine con EF Core migrazioni in ASP.NET Core

Di Tom Dykstra, Jon P Smith e Rick Anderson

L'app Web Contoso University illustra come creare Razor app Web Pages usando EF Core e Visual Studio. Per informazioni sulla serie di esercitazioni, vedere la prima esercitazione.

Se si verificano problemi che non è possibile risolvere, scaricare l'app completata e confrontare tale codice con quello creato seguendo questa esercitazione.

Questa esercitazione presenta la EF Core funzionalità delle migrazioni per la gestione delle modifiche del modello di dati.

Quando viene sviluppata una nuova app, il modello di dati cambia spesso. Ogni volta che vengono apportate modifiche al modello, questo non è più sincronizzato con il database. Questa serie di esercitazioni è iniziata con la configurazione di Entity Framework per creare il database se non esiste già. Ogni volta che il modello di dati cambia, è necessario eliminare il database. Alla successiva esecuzione dell'app, la chiamata a EnsureCreated ricrea il database in modo che corrisponda al nuovo modello di dati. Viene quindi eseguita la classe DbInitializer per il seeding del nuovo database.

Questo approccio per mantenere il database sincronizzato con il modello di dati funziona correttamente fino a quando l'app non deve essere distribuita nell'ambiente di produzione. Quando l'app è in esecuzione nell'ambiente di produzione, in genere esegue l'archiviazione di dati che devono essere mantenuti. L'app non può essere avviata con un database di test ogni volta che viene apportata una modifica, come ad esempio l'aggiunta di una colonna. La EF Core funzionalità Migrazioni risolve questo problema consentendo di EF Core aggiornare lo schema del database invece di creare un nuovo database.

Anziché eliminare e ricreare il database quando il modello di dati viene modificato, le migrazioni aggiornano lo schema e mantengono i dati esistenti.

Nota

Limitazioni di SQLite

Questa esercitazione usa la funzionalità migrazioni di Entity Framework Core laddove possibile. Le migrazioni aggiornano lo schema del database in base alle modifiche nel modello di dati. Tuttavia, le migrazioni eseguono solo i tipi di modifiche supportate dal motore di database e le funzionalità di modifica dello schema di SQLite sono limitate. Ad esempio l'aggiunta di una colonna è supportata, ma la rimozione di una colonna non è supportata. Se si crea una migrazione per rimuovere una colonna, il comando ef migrations add ha esito positivo, ma il comando ef database update ha esito negativo.

La soluzione per ovviare alle limitazioni di SQLite consiste nello scrivere manualmente il codice delle migrazioni per eseguire una ricompilazione della tabella in caso di modifiche nella tabella. Il codice viene inserito nei Up metodi e Down per una migrazione e implica:

  • Creazione di una nuova tabella.
  • Copia dei dati dalla vecchia tabella alla nuova tabella.
  • Eliminazione della tabella precedente.
  • Ridenominazione della nuova tabella.

La scrittura di codice specifico del database di questo tipo esula dall'ambito di questa esercitazione. Al contrario, in questa esercitazione viene eliminato e ricreato il database ogni volta che un tentativo di applicare una migrazione ha esito negativo. Per ulteriori informazioni, vedi le seguenti risorse:

Eliminare il database

Usare Esplora oggetti di SQL Server (SSOX) per eliminare il database oppure eseguire il comando seguente nella console di Gestione pacchetti:

Drop-Database

Creare una migrazione iniziale

Eseguire i comandi seguenti nella console di Gestione pacchetti:

Add-Migration InitialCreate
Update-Database

Rimuovere EnsureCreated

Questa serie di esercitazioni è iniziata usando EnsureCreated. EnsureCreated non crea una tabella di cronologia delle migrazioni e pertanto non può essere usato con le migrazioni. È progettato per il test o per la creazione rapida di prototipi in cui il database viene eliminato e ricreato frequentemente.

Da questo punto in poi, le esercitazioni useranno le migrazioni.

In Program.cseliminare la riga seguente:

context.Database.EnsureCreated();

Eseguire l'app e verificare che il database sia sottoposto a seeding.

Metodi Up e Down

Codice EF Coremigrations add generato dal comando per creare il database. Questo codice di migrazione si trova nel Migrations\<timestamp>_InitialCreate.cs file . Il metodo Up della classe InitialCreate crea le tabelle di database che corrispondono al set di entità del modello di dati. Il metodo Down le elimina, come illustrato nell'esempio seguente:

using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;

namespace ContosoUniversity.Migrations
{
    public partial class InitialCreate : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Course",
                columns: table => new
                {
                    CourseID = table.Column<int>(nullable: false),
                    Title = table.Column<string>(nullable: true),
                    Credits = table.Column<int>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Course", x => x.CourseID);
                });

            migrationBuilder.CreateTable(
                name: "Student",
                columns: table => new
                {
                    ID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    LastName = table.Column<string>(nullable: true),
                    FirstMidName = table.Column<string>(nullable: true),
                    EnrollmentDate = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Student", x => x.ID);
                });

            migrationBuilder.CreateTable(
                name: "Enrollment",
                columns: table => new
                {
                    EnrollmentID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CourseID = table.Column<int>(nullable: false),
                    StudentID = table.Column<int>(nullable: false),
                    Grade = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
                    table.ForeignKey(
                        name: "FK_Enrollment_Course_CourseID",
                        column: x => x.CourseID,
                        principalTable: "Course",
                        principalColumn: "CourseID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_Enrollment_Student_StudentID",
                        column: x => x.StudentID,
                        principalTable: "Student",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_Enrollment_CourseID",
                table: "Enrollment",
                column: "CourseID");

            migrationBuilder.CreateIndex(
                name: "IX_Enrollment_StudentID",
                table: "Enrollment",
                column: "StudentID");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Enrollment");

            migrationBuilder.DropTable(
                name: "Course");

            migrationBuilder.DropTable(
                name: "Student");
        }
    }
}

Il codice precedente è per la migrazione iniziale. Il codice:

  • È stato generato dal comando migrations add InitialCreate.
  • Viene eseguito dal comando database update.
  • Crea un database per il modello di dati specificato dalla classe del contesto di database.

Il parametro del nome della migrazione (InitialCreate nell'esempio) viene usato per il nome file. Il nome della migrazione può essere qualsiasi nome di file valido. È consigliabile scegliere una parola o una frase che riepiloghi cosa viene fatto nella migrazione. Ad esempio, una migrazione che ha aggiunto una tabella di reparto potrebbe essere denominata "AddDepartmentTable".

Tabella di cronologia delle migrazioni

  • Usare lo strumento SSOX o SQLite per esaminare il database.
  • Si noti l'aggiunta di una tabella __EFMigrationsHistory. La tabella __EFMigrationsHistory tiene traccia di quali migrazioni sono state applicate al database.
  • Visualizzare i dati nella tabella __EFMigrationsHistory. Viene visualizzata una riga per la prima migrazione.

Snapshot del modello di dati

Le migrazioni creano uno snapshot del modello di dati corrente in Migrations/SchoolContextModelSnapshot.cs. Quando si aggiunge una migrazione, Entity Framework determina le modifiche apportate confrontando il modello di dati corrente con il file di snapshot.

Poiché il file di snapshot tiene traccia dello stato del modello di dati, non è possibile eliminare una migrazione eliminando il <timestamp>_<migrationname>.cs file. Per eseguire il backup della migrazione più recente, usare il migrations remove comando . migrations remove elimina la migrazione e garantisce che lo snapshot venga reimpostato correttamente. Per altre informazioni, vedere dotnet ef migrations remove.

Vedere Reimpostazione di tutte le migrazioni per rimuovere tutte le migrazioni .

Applicazione delle migrazioni nell'ambiente di produzione

È consigliabile fare in modo che le app nell'ambiente di produzione non chiamino Database.Migrate all'avvio dell'applicazione. Migrate non deve essere chiamato da un'app distribuita in una server farm. Se l'app viene distribuita in più istanze del server, è difficile assicurarsi che gli aggiornamenti dello schema di database non vengano eseguiti da più server o che non siano in conflitto con l'accesso in lettura/scrittura.

La migrazione del database deve essere eseguita come parte della distribuzione e in modo controllato. Gli approcci alla migrazione di database in ambiente di produzione includono:

  • Uso delle migrazioni per creare script SQL e uso degli script SQL nella distribuzione.
  • Esecuzione di dotnet ef database update da un ambiente controllato.

Risoluzione dei problemi

Se l'app usa SQL Server Local DB e visualizza l'eccezione seguente:

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

È possibile che la soluzione esegua dotnet ef database update al prompt dei comandi.

Risorse aggiuntive

Passaggi successivi

L'esercitazione successiva compila il modello di dati, aggiungendo le proprietà delle entità e le nuove entità.

In questa esercitazione viene usata la EF Core funzionalità delle migrazioni per la gestione delle modifiche del modello di dati.

Se si verificano problemi che non si è in grado di risolvere, scaricare l'app completa.

Quando viene sviluppata una nuova app, il modello di dati cambia spesso. Ogni volta che vengono apportate modifiche al modello, questo non è più sincronizzato con il database. L'esercitazione è iniziata con la configurazione di Entity Framework per creare il database se non esiste già. Ogni volta che il modello di dati cambia:

  • Il database viene eliminato.
  • EF ne crea uno nuovo che corrisponde al modello.
  • L'app esegue il seeding del database con dati di test.

Questo approccio per mantenere il database sincronizzato con il modello di dati funziona correttamente fino a quando l'app non deve essere distribuita nell'ambiente di produzione. Quando l'app è in esecuzione nell'ambiente di produzione, in genere esegue l'archiviazione di dati che devono essere mantenuti. L'app non può essere avviata con un database di test ogni volta che viene apportata una modifica, come ad esempio l'aggiunta di una colonna. La EF Core funzionalità Migrazioni risolve questo problema consentendo di EF Core aggiornare lo schema del database invece di creare un nuovo database.

Anziché eliminare e ricreare il database quando il modello di dati viene modificato, le migrazioni aggiornano lo schema e mantengono i dati esistenti.

Eliminare il database

Usare Esplora oggetti di SQL Server o il comando database drop:

Nella console di Gestione pacchetti eseguire il comando seguente:

Drop-Database

Eseguire Get-Help about_EntityFrameworkCore dalla console di Gestione pacchetti per ottenere informazioni.

Creare una migrazione iniziale e aggiornare il database

Compilare il progetto e creare la prima migrazione.

Add-Migration InitialCreate
Update-Database

Esaminare i metodi Up e Down

Codice EF Coremigrations add generato dal comando per creare il database. Questo codice di migrazione si trova nel Migrations\<timestamp>_InitialCreate.cs file . Il metodo Up della classe InitialCreate crea le tabelle di database che corrispondono al set di entità del modello di dati. Il metodo Down le elimina, come illustrato nell'esempio seguente:

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Course",
            columns: table => new
            {
                CourseID = table.Column<int>(nullable: false),
                Title = table.Column<string>(nullable: true),
                Credits = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Course", x => x.CourseID);
            });

        migrationBuilder.CreateTable(
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Enrollment");

        migrationBuilder.DropTable(
            name: "Course");

        migrationBuilder.DropTable(
            name: "Student");
    }
}

Le migrazioni chiamano il metodo Up per implementare le modifiche al modello di dati per una migrazione. Quando viene immesso un comando per eseguire il rollback dell'aggiornamento, le migrazioni chiamano il Down metodo .

Il codice precedente è per la migrazione iniziale. Tale codice è stato creato quando è stato eseguito il comando migrations add InitialCreate. Il parametro del nome della migrazione, nell'esempio "InitialCreate", viene usato per il nome del file. Il nome della migrazione può essere qualsiasi nome di file valido. È consigliabile scegliere una parola o una frase che riepiloghi cosa viene fatto nella migrazione. Ad esempio, una migrazione che ha aggiunto una tabella di reparto potrebbe essere denominata "AddDepartmentTable".

Se la migrazione iniziale viene creata e il database esiste:

  • Viene generato il codice di creazione del database.
  • Non è necessario che venga eseguito il codice di creazione del database perché il database corrisponde già al modello di dati. Se viene eseguito il codice di creazione del database, non apporta alcuna modifica perché il database corrisponde già al modello di dati.

Quando l'app viene distribuita in un nuovo ambiente, è necessario eseguire il codice di creazione del database per creare il database.

Il database infatti non esiste in quanto è stato precedentemente eliminato, pertanto la migrazione crea il nuovo database.

Snapshot del modello di dati

Le migrazioni creano uno snapshot dello schema del database corrente in Migrations/SchoolContextModelSnapshot.cs. Quando si aggiunge una migrazione, EF determina le modifiche apportate confrontando il modello di dati con il file dello snapshot.

Per eliminare una migrazione, usare il comando seguente:

Remove-Migration

Il comando di rimozione migrazioni elimina la migrazione e garantisce che lo snapshot venga reimpostato correttamente.

Rimuovere EnsureCreated e testare l'app

Per lo sviluppo iniziale è stato usato il comando EnsureCreated. In questa esercitazione vengono usate le migrazioni. EnsureCreated presenta le limitazioni seguenti:

  • Ignora le migrazioni e crea il database e lo schema.
  • Non crea una tabella delle migrazioni.
  • Non può essere usato con le migrazioni.
  • È progettato per il test o per la creazione rapida di prototipi in cui il database viene eliminato e ricreato frequentemente.

Rimuovere EnsureCreated:

context.Database.EnsureCreated();

Eseguire l'app e verificare che il database venga inizializzato.

Esaminare il database

Per controllare il database, usare Esplora oggetti di SQL Server. Si noti l'aggiunta di una tabella __EFMigrationsHistory. La tabella __EFMigrationsHistory tiene traccia di quali migrazioni sono state applicate al database. Visualizzare i dati nella tabella __EFMigrationsHistory: viene visualizzata una riga per la prima migrazione. Nell'ultimo log nell'esempio di output della CLI precedente viene visualizzata l'istruzione INSERT che crea tale riga.

Eseguire l'app e verificare che tutto funzioni.

Applicazione delle migrazioni nell'ambiente di produzione

È consigliabile fare in modo che le app nell'ambiente di produzione non chiamino Database.Migrate all'avvio dell'applicazione. L'elemento Migrate non deve essere chiamato da un'app nella server farm. Ad esempio, se l'app è stata distribuita in un cloud con scale-out, ovvero se sono in esecuzione più istanze dell'app.

La migrazione del database deve essere eseguita come parte della distribuzione e in modo controllato. Gli approcci alla migrazione di database in ambiente di produzione includono:

  • Uso delle migrazioni per creare script SQL e uso degli script SQL nella distribuzione.
  • Esecuzione di dotnet ef database update da un ambiente controllato.

EF Core usa la __MigrationsHistory tabella per verificare se è necessario eseguire migrazioni. Se il database è aggiornato, non viene eseguita alcuna migrazione.

Risoluzione dei problemi

Scaricare l'app completa.

L'app genera l'eccezione seguente:

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

Soluzione: eseguire dotnet ef database update

Risorse aggiuntive