Januar 2016

Band 31, Nummer 1

Data Points – EF7-Migration: Nicht neu, aber besser denn je

Von Julie Lerman | Januar 2016

Julie LermanEntity Framework 7 bringt einige dramatische Veränderungen für Code First-Migrationen mit sich. Während die grundlegenden Konzepte und Migrationsschritte gleich bleiben, ist der Workflow, der jetzt überzeugend mit der Quellcodeverwaltung zusammenarbeitet, wesentlich aufgeräumter und geradliniger geworden.

EF7 ermöglicht zwei Arten von Migrationen: die vertraute Variante, die Sie mit NuGet und den meisten .NET-Projekten verwenden, und die Variante, die Sie als Teil der DNX Runtime mit ASP.NET 5-Apps ausführen.

In meinen Pluralsight-Kursen (bit.ly/1MThS4J) habe ich einen Blick darauf geworfen. Wenngleich sich zwischen der Betaversion 4, die ich in meinem Kurs verwendet habe, und der RC1-Version (Release Candidate 1), die ich derzeit nutze, verschiedene Dinge geändert haben, stellt dieser Kurs weiter eine nützliche Ressource zum Veranschaulichen der Migrationen in der Praxis und zum Vermitteln einiger der Unterschiede dar.

Doch mit der Veröffentlichung der RC1-Version, deren Features mit Blick auf die anstehende RTM-Version als vollständig gelten, war es an der Zeit, sich eingehender mit EF7-Migrationen zu beschäftigen. Ich beginne mit den vertrauten Windows PowerShell-Befehlen in der Paket-Manager-Konsole von Visual Studio. Anschließend erläutere ich diese Befehle und ihre Verwendung mit DNX und ASP.NET 5. Dieser Artikel setzt voraus, dass Sie bereits über Kenntnisse der Features für die Datenbankinitialisierung und -migration in EF6 oder früheren Versionen verfügen.

Dank Migrationen keine magische Datenbankinitialisierung mehr

Die Unterstützung „magischer“ Vorgänge ist aufwendig, einschränkend und sorgt für eine Menge Ballast. Und im Fall von „DbInitializers“ sorgte sie für große Verwirrung. Aus diesem Grund wurden „EF DbInitializers“ sowie das Standardverhalten von „CreateData­baseIfNotExists“ entfernt. Stattdessen gibt es nun Logik, mit deren Hilfe Sie Datenbanken explizit erstellen und löschen (EnsureDeleted, Ensure­Created) können, worauf ich bei Integrationstests gerne zurückgreife.

EF7 ist auf das Arbeiten mit Migrationen ausgelegt, die nun Ihren Standardworkflow darstellen sollten.

Der „enable-migrations“-Befehl war vorhanden, da Migrationen an EF angeheftet waren und für ihre Verwendung das Erstellen bestimmter Infrastruktur erforderlich war. Mit diesem Befehl wurden keine neuen APIs installiert, sondern es wurden bloß ein Projektordner und Ihrem Projekt eine neue „DbMigrationConfiguration“-Klasse hinzugefügt. Bei EF7 ist die entsprechende Logik intern, weshalb Sie EF nicht explizit informieren müssen, dass Sie Migrationen verwenden möchten.

Wie Sie wissen, ist EF7 zusammensetzbar. Nicht jeder will oder braucht Migrationen. Falls gewünscht, müssen Sie sich für die Migrationsbefehle entscheiden, indem Sie das Paket installieren, in dem sie enthalten sind:

install-package entityframework.commands -pre

Die EF7-Migrationsbefehle

Mithilfe des Windows PowerShell-Cmdlets „Get-Help“ können Sie die aktuellen Migrationsbefehle (RC1-Version) anzeigen (siehe Abbildung 1). In der RTM-Version sollte diese Liste identisch sein.

Liste der mit dem NuGet-Paket-Manager-Befehl „Get“ abgerufenen Migrationsbefehle
Abbildung 1: Liste der mit dem NuGet-Paket-Manager-Befehl „Get“ abgerufenen Migrationsbefehle

Beachten Sie auch, dass es für „Update-Database“ nicht mehr den „-script“-Parameter gibt. Der Befehl führt nur das aus, was sein Name besagt: eine Aktualisierung der Datenbank. Wie immer wird das Anwenden dieses Befehls auf Produktionsdatenbanken nicht empfohlen. Abbildung 2 zeigt Details zum „Update-Database“-Befehl.

Syntax und Parameter von „Update-Database“
Abbildung 2: Syntax und Parameter von „Update-Database“

Zum Erstellen von Skripts können Sie den neuen „Script-Migration“-Befehl verwenden. Und es gibt nicht länger ein Geheimrezept zum Erstellen idempotenter Skripts (die in EF6 eingeführt wurden). Wie aus Abbildung 3 ersichtlich, ist „Idempotent“ ein Parameter.

„Script-Migration“-Parameter
Abbildung 3: „Script-Migration“-Parameter

Umfassende Änderungen beim Migrationsverlauf und Modellieren von Momentaufnahmen

Automatische Migrationen wurden aus EF7 entfernt. Aufgrund dessen ist das Verwalten von Migrationen einfacher, und die Quellcodeverwaltung wird nun unterstützt.

Damit für Migrationen bestimmt werden kann, was als Nächstes erfolgen muss, ist ein Verlauf vorhergehender Migrationen erforderlich. Immer wenn Sie den „add-migration“-Befehl ausführen, sucht die API nach Verlaufsinformationen. Bislang wurde bei Migrationen eine codierte Version einer Momentaufnahme des Modells für jede Migration in der Datenbank in der Tabelle mit (zuletzt) dem Namen „_MigrationHistory“ gespeichert. EF nutzte diese Tabelle, um zu bestimmen, was mit einer Migration erreicht werden sollte. Bei jeder Ausführung von „add-migration“ war eine Abfrage der Datenbank zum Lesen dieses Verlaufs erforderlich, was Ursache vieler Probleme war.

Wenn bei EF7 eine Migration (über „add-migration“) erstellt wird, erstellt EF7 zusätzlich zur vertrauten Migrationsdatei im Ordner „Migrations“ auch eine Datei zum Speichern der aktuellen Momentaufnahme des Modells. Das Modell wird mithilfe der für Konfigurationen verwendeten Syntax der Fluent-API beschrieben. Wenn Sie eine Änderung an Ihrem Modell vornehmen und dann eine Migration hinzufügen, wird die Momentaufnahme mit der aktuellen Beschreibung des vollständigen Modells überarbeitet. Abbildung 4 zeigt eine Migrationsdatei mit der neuen Zeichenfolge „MyProperty“.

Abbildung 4: Die vollständige Migrationsklasse „newStringInSamurai“

public partial class newStringInSamurai : Migration
  {
    protected override void Up(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.AddColumn<string>(
        name: "MyProperty",
        table: "Samurai",
        nullable: true);
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropColumn(name: "MyProperty", table: "Samurai");
    }
  }

Und hier sehen den Teil der aktualisierten „ModelSnapshot“-Klasse und können erkennen, dass „MyProperty“ nun Teil der Beschreibung der „Samurai“-Klasse ist:

modelBuilder.Entity("EF7Samurai.Domain.Samurai", b =>
  {
    b.Property<int>("Id")
      .ValueGeneratedOnAdd();
    b.Property<string>("MyProperty");
    b.Property<string>("Name");
    b.HasKey("Id");
  });

Mittels einzelner Migrationen wird also beschrieben, was sich geändert hat, was bislang auch so war. Doch nun verfügen Sie im Projekt stets über eine Beschreibung des vollständigen Modells. Dies bedeutet, dass EF bei Hinzufügen einer neuen Migration nicht die Datenbank abfragen muss, um zu bestimmen, wie das vorherige Modell ausgesehen hat. Die benötigten Informationen stehen unmittelbar zur Verfügung. Noch wichtiger ist das wesentlich verbesserte Zusammenspiel mit der Quellcodeverwaltung, da sich die Informationen direkt in Ihrem Code befinden und wie alle anderen Dateien von der Quellcodeverwaltung auf Unterschiede untersucht und zusammengeführt werden können. Die Informationen sind nicht in einer Datenbank gespeichert, auf die Ihre Teamkollegen ggf. gar keinen Zugriff haben. Vor EF7 gab es keine geeignete Möglichkeit zum Verwalten von Migrationen in der Quellcodeverwaltung. Unter „Code First Migrations in Team Environments“ (bit.ly/1OVO3qr) finden Sie eine Anleitung für Migrationen für Versionen vor EF7. Dieser Artikel beginnt mit dem Ratschlag: „Holen Sie sich einen Kaffee, denn Sie werden den gesamten Artikel lesen müssen.“

Bei EF7 werden in der Migrationsverlaufstabelle in der Datenbank (Abbildung 5) nun nur die Migrations-ID und die Version von EF gespeichert, mit der diese erstellt wurde. Dies ist wiederum eine umfassende Änderung im Vergleich zu früheren Versionen, bei denen die Momentaufnahme selbst in dieser Tabelle gespeichert wurde. „Update-Database“ überprüft diese Tabelle, um festzustellen, welche Migrationen angewendet wurden, und wendet die fehlenden an, darunter auch diejenigen, die nicht aufeinanderfolgend sind. Solange dieser Vorgang die Datenbank abfragen muss, sehe ich kein Problem damit, dass Migrationen in diesem Szenario eine schnelle Vorababfrage der Datenbank ausführen.

„dbo_EFMigrationsHistory“ mit von Migrationen hinzugefügter Tabelle
Abbildung 5: „dbo_EFMigrationsHistory“ mit von Migrationen hinzugefügter Tabelle

Sie müssen sich jetzt nicht mehr mit dem Unsinn abfinden, dass eine Migration nicht hinzugefügt werden kann, wenn die vorherige noch nicht auf die Datenbank angewendet wurde. Dies war eine Nebenwirkung automatischer Migrationen, die dazu führte, dass viele von uns in ein verworrenes Netz stolperten. Nun können Sie Migrationen in Ihrer Projektmappe nach Bedarf erstellen und, wenn Sie soweit sind, die Datenbank anschließend aktualisieren.

Aufgrund der Modellmomentaufnahme, die EF7 in Ihrem Projekt speichert, dürfen Sie eine Migrationsdatei nicht bloß aus Ihrem Projekt löschen, da die Momentaufnahme dann fehlerhaft wäre. Deshalb kommt zur Änderung am Migrationsworkflow ein neuer Befehl hinzu: remove-migration. „remove-migration“ dient zum Rückgängigmachen einer Migration, die Sie vermutlich lediglich hinzugefügt und dann ignoriert haben, bevor sie auf die Datenbank angewendet wurde. Der Befehl bewirkt zwei Dinge in Ihrem Projekt: Zunächst wird die letzte Migrationsdatei entfernt, und anschließend wird die Momentaufnahmedatei im Ordner „migrations“ aktualisiert. Interessanterweise wird dadurch nicht die Momentaufnahme basierend auf dem Modell neu erstellt. Stattdessen wird die Momentaufnahme basierend auf der Modellmomentaufnahme in der Datei „Designer.cs“ geändert, die zusammen mit jeder Momentaufnahme erstellt wird. Ich habe dies getestet und erkannt, dass wenn ich das Modell nicht änderte (d. h. ich nicht die unerwünschte Eigenschaft aus der „Samurai“-Klasse entfernte), die Momentaufnahme weiter gemäß den Migrationen angeordnet war. Sie dürfen aber nicht vergessen, auch Ihr Modell anzupassen. Andernfalls sind Modell und Momentaufnahme nicht synchron, und vermutlich ist auch Ihr Datenbankschema nicht mehr synchron mit dem Modell.

Brice Lambson, das für Migrationen zuständige Mitglied des EF-Teams, hat mir den interessanten Hinweis gegeben, dass wenn Sie eine Migrationsdatei manuell löschen, dies beim unmittelbar nächsten Aufruf von „remove-­migration“ erkannt und die Momentaufnahme korrigiert wird, ohne dass eine weitere Migrationsdatei gelöscht wird.

Ebenso wie „add-migration“ wirkt sich „remove-­migration“ nicht auf die Datenbank aus. Jedoch überprüft der Befehl die Migrationsverlaufsdatei der Datenbank, um festzustellen, ob die Migration bereits angewendet wurde. Falls ja, können Sie „remove-migration“ bislang noch nicht verwenden.

Das hört sich verwirrend an, ich weiß. Dennoch mag ich „remove-migration“, denn die Alternative ist, einfach weiterzumachen und eine Migration immer dann hinzuzufügen, wenn Sie Ihre Meinung ändern. Dies sorgt möglicherweise für Verwirrung, nicht nur bei anderen Teammitgliedern, sondern in Zukunft auch bei Ihnen. Darüber hinaus ist dies nur ein kleines Opfer für den Vorteil, dass Migrationen der Quellcodeverwaltung unterstellt werden.

Abbildung 6 bietet zur Klärung eine Anleitung zum Rückgängigmachen einer unerwünschten Migration. Das Diagramm geht davon aus, dass „Migration1“ auf die Datenbank angewendet wurde und „Migration2“ zwar erstellt, aber nie auf die Datenbank angewendet wurde.

Abbildung 6: Umgang mit einer unerwünschten Migration

Status der Datenbank Befehle Vom Befehl ausgeführte Aufgaben
„Update-Database“ wurde nach Migration2 nicht ausgeführt. Remove-Migration

1. Überprüft, ob Migration2 nicht in der Verlaufstabelle ist.

2. Löscht „Migration2.cs“

3. Korrigiert „Snapshot.cs“ entsprechend der vorherigen Migration.

„Update-Database“ wurde nach Migration2 ausgeführt.

Update-Database Migration1

Remove-Migration

1. Führt SQL für die „Drop“-Methode von Migration2 aus.

2. Entfernt die Zeile „Migration2“ aus der Verlaufstabelle.

1. Löscht „Migration2.cs“

2. Korrigiert „Snapshot.cs“ entsprechend der vorherigen Migration.

Wenn Sie mehrere Migrationen rückgängig machen müssen, rufen Sie zunächst „update-database“ gefolgt vom Namen der letzten funktionierenden Migration auf. Danach können Sie „remove-migration“ aufrufen, um Migrationen und die Momentaufnahme einzeln nacheinander zurückzusetzen.

Vergessen Sie bei all diesem Fokus auf Migrationen nicht, dass diese Migrationen Änderungen am Modell widerspiegeln. Ein letztes Mal zur Erinnerung: Sie müssen die Änderungen am Modell manuell rückgängig machen und dann alle Migrationen entfernen, die diese Änderungen widerspiegeln.

Zurückentwicklung mit „Scaffold-DbContext“

Vorversionen von EF boten die Möglichkeit der Zurückentwicklung einer Datenbank in Code First-Modelle. Microsoft stellte diese Fähigkeit über die Entity Framework Power Tools und ab EF6 über den EF Designer zur Verfügung. Es gibt auch Tools von Drittanbietern, wie z. B. die (kostenlose) Erweiterung EntityFramework Reverse POCO Generator (reversepoco.com). Da EF7 keinen Designer hat, wurde für diese Aufgabe der „Scaffold“-Befehl entwickelt.

Es folgen die Parameter für „Scaffold-DbContext“:

Scaffold-DbContext [-Connection] <String> [-Provider] <String>
                   [-OutputDirectory <String>] [-ContextClassName <String>]
                   [-Tables <String>] [-DataAnnotations] [-Project <String>]
                   [<CommonParameters>]

Ich bin ein großer Fan von Fluent-API-Konfigurationen, weshalb es mich freut, dass diese erneut die Standardeinstellungen bilden, weshalb Sie das Verwenden von Datenanmerkungen mithilfe des Kennzeichens „DataAnnotations“ anfordern müssen. Es folgt ein Befehl zum Zurückentwickeln meiner vorhandenen Datenbank mithilfe dieses Parameters (ich könnte auch die Tabellen für den „scaffold“-Befehl auswählen, was ich hier jedoch unterlasse):

Scaffold-DbContext
   -Connection "Server = (localdb)\mssqllocaldb; Database=EF7Samurai;"
   -Provider entityframework.sqlserver
   -OutputDirectory "testRE"

Wie Abbildung 7 zeigt, sind das Ergebnis ein neuer Ordner und mehrere Dateien, die meinem Projekt hinzugefügt werden müssen. Auf diese Klassen müsste ich eigentlich weiter im Detail eingehen, da dies ein wichtiges Feature ist, was ich aber erst in Zukunft tun werde.

Ergebnis von „Scaffold-DbContext“
Abbildung 7: Ergebnis von „Scaffold-DbContext“

Die DNX-Versionen von Migrationsbefehlen

Die Befehle, die Sie in der Paket-Manager-Konsole ausführen, sind Windows PowerShell-Befehle, und Windows PowerShell ist ein Teil von Windows. Eines der wichtigsten Merkmale von ASP.NET 5 (und von EF7) ist, dass keine Abhängigkeit von Windows mehr besteht. DNX ist eine schlanke .NET-Ausführungsumgebung, die unter OS X und Linux sowie unter Windows ausgeführt werden kann. Weitere Informationen finden Sie unter bit.ly/1Gu34wX. Das Paket „entityframework.commands“ enthält auch Migrationsbefehle für die DNX-Umgebung. Wir wollen nun einen kurzen Blick auf diese Befehle werfen.

Wenn Sie mit Visual Studio 2015 arbeiten (wie hier gezeigt), können Sie die Befehle direkt in der Paket-Manager-Konsole ausführen. Führen Sie sie andernfalls an der Befehlszeile Ihres Betriebssystems aus. Sehen Sie sich das Channel 9-Video von Nate McMaster an, in dem er EF7 und Migrationen auf einem Mac programmiert. (bit.ly/1OSUCdo)

Zunächst müssen Sie sich vergewissern, dass Sie sich im richtigen Ordner befinden, d. h. in dem Ordner des Projekts, in dem sich Ihr Modell befindet. Die Dropdownliste „Standardprojekt“ bezieht sich nicht auf Ihren Dateipfad. Verwenden Sie den Befehl „dir“, um festzustellen, wo Sie sind, und wechseln Sie anschließend über „cd“ zum richtigen Ordner. Die Vervollständigung mit der TAB-TASTE leistet hier gute Dienste. In Abbildung 8 können Sie erkennen, dass obgleich das Standardprojekt „src\EF7WebAPI“ ist, ich zum Ausführen von DNX-Befehlen in diesem Verzeichnis explizit das Verzeichnis in den Pfad des Projekts ändern muss, das mein EF7-Modell enthält.

Wechseln zum richtigen Verzeichnis vor der Ausführung von DNX-Befehlen
Abbildung 8: Wechseln zum richtigen Verzeichnis vor der Ausführung von DNX-Befehlen

Nachdem ich dorthin gelangt bin, kann ich Befehle ausführen. Vor jeden Befehl muss übrigens „dnx“ gesetzt werden, und Sie können mithilfe von „dnx ef“ alle verfügbaren Befehle auflisten. Beachten Sie, dass „ef“ eine Verknüpfung ist, die ich in „project.json“ eingerichtet habe. In meinem Pluralsight-Video „Looking Ahead to Entity Framework 7“ (bit.ly/PS_EF7Alpha) finden Sie weitere Informationen zur Einrichtung.

Die Hauptbefehle sind „database“, „dbcontext“ und „migrations“, wobei „migrations“ die folgenden Unterbefehle hat:

add     Add a new migration
list    List the migrations
remove  Remove the last migration
script  Generate a SQL script from migrations

Jeder Befehl hat Parameter, die Sie durch Hinzufügen von „--help“ abfragen können:

dnx ef migrations add --help

Wenn der Migrationsname beispielsweise „initial“ ist, lautet der Befehl wie folgt:

dnx migrations add initial

Zur Erinnerung: Dies sind dieselben Parameter, die bereits in diesem Artikel vorgestellt wurden. Sie finden also Parameter vor, die Ihnen das Angeben des Projekts, von „DbContext“ und anderen Details ermöglichen. Der „script“-Befehl hat den zuvor beschriebenen „idempotent“-Parameter. Doch ein Unterschied, der den DNX-Befehl „script“ ausmacht, ist, dass das Skript standardmäßig einfach im Befehlsfenster aufgelistet wird. Sie müssen den Ausgabeparameter „<file>“ explizit angeben, um das Skript in eine Datei zu übertragen.

Mit dem folgenden Befehl werden die Migrationen auf die Datenbank angewendet:

dnx ef database update

Der „dbcontext“-Befehl hat zwei Unterbefehle: Mit „dbcontext list“ werden alle Datenbankontexte aufgelistet, die im Projekt gefunden werden. Dies ist der Fall, da keine Vervollständigung mit der TAB-TASTE verfügbar ist, mit deren Hilfe die Befehle so einfach wie in Windows PowerShell bereitgestellt werden können. „scaffold“ ist die DNX-Version des zuvor beschriebenen „DbContext-Scaffold“-Befehls.

Mit EF7 insgesamt eine viel bessere Migrationsumgebung

Durch das Entfernen automatischer Migrationen wurden Migrationen in EF7 vereinfacht. Wenngleich die grundlegenden Konzepte gleich sind, ist der Workflow übersichtlicher. Das Beseitigen der Hindernisse beim Verwenden von Migrationen in einer Teamumgebung war wichtig. Mir gefällt, wie dies gelöst wurde, indem die Momentaufnahme des Modells in den Quellcode einbezogen wird. Und das Ermöglichen der Zurückentwicklung mithilfe des „scaffold“-Befehls ist eine gute Möglichkeit, diese wichtige Fähigkeit bereitzustellen, ohne einen Designer zu verwenden.

Die Design Meeting Notes des EF-Teams (bit.ly/1S813ro) vom 23.07.15 enthalten eine nützliche Tabelle mit allen Windows PowerShell- und DNX-Befehlen, nachdem die Benennung gestrafft wurde. Ich erwarte, dass die Dokumentation letztendlich eine noch übersichtlichere Version enthalten wird, aber ich finde auch diese Tabelle nützlich.

Ich habe diesen Artikel unter Verwendung der RC1-Version von EF7 verfasst. Diese Version sollte sämtliche Features enthalten, weshalb es wahrscheinlich ist, dass diese Informationen denen zu EF7 entsprechen, das zusammen mit ASP.NET 5 im Frühjahr 2016 veröffentlicht wird.


Julie Lermanist Microsoft MVP, .NET-Mentorin und Unternehmensberaterin. Sie lebt in den Bergen von Vermont. Sie hält bei User Groups und Konferenzen in der ganzen Welt Vorträge zum Thema Datenzugriff und anderen .NET-Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Autorin von „Programming Entity Framework“ (2009) sowie der Ausgaben „Code First“ und „DbContext“ (alle bei O’Reilly Media erschienen). Folgen Sie ihr auf Twitter: @julielerman, und sehen Sie sich ihre Pluralsight-Kurse unter juliel.me/PS-Videos an.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Brice Lambson