Il presente articolo è stato tradotto automaticamente.

Concetti sui dati

Un mistero di migrazioni primo codice: Risolto

Julie Lerman

Julie LermanUn amico di recente mi ha chiesto una domanda Entity Framework (EF) codice migrazioni prima che mi ha confuso quasi tanto quanto esso confuso lui — ma per motivi diversi.

La conversazione è andato qualcosa come questo:

"Hey Julie, perché il mio sito Web Azure automaticamente migrerebbero mio database?"

"Sei sicuro che non hai migrazioni automatiche impostato su true? È falsa per impostazione predefinita."

"Sicuramente non. Non ho cambiato l'impostazione corrispondente."

"Riguardo l'inizializzatore di migrazioni? Che potrebbe farlo, troppo."

Vorrei interrompere qui e assicurarsi di che comprendere veramente circa tale inizializzatore. Se le migrazioni sono esplicite, qualcosa creare ed eseguire nella Console di Gestione pacchetti , perché c'è un inizializzatore?

Codice inizializzatori di primo

Codice iniziato con tre inizializzatori:

CreateDatabaseIfNotExists: Con questo uno, se il database non viene trovato esso verrà creato, e che è la fine del suo lavoro. Se si modifica il database, il database non verrà ricreato o migrato. Si otterrà semplicemente un'eccezione suggerendo che si utilizza un metodo diverso per gestire i database. In particolare, il suggerimento sarà utilizzare migrazioni.

DropCreateDatabaseAlways: Questo è ideale per i test di integrazione. Sarà eliminare il database esistente e ricrearla da zero, in esecuzione di qualsiasi logica di seme che hai aggiunto in. Per test di integrazione, è un ottimo modo per avere sempre un database coerenza.

DropCreateDatabaseIfModelChanges: Prima c'erano le migrazioni, questo era un'opzione utile durante lo sviluppo, se lei non ha bisogno di conservare dati o qualsiasi altro schema che potrebbe aver modificato nel database quali l'aggiunta di indici o trigger.

Per completezza, vorrei anche sottolineare che in EF6, abbiamo ottenuto la NullDatabaseInitializer per chi fatica a ricordare (o scoprire) l'altra sintassi per la disattivazione di inizializzazione: SetDatabaseInitialier < MyContext > (null).

Quindi le migrazioni arrivarono in EF4.3. Le migrazioni non sono attive a livello di dati fino a che non vengano abilitate esplicitamente dalla finestra di Console di Gestione pacchetti digitando enable-migrazioni. Tutto questo davvero non è aggiungere una nuova cartella con una classe di configurazione al progetto. Se il processo di abilitazione scopre un database esistente legato al tuo contesto, crea una classe iniziale migrazione, pure. Questo è per assicurare che quando si esegue la migrazione, il primo codice non tentare di creare un oggetto di database già esistente.

Infatti, vedrai un messaggio informativo nella console dopo aver abilitato le migrazioni, come mostrato Figura 1.

La risposta di Enable-migrazioni a Database esistenti creati da un inizializzatore Initializer
Figura 1: La risposta di Enable-migrazioni a Database esistenti creati da un inizializzatore

Se si guarda nel tuo database, nel nuovo file di __MigrationHistory, vedrai una riga nella tabella riflettendo che la migrazione iniziale ha già eseguita sul database.

È importante capire che non c'è nient'altro — senza aggiunte al file config dell'applicazione o qualsiasi altri nascosti pezzi del puzzle di migrazione. La classe Configuration è critica, però.

Qui è l'implementazione predefinita da quando ho attivato la migra­zioni sul mio livello di dati, dove ho un DbContext classe chiamato MigratoryBirdsContext:

internal sealed class Configuration : 
  DbMigrationsConfiguration<MigratoryBirdsContext>
  {
    public Configuration()
    {
      AutomaticMigrationsEnabled = false;
      ContextKey = "DataLayer.MigratoryBirdsContext";
    }
    protected override void Seed(MigratoryBirdsContext context)
    {
    }
  }

Perché sarò con questa classe in modo esplicito ulteriormente, cambierò la sua firma al pubblico. Per quelli di voi semplicemente scansione attraverso il codice, ecco la nuova dichiarazione:

public class Configuration : DbMigrationsConfiguration<MigratoryBirdsContext>

Le migrazioni possono eseguire automaticamente, ovvero cambiamenti di modello saranno scoperti e migrazioni corrispondenti alle modifiche verranno create ed eseguite sul database. Tutto questo accade in fase di esecuzione durante l'inizializzazione del database. Migrazioni automatiche sono utili per applicazioni semplici, ma avete ben poco controllo su di loro e in genere non Consiglio consentendo loro. Sono stato felice quando acceso il primo codice predefinito false.

In fase di esecuzione, il primo codice cerca una classe DbMigrationsConfiguration che è legata a un contesto quando quel contesto passa attraverso i suoi dati­processo di inizializzazione di base. Se tale classe viene trovata, eventuali chiamate esplicite o impostazioni per impostare l'inizializzazione database a qualsiasi gli inizializzatori di tre originali — cui compito è quello di creare o eliminare e creare il database — creerà il database utilizzando le migrazioni. Questo è un nuovo modello per EF6.1. Prima di questo, quei tre inizializzatori userebbe la logica di inizializzazione che avevano sempre usato dal EF4.3, inferendo lo schema del database dal modello e configurazioni. Questo cambiamento di EF6.1 è conveniente per gli sviluppatori che, come me, usare il DropCreateDatabase­inizializzatore sempre nei loro test. Ora esso sarà eliminare il database ma utilizzare mio migrazioni per ricrearlo. Se ho personalizzato il mio classi di migrazioni, otterrò quella personalizzazione applicata al mio database, pure.

È anche importante tenere a mente che l'inizializzatore predefinito — CreateDatabaseIfNotExists — utilizzerà anche le migrazioni per creare un database se le migrazioni esistono ma non il database.

Così, ora posso guardare l'inizializzatore di quarto, MigrateDatabaseToLatestVersion, che è quello che chiesto al mio amico circa quando stava cercando di arrivare all'origine delle migrazioni misteriose nel suo sito Web Azure. Questo inizializzatore è stato intorno dal momento che le migrazioni sono state introdotte in EF4.3.

Il nome, MigrateDatabaseToLatestVersion, sembra suggerire avrebbe funzionato tanto come migrazioni automatiche, ma c'è una differenza significativa, singola. Migrazioni automatiche sono innescate da un cambiamento nel modello, considerando che il compito di questo inizializzatore è innescato da classi esistenti di migrazione.

Per vedere come funziona, cominciamo con lo stato predefinito dopo aver abilitato le migrazioni. Non non c'è alcun inizializzatore MigrateDatabaseToLatestVersion nel gioco e AutomaticMigrations è impostata su false.

Ecco un test di integrazione di base che esegue una semplice query su MigratoryBirdsContext:

[TestMethod]
public void CanInitializeDatabase() {
  using (var context = new MigratoryBirdsContext())
  {
    context.MigratoryBirds.ToList();
  }
  Assert.Inconclusive("If we're here, it did not crash");
}

Se dovessi apportare una modifica a una delle classi nel mio modello ed eseguire questo test, sarebbe essere generata un'eccezione, che mi dice, "il modello MigratoryBirdsContext contesto di supporto è cambiato da quando è stato creato il database. È consigliabile utilizzare le prime migrazioni codice per aggiornare il database."

Anche se le migrazioni sono state abilitate e ho la classe di MigrationsConfiguration, tutti che EF SA è che il modello attuale non corrisponde l'ultima versione archiviata nella tabella MigrationHistory. A questo punto, potrei usare Aggiungi-migrazione e aggiornamento database per risolvere il problema, ma voglio mostrarvi come funziona l'inizializzatore, così mi prendo una strada diversa: Modificherò il test per abilitare l'inizializzatore. Se non hai familiarità con la sintassi del costruttore, devi puntare al file di configurazione con destinazione DbContext, in questo modo:

[TestMethod]
public void CanInitializeDatabase()
{
  Database.SetInitializer(
    new MigrateDatabaseToLatestVersion<MigratoryBirdsContext, 
    DataLayer.Migrations.Configuration>());
  using (var context = new MigratoryBirdsContext())
  {
    context.MigratoryBirds.ToList();
  }
  Assert.Inconclusive("If we're here, it did not crash");
}

Eseguendo nuovamente il test genera un'altra eccezione, e relativo messaggio evidenzia la differenza tra questo inizializzatore e migrazioni automatiche: "Impossibile aggiornare il database per abbinare il modello attuale, perché ci sono modifiche in sospeso e automatico migrazione è disabilitato. Scrivere le modifiche in sospeso modello ad una basata su codice migrazione o consentire la migrazione automatica."

AutomaticMigrations vuoi notare la differenza, la migrazione di creare e aggiornare il database — tutto in-the-fly e tranquillamente. Tuttavia, AutomaticMigrations non persistono eventuali migrazioni che crea come una classe del progetto. Sebbene questo inizializzatore rileva il cambiamento, non crea automaticamente la migrazione per voi. Può funzionare solo con migrazioni esistenti.

Se tu non hai eseguito migrazioni manualmente prima, è un processo in due fasi:

In primo luogo, si crea la migrazione con il comando add-migrazione. Questo leggerà il vostro modello e interrogare il database per l'ultima entrata nella tabella Cronologia migrazione, quindi confrontare i due. Se il tuo modello è cambiato da quando l'ultima è stato archiviato, creerà una nuova classe di migrazione con le istruzioni per la migrazione dello schema del database corrente per allinearsi con i cambiamenti nel vostro modello. Successivamente, si esegue tale migrazione (o una combinazione delle migrazioni basato sulla sintassi del comando) sul database. Il comando base che è digitare nella Console Gestione pacchetti si aggiorna il database.

Non molti sviluppatori si rendono conto che è possibile eseguire l'aggiornamento database a livello di codice, come pure. Sapevo che era possibile, ma non aveva fatto tanto tempo che ho dovuto andare a cercare i dettagli nel mio corso di migrazioni primo codice Pluralsight. L'API di migrazioni ha una classe DbMigrator che consente di aggiornare il database con le stesse opzioni che è possibile accedere a dal comando update-database nella console.

I passaggi chiavi sono per creare il file di configurazione di migrazione; un'istanza della classe API DbMigrator, passando un'istanza della classe Configuration; migrazione e quindi chiamare Update:

var migrator = new DbMigrator(new DbMigrationsConfiguration());
migrator.Update();

E questo è ciò che fa l'inizializzatore di MigrateDatabaseToLatestVersion per tuo conto. Le migrazioni hanno bisogno di esistere già nel progetto (o in un assembly compilato) e l'inizializzatore eseguirà li sul database per voi in fase di esecuzione.

Con tale modifica che ho fatto alla mia classe di dominio, ora potrai andare avanti e aggiungere una migrazione con il comando Aggiungi-migrazione 'JetStream.' Qui è la classe che viene generata per me:

public partial class JetStream : DbMigration
{
  public override void Up()
  {
    AddColumn("dbo.MigratoryBirds", "DependentOnJetStream", c =>
      c.Boolean(nullable: false));
  }
  public override void Down()
  {
    DropColumn("dbo.MigratoryBirds", "DependentOnJetStream");
  }
}

Ora questa classe fa parte del mio progetto. Quando si esegue il test di nuovo, dove ho impostato la migrazione-­inizializzatore di database, il test non genera un'eccezione. E mio profiler mi dimostra che prima l'essere seleziona eseguito, lo schema di tabella del database viene modificato per aggiungere nella nuova proprietà DependentOnJetStream.

Quello che sto ottenendo con questo modello che mi piaceva per l'inizializzatore di DropCreateDatabaseAlways non è una versione fresca del database per test di integrazione. Per raggiungere questo obiettivo, devo eseguire qualche crudo SQL nel metodo del file di configurazione prima della semina del database Seed. O, per i test di integrazione, potrei semplicemente disabilitare del tutto l'inizializzazione e controllare esplicitamente la creazione del database. Eric Hexter ha scritto un post sul blog grande a questo proposito, "Utilizzando SQL Compact per test di integrazione con Entity Framework(bit.ly/1qgh9Ps).

Dove brilla MigrateDatabaseToLatestVersion

Se si dovesse compilare il progetto e dare a un altro sviluppatore che non ha migrato il proprio database di sviluppo, l'inizializzatore innescherebbe l'app per cercare una migrazione. La migrazione verrà trovata perché è nel progetto. L'inizializzatore verificherà la migrazione storia tabella del database e aggiornare lo schema del database, se necessario.

Considerando questo in produzione, non lo sviluppo, la classe di migrazione sarà compilato in un assembly e distribuito come parte dell'applicazione. Quando l'applicazione viene eseguita, essa risponde ad un'impostazione di MigrateDatabaseToLatestVersion (nel posto appropriato nell'applicazione) e aggiornare di conseguenza lo schema del database.

Cos'e ' successo nel sito Web di azzurro?

Ora che avete una buona comprensione di come MigrateDatabase­ToLatestVersion opere, torniamo alla conversazione con il mio amico. Abbiamo lasciato presso: "Riguardo l'inizializzatore di migrazioni? Che farei, troppo."

Mi ha risposto che lui non aveva impostato l'inizializzatore di migrazioni ovunque nel codice. Così ora è stato un grande mistero. Il comportamento corrispondeva a quella di MigrateDatabaseToLatestVersion esattamente. Ha distribuito il suo app in Azure. Quando si correva, l'app ha notato il cambiamento che nel modello aveva fatto prima di ridistribuire e aggiornata dello schema del database.

Con tutte queste domande ha chiesto e ha risposto, lasciandomi un po' perplesso, sono andato al mio motore di ricerca preferito con alcune informazioni utili in mano e ha fatto una scoperta.

Quando si pubblica una Web app in azzurro, c'è una casella di controllo nella pagina Impostazioni che deselezionata per impostazione predefinita. Ma che la casella di controllo dice "Execute codice prime migrazioni (gira su avvio dell'applicazione)."  Controllando questo aggiungerà l'impostazione di MigrateDatabaseToLatestVersion in modo dichiarativo nel file config.

Inviato via mail il mio amico e gli mostrò uno screenshot della pagina con quell'impostazione e chiesto, "Un aspetto familiare?" Mistero risolto. Aveva creato le migrazioni e aggiornato il suo database di sviluppo. Così le migrazioni sono state compilate in suo assembly quando egli ha distribuito. E si ricordò barrando questa casella di controllo.

Così quando correva il suo sito Web per la prima volta, l'inizializzatore chiamato DbMigrator.Update, che rispetto l'elenco delle migrazioni nell'assembly all'elenco nella tabella MigrationsHistory e corse di quelli che non erano presenti nella tabella.

Ora che egli riconosce come una caratteristica, non un mistero.


Julie Lerman è un Microsoft MVP, mentore e consulente .NET che risiede nel Vermont. È possibile trovare la sua presentazione sull'accesso ai dati e altri argomenti .NET a gruppi di utenti e conferenze in tutto il mondo. Blog di lei a /Blog ed è l'autore di "programmazione Entity Framework" (2010), nonché un codice prima edizione (2011) e un DbContext edizione (2012), tutti da o ' Reilly Media. Seguirla su Twitter a Twitter.com /julielerman. e vedere i suoi corsi Pluralsight presso Juliel.me/PS-video.

Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Rowan Miller