Applicazione di migrazioni

Dopo aver aggiunto le migrazioni, è necessario distribuirla e applicarla ai database. Esistono diverse strategie per eseguire questa operazione, con alcune più appropriate per gli ambienti di produzione e altre per il ciclo di vita di sviluppo.

Nota

Indipendentemente dalla strategia di distribuzione, controllare sempre le migrazioni generate e testarle prima di applicarle a un database di produzione. Una migrazione può eliminare una colonna quando la finalità era rinominarla o potrebbe non riuscire per vari motivi quando viene applicata a un database.

Script SQL

Il modo consigliato per distribuire le migrazioni in un database di produzione consiste nel generare script SQL. I vantaggi di questa strategia includono quanto segue:

  • Gli script SQL possono essere esaminati per verificare l'accuratezza; questo aspetto è importante perché l'applicazione delle modifiche dello schema ai database di produzione è un'operazione potenzialmente pericolosa che potrebbe comportare la perdita di dati.
  • In alcuni casi, gli script possono essere ottimizzati per soddisfare le esigenze specifiche di un database di produzione.
  • Gli script SQL possono essere usati insieme a una tecnologia di distribuzione e possono anche essere generati come parte del processo di integrazione continua.
  • Gli script SQL possono essere forniti a un amministratore di database e possono essere gestiti e archiviati separatamente.

Utilizzo di base

Di seguito viene generato uno script SQL da un database vuoto alla migrazione più recente:

dotnet ef migrations script

Con from (destinazione implicita)

Di seguito viene generato uno script SQL dalla migrazione specificata alla migrazione più recente.

dotnet ef migrations script AddNewTables

Con from e to

Di seguito viene generato uno script SQL dalla migrazione specificata from alla migrazione specificata to .

dotnet ef migrations script AddNewTables AddAuditTable

È possibile usare un from più recente rispetto a to per generare uno script di rollback.

Avviso

Tenere conto degli scenari con potenziale perdita di dati.

La generazione di script accetta i due argomenti seguenti per indicare quale intervallo di migrazioni deve essere generato:

  • La migrazione di origine deve essere l'ultima migrazione applicata al database prima dell'esecuzione dello script. Se non è stata applicata alcuna migrazione, specificare 0 (valore predefinito).
  • La migrazione di destinazione è l'ultima migrazione applicata al database dopo l'esecuzione dello script. L'impostazione predefinita corrisponde all'ultima migrazione nel progetto.

Script SQL Idempotenti

Gli script SQL generati in precedenza possono essere applicati solo per modificare lo schema da una migrazione a un'altra; è responsabilità dell'utente applicare lo script in modo appropriato e solo ai database nello stato di migrazione corretto. EF Core supporta anche la generazione di script idempotenti , che controllano internamente quali migrazioni sono già state applicate (tramite la tabella della cronologia delle migrazioni) e applicano solo quelle mancanti. Ciò è utile se non si conosce esattamente l'ultima migrazione applicata al database o se si esegue la distribuzione in più database che possono trovarsi in una migrazione diversa.

Di seguito vengono generate migrazioni idempotenti:

dotnet ef migrations script --idempotent

Strumenti da riga di comando

Gli strumenti da riga di comando di Entity Framework possono essere usati per applicare le migrazioni a un database. Sebbene sia produttiva per lo sviluppo locale e il test delle migrazioni, questo approccio non è ideale per la gestione dei database di produzione:

  • I comandi SQL vengono applicati direttamente dallo strumento, senza consentire allo sviluppatore di esaminarli o modificarli. Questo può essere pericoloso in un ambiente di produzione.
  • .NET SDK e lo strumento EF devono essere installati nei server di produzione e richiedono il codice sorgente del progetto.

Nota

Ogni migrazione viene applicata nella propria transazione. Per una descrizione dei possibili miglioramenti futuri in questa area, vedere il problema 22616 di GitHub.

Di seguito viene aggiornato il database alla migrazione più recente:

dotnet ef database update

Di seguito viene aggiornato il database a una determinata migrazione:

dotnet ef database update AddNewTables

Si noti che questa operazione può essere usata anche per eseguire il rollback a una migrazione precedente.

Avviso

Tenere conto degli scenari con potenziale perdita di dati.

Per altre informazioni sull'applicazione delle migrazioni tramite gli strumenti da riga di comando, vedere le informazioni di riferimento sugli strumenti di EF Core.

Aggregazioni

I bundle di migrazione sono eseguibili a file singolo che possono essere usati per applicare le migrazioni a un database. Affrontano alcune delle carenze dello script SQL e degli strumenti da riga di comando:

  • L'esecuzione di script SQL richiede strumenti aggiuntivi.
  • La gestione delle transazioni e il comportamento di errore continuo di questi strumenti sono incoerenti e talvolta imprevisti. Questo può lasciare il database in uno stato non definito se si verifica un errore durante l'applicazione delle migrazioni.
  • I bundle possono essere generati come parte del processo di integrazione continua e facilmente eseguiti in un secondo momento come parte del processo di distribuzione.
  • I bundle possono essere eseguiti senza installare .NET SDK o ef Tool (o anche il runtime .NET, se autonomo) e non richiedono il codice sorgente del progetto.

Di seguito viene generato un bundle:

dotnet ef migrations bundle

Di seguito viene generato un bundle autonomo per Linux:

dotnet ef migrations bundle --self-contained -r linux-x64

Per altre informazioni sulla creazione di bundle, vedere le informazioni di riferimento sugli strumenti di EF Core.

efbundle

Il file eseguibile risultante è denominato efbundle per impostazione predefinita. Può essere usato per aggiornare il database alla migrazione più recente. Equivale all'esecuzione dotnet ef database update di o Update-Database.

Argomenti:

Argomento Descrizione
<MIGRATION> Migrazione di destinazione. Se '0', tutte le migrazioni verranno ripristinate. Il valore predefinito è l'ultima migrazione.

Opzioni:

Opzione Short Descrizione
--connection <CONNECTION> Stringa di connessione al database. Il valore predefinito è quello specificato in AddDbContext o OnConfiguring.
--verbose -v Visualizzare l'output dettagliato.
--no-color Non colorare l'output.
--prefix-output Output del prefisso con livello.

L'esempio seguente applica le migrazioni a un'istanza di SQL Server locale usando il nome utente e la password specificati.

.\efbundle.exe --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;User ID=myUsername;Password=myPassword'

Avviso

Non dimenticare di copiare appsettings.json insieme al bundle. Il bundle si basa sulla presenza di appsettings.json nella directory di esecuzione.

Esempio di bundle di migrazione

Un bundle richiede migrazioni da includere. Questi vengono creati usando dotnet ef migrations add come descritto in Creare la prima migrazione. Dopo aver pronto la distribuzione delle migrazioni, creare un bundle usando .dotnet ef migrations bundle Ad esempio:

PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations bundle
Build started...
Build succeeded.
Building bundle...
Done. Migrations Bundle: C:\local\AllTogetherNow\SixOh\efbundle.exe
PS C:\local\AllTogetherNow\SixOh>

L'output è un eseguibile adatto per il sistema operativo di destinazione. Nel mio caso si tratta di Windows x64, quindi viene eliminato efbundle.exe nella cartella locale. L'esecuzione di questo eseguibile applica le migrazioni contenute al suo interno:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
Applying migration '20210903083845_MyMigration'.
Done.
PS C:\local\AllTogetherNow\SixOh>

Come con dotnet ef database update o Update-Database, le migrazioni vengono applicate al database solo se non sono già state applicate. Ad esempio, l'esecuzione dello stesso bundle non esegue di nuovo alcuna operazione, poiché non sono disponibili nuove migrazioni da applicare:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
No migrations were applied. The database is already up to date.
Done.
PS C:\local\AllTogetherNow\SixOh>

Tuttavia, se vengono apportate modifiche al modello e vengono generate più migrazioni con dotnet ef migrations add, questi possono essere raggruppati in un nuovo eseguibile pronto per l'applicazione. Ad esempio:

PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations add SecondMigration
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations add Number3
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations bundle --force
Build started...
Build succeeded.
Building bundle...
Done. Migrations Bundle: C:\local\AllTogetherNow\SixOh\efbundle.exe
PS C:\local\AllTogetherNow\SixOh>

Suggerimento

L'opzione --force può essere usata per sovrascrivere il bundle esistente con uno nuovo.

L'esecuzione di questo nuovo bundle applica queste due nuove migrazioni al database:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
Applying migration '20210903084526_SecondMigration'.
Applying migration '20210903084538_Number3'.
Done.
PS C:\local\AllTogetherNow\SixOh>

Per impostazione predefinita, il bundle usa il database stringa di connessione dalla configurazione dell'applicazione. Tuttavia, è possibile eseguire la migrazione di un database diverso passando il stringa di connessione nella riga di comando. Ad esempio:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe --connection "Data Source=(LocalDb)\MSSQLLocalDB;Database=SixOhProduction"
Applying migration '20210903083845_MyMigration'.
Applying migration '20210903084526_SecondMigration'.
Applying migration '20210903084538_Number3'.
Done.
PS C:\local\AllTogetherNow\SixOh>

Nota

Questa volta sono state applicate tutte e tre le migrazioni, poiché nessuna di esse era ancora stata applicata al database di produzione.


Applicare migrazioni al runtime

È possibile che l'applicazione stessa applichi le migrazioni a livello di codice, in genere durante l'avvio. Sebbene sia produttiva per lo sviluppo locale e il test delle migrazioni, questo approccio non è appropriato per la gestione dei database di produzione, per i motivi seguenti:

  • Se più istanze dell'applicazione sono in esecuzione, entrambe le applicazioni potrebbero tentare di applicare la migrazione simultaneamente e hanno esito negativo (o peggio, causare il danneggiamento dei dati).
  • Analogamente, se un'applicazione accede al database mentre un'altra applicazione ne esegue la migrazione, questo può causare problemi gravi.
  • L'applicazione deve avere accesso con privilegi elevati per modificare lo schema del database. In genere è consigliabile limitare le autorizzazioni del database dell'applicazione nell'ambiente di produzione.
  • È importante poter eseguire il rollback di una migrazione applicata in caso di problema. Le altre strategie forniscono questa soluzione facilmente e predefinita.
  • I comandi SQL vengono applicati direttamente dal programma, senza consentire allo sviluppatore di esaminarli o modificarli. Questo può essere pericoloso in un ambiente di produzione.

Per applicare migrazioni a livello di codice, chiamare context.Database.Migrate(). Ad esempio, un'applicazione ASP.NET tipica può eseguire le operazioni seguenti:

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        db.Database.Migrate();
    }

    host.Run();
}

Si noti che Migrate() si basa sul IMigrator servizio, che può essere usato per scenari più avanzati. Usare myDbContext.GetInfrastructure().GetService<IMigrator>() per accedervi.

Avviso

  • Considerare attentamente prima di usare questo approccio nell'ambiente di produzione. L'esperienza ha dimostrato che la semplicità di questa strategia di distribuzione è superata dai problemi creati. Valutare invece la possibilità di generare script SQL dalle migrazioni.
  • Non chiamare EnsureCreated() prima di Migrate(). EnsureCreated() ignora la cartella Migrations per creare lo schema, causando un errore di Migrate().