Teil 4: Razor-Seiten mit EF Core-Migrationen in ASP.NET Core

Von Tom Dykstra, Jon P. Smith und Rick Anderson

Die Web-App Contoso University veranschaulicht, wie Razor-Seiten-Web-Apps mit EF Core und Visual Studio erstellt werden können. Informationen zu den Tutorials finden Sie im ersten Tutorial.

Wenn Probleme auftreten, die Sie nicht beheben können, laden Sie die vollständige App herunter, und vergleichen Sie diesen Code mit dem Code, den Sie anhand des Tutorials erstellt haben.

In diesem Tutorial wird das EF Core-Migrationsfeature für die Verwaltung von Datenmodelländerungen vorgestellt.

Wenn eine neue App entwickelt wird, ändert sich das Datenmodell häufig. Nach jeder Modelländerung ist das Modell nicht mehr synchron mit der Datenbank. In dieser Tutorialreihe wurde zunächst Entity Framework für die Erstellung der Datenbank konfiguriert, falls diese nicht vorhanden ist. Jedes Mal, wenn sich das Datenmodell ändert, muss die Datenbank gelöscht werden. Wenn die App das nächste Mal ausgeführt wird, wird durch den Aufruf von EnsureCreated die Datenbank erneut entsprechend dem neuen Datenmodell erstellt. Die DbInitializer-Klasse wird dann ausgeführt, um das Seeding der neuen Datenbank durchzuführen.

Dieser Ansatz, die DB mit dem Datenmodell synchron zu halten, funktioniert gut, bis die App in der Produktion bereitgestellt werden muss. Wenn die App in der Produktionsumgebung ausgeführt wird, speichert sie in der Regel Daten, die verwaltet werden müssen. Jedes Mal, wenn eine Änderung vorgenommen wird (z.B. wenn eine neue Spalte hinzugefügt wird), kann die App nicht mit einer Testdatenbank starten. Mit dem EF Core-Migrationsfeature wird dieses Problem gelöst, indem EF Core das Datenbankschema aktualisiert, anstatt eine neue Datenbank zu erstellen.

Anstatt die Datenbank bei Änderungen des Datenmodell zu löschen und neu zu erstellen, erfolgt bei der Migration eine Aktualisierung des Schemas und die Beibehaltung vorhandener Daten.

Hinweis

SQLite-Einschränkungen

Dieses Tutorial verwendet nach Möglichkeit das Entity Framework Core-Feature Migrationen. Durch Migrationen wird das Datenbankschema so aktualisiert, dass es den Änderungen am Datenmodell entspricht. Bei Migrationen werden jedoch nur die Änderungen vorgenommen, die von der Datenbank-Engine unterstützt werden, und die Schemaänderungsfunktionen von SQLite sind beschränkt. Zum Beispiel wird Hinzufügen einer Spalten unterstützt, wogegen Entfernen einer Spalte nicht unterstützt wird. Wenn eine Migration zum Entfernen einer Spalte erstellt wird, ist der Befehl ef migrations add erfolgreich, aber der Befehl ef database update schlägt fehl.

Die Problemumgehung für die SQLite-Einschränkungen besteht darin, manuell Migrationscode zu schreiben, um das erneute Erstellen einer Tabelle durchzuführen, wenn sich etwas in der Tabelle ändert. Der Code fließt in die Up- und Down-Methoden für eine Migration ein und umfasst Folgendes:

  • Erstellen einer neuen Tabelle.
  • Kopieren von Daten aus der alten Tabelle in die neue Tabelle.
  • Löschen der alten Tabelle.
  • Umbenennen der neuen Tabelle.

Das Schreiben von datenbankspezifischem Code dieses Typs übersteigt den Rahmen dieses Tutorials. Stattdessen wird die Datenbank in diesem Tutorial gelöscht und neu erstellt, wenn der Versuch fehlschlägt, eine Migration anzuwenden. Weitere Informationen finden Sie in den folgenden Ressourcen:

Löschen der Datenbank

Verwenden Sie SQL Server-Objekt-Explorer (SSOX), um die Datenbank zu löschen, oder führen Sie den folgenden Befehl in der Paket-Manager-Konsole (PMC) aus:

Drop-Database

Erstellen einer ursprünglichen Migration

Führen Sie die folgenden Befehle in der PMC aus:

Add-Migration InitialCreate
Update-Database

Entfernen von EnsureCreated

Diese Tutorialreihe hat mit der Verwendung von EnsureCreated begonnen. EnsureCreated erstellt keine Migrationsverlaufstabelle und kann daher nicht mit Migrationen verwendet werden. EnsureCreated dient zum Testen oder schnellen Erstellen von Prototypen, wenn die Datenbank häufig gelöscht und neu erstellt wird.

Ab diesem Zeitpunkt werden die Tutorials Migrationen verwenden.

Löschen Sie in Program.cs das folgende Element:

context.Database.EnsureCreated();

Führen Sie die App aus, und stellen Sie sicher, dass für die Datenbank ein Seeding durchgeführt wird.

Up- und Down-Methoden

Der EF Core-Befehl migrations add hat Code zum Erstellen der Datenbank generiert. Der Migrationscode befindet sich in der Migrations\<timestamp>_InitialCreate.cs-Datei. Die Methode Up der Klasse InitialCreate erstellt die Datenbanktabellen, die den Datenmodell-Entitätenmengen entsprechen. Die Methode Down löscht diese, wie im folgenden Beispiel dargestellt:

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");
        }
    }
}

Der vorangehende Code ist für die ursprüngliche Migration bestimmt. Der Code:

  • Wurde durch den migrations add InitialCreate-Befehl generiert.
  • Wird durch den database update-Befehl ausgeführt.
  • Erstellt eine Datenbank für das Datenmodell, das durch die Datenbankkontextklasse angegeben wird.

Der Parameter für den Migrationsnamen (im Beispiel InitialCreate) wird als Dateiname verwendet. Der Migrationsname kann ein beliebiger gültiger Dateiname sein. Es wird empfohlen, ein Wort oder einen Ausdruck auszuwählen, durch das bzw. den der Hintergrund der Migration widergespiegelt wird. Eine Migration, bei der eine Tabelle mit Fachbereichen hinzugefügt wurde, könnte beispielsweise als „AddDepartmentTable“ bezeichnet werden.

Die Migrationsverlaufstabelle

  • Verwenden Sie SSOX oder das SQLite-Tool, um die Datenbank zu untersuchen.
  • Beachten Sie die zusätzliche Tabelle__EFMigrationsHistory. In der Tabelle __EFMigrationsHistory wird nachverfolgt, welche Migrationen auf die Datenbank angewendet wurden.
  • Zeigen Sie die Daten in der Tabelle __EFMigrationsHistory an. Es wird eine Zeile für die erste Migration angezeigt.

Die Momentaufnahme des Datenmodells

Migrationen erstellen eine Momentaufnahme des aktuellen Datenmodells in Migrations/SchoolContextModelSnapshot.cs. Wenn eine Migration hinzugefügt wird, bestimmt EF die vorgenommenen Änderungen, indem das aktuelle Datenmodell mit der Momentaufnahmedatei verglichen wird.

Da die Momentaufnahmedatei den Zustand des Datenmodells nachverfolgt, kann eine Migration nicht durch Löschen der Datei <timestamp>_<migrationname>.cs gelöscht werden. Zum Zurücksetzen der neuesten Migration verwenden Sie den Befehl migrations remove. migrations remove löscht die Migration und stellt sicher, dass die Momentaufnahme ordnungsgemäß zurückgesetzt wird. Weitere Informationen finden Sie unter dotnet ef migrations remove.

Informationen zum Entfernen aller Migrationen finden Sie unter Zurücksetzen aller Migrationen .

Anwenden von Migrationen in der Produktionsumgebung

Es wird empfohlen, dass Produktions-Apps Database.Migrate beim Anwendungsstart nicht aufrufen. Migrate sollte nicht aus einer APP aufgerufen werden, die in einer Serverfarm bereitgestellt wird. Wenn die App auf mehrere Serverinstanzen horizontal hochskaliert wird, ist es schwierig sicherzustellen, dass Datenbankschemaaktualisierungen nicht von mehreren Servern erfolgen oder mit Lese-/Schreibzugriffen einen Konflikt verursachen.

Die Datenbankmigration sollte im Rahmen der Bereitstellung und auf kontrollierte Weise erfolgen. Zu den Ansätzen für die Migration von Produktionsdatenbanken zählen die folgenden:

  • Verwendung von Migrationen zur Erstellung von SQL-Skripts und Verwendung der SQL-Skripts bei der Bereitstellung.
  • Ausführen von dotnet ef database update über eine kontrollierte Umgebung.

Problembehandlung

Wenn die App SQL Server LocalDB verwendet und die folgende Ausnahme angezeigt wird:

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

Die Lösung kann die Ausführung von dotnet ef database update an einer Eingabeaufforderung sein.

Zusätzliche Ressourcen

Nächste Schritte

Im nächsten Tutorial wird das Datenmodell erstellt, und es werden Entitätseigenschaften und neue Entitäten hinzugefügt.

In diesem Tutorial wird das EF Core-Migrationsfeature zur Verwaltung von Datenmodelländerungen verwendet.

Wenn nicht zu lösende Probleme auftreten, laden Sie die fertige App herunter.

Wenn eine neue App entwickelt wird, ändert sich das Datenmodell häufig. Nach jeder Modelländerung ist das Modell nicht mehr synchron mit der Datenbank. In diesem Tutorial wurde zunächst Entity Framework für die Erstellung der Datenbank konfiguriert, falls diese nicht vorhanden ist. Bei jeder Datenmodelländerung:

  • Wird die Datenbank gelöscht.
  • Erstellt EF eine neue, dem Modell entsprechende Datenbank.
  • Füllt die App die Datenbank mit Testdaten auf.

Dieser Ansatz, die DB mit dem Datenmodell synchron zu halten, funktioniert gut, bis die App in der Produktion bereitgestellt werden muss. Wenn die App in der Produktionsumgebung ausgeführt wird, speichert sie in der Regel Daten, die verwaltet werden müssen. Jedes Mal, wenn eine Änderung vorgenommen wird (z.B. wenn eine neue Spalte hinzugefügt wird), kann die App nicht mit einer Testdatenbank starten. Mit dem EF Core-Migrationsfeature wird dieses Problem gelöst, indem EF Core das Datenbankschema aktualisiert, anstatt eine neue Datenbank zu erstellen.

Statt die Datenbank bei den Datenmodelländerungen zu löschen und neu zu erstellen, wird bei der Migration eine Aktualisierung des Schemas und eine Beibehaltung vorhandener Daten angestrebt.

Löschen der Datenbank

Verwenden Sie den SQL Server-Objekt-Explorer (SSOX) oder den Befehl database drop:

Führen Sie folgenden Befehl in der Paket-Manager-Konsole aus:

Drop-Database

Führen Sie Get-Help about_EntityFrameworkCore über die Paket-Manager-Konsole aus, um Hilfeinformationen zu erhalten.

Erstellen einer ursprünglichen Migration und Aktualisieren der Datenbank

Erstellen Sie das Projekt und die erste Migration.

Add-Migration InitialCreate
Update-Database

Überprüfen der Methoden „Up“ und „Down“

Der EF Core-Befehl migrations add hat Code zum Erstellen der Datenbank generiert. Der Migrationscode befindet sich in der Migrations\<timestamp>_InitialCreate.cs-Datei. Die Methode Up der Klasse InitialCreate erstellt die Datenbanktabellen, die den Datenmodell-Entitätenmengen entsprechen. Die Methode Down löscht diese, wie im folgenden Beispiel dargestellt:

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");
    }
}

Die Migrationsfunktion ruft die Methode Up auf, um die Datenmodelländerungen für eine Migration zu implementieren. Wenn ein Befehl eingegeben wird, um ein Rollback für das Update auszuführen, ruft die Migrationsfunktion die Down-Methode auf.

Der vorangehende Code ist für die ursprüngliche Migration bestimmt. Dieser Code wurde bei der Ausführung des Befehls migrations add InitialCreate erstellt. Der Parameter für den Migrationsnamen (in dem Beispiel „InitialCreate“) wird für den Dateinamen verwendet. Der Migrationsname kann ein beliebiger gültiger Dateiname sein. Es wird empfohlen, ein Wort oder einen Ausdruck auszuwählen, durch das bzw. den der Hintergrund der Migration widergespiegelt wird. Eine Migration, bei der eine Tabelle mit Fachbereichen hinzugefügt wurde, könnte beispielsweise als „AddDepartmentTable“ bezeichnet werden.

Wenn die ursprüngliche Migration erstellt wird und die Datenbank vorhanden ist:

  • Wird der Code für die Datenbankerstellung generiert.
  • Muss der Code für die Datenbankerstellung nicht ausgeführt werden, da die Datenbank bereits mit dem Datenmodell übereinstimmt. Wenn der Code für die Datenbankerstellung ausgeführt wird, werden keine Änderungen vorgenommen, da die Datenbank bereits mit dem Datenmodell übereinstimmt.

Wenn die App in einer neuen Umgebung bereitgestellt wird, muss der Code für die Datenbankerstellung ausgeführt werden, damit die Datenbank erstellt wird.

Die Datenbank wurde zuvor gelöscht und ist daher nicht vorhanden, darum erstellt die Migration eine neue Datenbank.

Die Momentaufnahme des Datenmodells

Die Migration erstellt unter Migrations/SchoolContextModelSnapshot.cs eine Momentaufnahme des aktuellen Datenbankschemas. Wenn Sie eine Migration hinzufügen, bestimmt EF die vorgenommenen Änderungen, indem das Datenmodell mit der Momentaufnahmedatei verglichen wird.

Verwenden Sie folgenden Befehl, um eine Migration zu löschen:

Remove-Migration

Der Befehl „Remove-Migration“ löscht die Migration und stellt sicher, dass die Momentaufnahme ordnungsgemäß zurückgesetzt wird.

Entfernen von EnsureCreated und Testen der App

Zu einem frühen Entwicklungszeitpunkt wurde EnsureCreated verwendet. In diesem Tutorial werden Migrationen verwendet. Der Befehl EnsureCreated weist folgende Einschränkungen auf:

  • Er umgeht Migrationen und erstellt die Datenbank und das Schema.
  • Er erstellt keine Migrationstabelle.
  • Er kann nicht mit Migrationen verwendet werden.
  • Er dient zum Testen oder schnellen Erstellen von Prototypen an Positionen, an denen die Datenbank häufig gelöscht und neu erstellt wird.

Entfernen Sie EnsureCreated:

context.Database.EnsureCreated();

Führen Sie die App aus, und stellen Sie sicher, dass für die Datenbank ein Seeding durchgeführt wird.

Untersuchen der Datenbank

Verwenden Sie den SQL Server-Objekt-Explorer zur Untersuchung der Datenbank. Beachten Sie die zusätzliche Tabelle__EFMigrationsHistory. In der Tabelle __EFMigrationsHistory wird nachverfolgt, welche Migrationen auf die Datenbank angewendet wurden. Wenn Sie die Daten in der Tabelle __EFMigrationsHistory anzeigen, wird eine Zeile für die erste Migration angezeigt. Im letzten Protokoll im vorherigen Beispiel der CLI-Ausgabe wird die INSERT-Anweisung angezeigt, mit der diese Zeile erstellt wird.

Führen Sie die App aus, und überprüfen Sie, ob alles funktioniert.

Anwenden von Migrationen in der Produktionsumgebung

Es wird empfohlen, dass Produktions-Apps Database.Migrate beim Anwendungsstart nicht aufrufen. Migrate sollte in der Serverfarm nicht über eine App aufgerufen werden. Beispielsweise wenn die App über eine Cloud mit horizontaler Skalierung bereitgestellt wurde (mehrere Instanzen der App werden ausgeführt).

Die Datenbankmigration sollte im Rahmen der Bereitstellung und auf kontrollierte Weise erfolgen. Zu den Ansätzen für die Migration von Produktionsdatenbanken zählen die folgenden:

  • Verwendung von Migrationen zur Erstellung von SQL-Skripts und Verwendung der SQL-Skripts bei der Bereitstellung.
  • Ausführen von dotnet ef database update über eine kontrollierte Umgebung.

EF Core kann anhand der Tabelle __MigrationsHistory sehen, ob Migrationen ausgeführt werden müssen. Wenn die Datenbank auf dem neuesten Stand ist, wird keine Migration ausgeführt.

Problembehandlung

Laden Sie die fertige App herunter.

Die App generiert folgende Ausnahme:

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

Projektmappe: Ausführen von dotnet ef database update

Zusätzliche Ressourcen