Tutorial: Erstellen eines komplexen Datenmodells: ASP.NET MVC mit EF Core

In den vorherigen Tutorials haben Sie mit einem einfachen Datenmodell gearbeitet, das aus drei Entitäten bestand. In diesem Tutorial fügen Sie weitere Entitäten und Beziehungen hinzu und passen das Datenmodell an, indem Sie die Regeln zur Formatierung, Validierung und Datenbankzuordnung angeben.

Wenn Sie dies erledigt haben, bilden die Entitätsklassen ein vollständiges Datenmodell, das in der folgenden Abbildung dargestellt wird:

Entity diagram

In diesem Tutorial:

  • Anpassen des Datenmodells
  • Ändern der Entität „Student“
  • Erstellen der Entität „Instructor“
  • Erstellen der Entität „OfficeAssignment“
  • Ändern der Entität „Course“
  • Erstellen der Entität „Department“
  • Ändern der Entität „Enrollment“
  • Aktualisieren des Datenbankkontexts
  • Füllen der Datenbank mit Testdaten
  • Hinzufügen einer Migration
  • Ändern der Verbindungszeichenfolge
  • Aktualisieren der Datenbank

Voraussetzungen

Anpassen des Datenmodells

In diesem Abschnitt wird das Anpassen des Datenmodells mithilfe von Attributen erläutert, die Regeln zur Formatierung, Validierung und Datenbankzuordnung angeben. In den folgenden Abschnitten erstellen Sie dann das vollständige Datenmodell „School“, indem Sie Attribute zu den bereits erstellten Klassen hinzufügen und neue Klassen für die verbleibenden Entitätstypen im Modell erstellen.

Das DataType-Attribut

Für die Anmeldedaten von Studenten zeigen alle Webseiten derzeit die Zeit und das Datum an, obwohl für dieses Feld nur das Datum relevant ist. Indem Sie Attribute für die Datenanmerkung verwenden, können Sie eine Codeänderungen vornehmen, durch die das Anzeigeformat in jeder Ansicht korrigiert wird, in der die Daten angezeigt werden. Sie können dies beispielhaft testen, indem Sie ein Attribut zur EnrollmentDate-Eigenschaft der Student-Klasse hinzufügen.

Fügen Sie in Models/Student.cs eine using-Anweisung für den System.ComponentModel.DataAnnotations-Namespace hinzu, und fügen Sie wie im folgenden Beispiel dargestellt die Attribute DataType und DisplayFormat zur EnrollmentDate-Eigenschaft hinzu:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Das DataType-Attribut wird verwendet, um einen Datentyp anzugeben, der spezifischer als der datenbankinterne Typ ist. In diesem Fall soll nur das Datum verfolgt werden, nicht das Datum und die Zeit. Die DataType-Enumeration stellt viele Datentypen bereit, wie z.B. „Date“, „Time“, „PhoneNumber“, „Currency“, „EmailAddress“ usw. Das DataType-Attribut kann der Anwendung auch ermöglichen, typspezifische Features bereitzustellen. Beispielsweise kann ein mailto:-Link für DataType.EmailAddress erstellt werden, und eine Datumsauswahl kann in Browsern mit Unterstützung für HTML5 für DataType.Date bereitgestellt werden. Das DataType-Attribut gibt data--Attribute (ausgesprochen „Datadash“) von HTML5 aus, die von HTML5-Browsern genutzt werden können. Die DataType-Attribute bieten keine Validierung.

DataType.Date gibt nicht das Format des Datums an, das angezeigt wird. Standardmäßig wird das Datenfeld gemäß den Standardformaten basierend auf der CultureInfo-Klasse des Servers angezeigt.

Das DisplayFormat-Attribut dient zum expliziten Angeben des Datumsformats:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Die Einstellung ApplyFormatInEditMode gibt an, dass die Formatierung auch angewendet werden soll, wenn der Wert in einem Textfeld zur Bearbeitung angezeigt wird. (Dies könnte für einige Felder nützlich sein, z.B. soll für Währungsangaben das Währungssymbol nicht im Textfeld für die Bearbeitung enthalten sein.)

Sie können das DisplayFormat-Attribut eigenständig verwenden, meistens empfiehlt es sich jedoch, ebenfalls das DataType-Attribut zu verwenden. Das DataType-Attribut übermittelt die Semantik der Daten im Gegensatz zu deren Rendern auf einem Bildschirm. Es bietet folgende Vorteile, die DisplayFormat nicht bietet:

  • Der Browser kann HTML5-Features aktivieren (z.B. zum Anzeigen eines Kalendersteuerelements, des dem Gebietsschema entsprechenden Währungssymbols, von E-Mail-Links, einigen clientseitigen Eingabevalidierungen usw.).

  • Standardmäßig rendert der Browser Daten mit dem ordnungsgemäßen auf Ihrem Gebietsschema basierenden Format.

Weitere Informationen finden Sie in der Dokumentation zum <input>-Taghilfsprogramm.

Führen Sie die App aus, und wechseln Sie zur Indexseite „Studenten“, um festzustellen, dass beim Registrierungsdatum nicht mehr die Uhrzeit angezeigt wird. Dies gilt für jede Ansicht, die das Modell „Student“ verwendet.

Students index page showing dates without times

Das StringLength-Attribut

Sie können ebenfalls Regeln für die Datenvalidierung und Meldungen für Validierungsfehler mithilfe von Attributen angeben. Das StringLength-Attribut legt die maximale Länge in der Datenbank fest und stellt die clientseitige und serverseitige Validierung für ASP.NET Core MVC bereit. Sie können ebenfalls die mindestens erforderliche Zeichenfolgenlänge in diesem Attribut angeben, dieser Wert hat jedoch keine Auswirkung auf das Datenbankschema.

Angenommen, Sie möchten sicherstellen, dass Benutzer nicht mehr als 50 Zeichen für einen Namen eingeben. Fügen Sie zum Hinzufügen dieser Einschränkung wie im folgenden Beispiel dargestellt StringLength-Attribute zu den LastName- und FirstMidName-Eigenschaften hinzu:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50)]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Das StringLength-Attribut verhindert nicht, dass ein Benutzer einen Leerraum als Namen eingibt. Sie können das RegularExpression-Attribut verwenden, um Beschränkungen auf die Eingabe anzuwenden. Folgender Code erfordert beispielsweise, dass das erste Zeichen ein Großbuchstabe sein muss und die restlichen Zeichen alphabetisch sein müssen:

[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]

Das MaxLength-Attribut stellt ähnliche Funktionalitäten wie das StringLength-Attribut bereit, ermöglicht jedoch keine clientseitige Validierung.

Das Datenbankmodell hat sich nun auf eine Weise geändert, die eine Änderung des Datenbankschemas erfordert. Verwenden Sie Migrationen, um das Schema ohne Verlust der Daten zu aktualisieren, die Sie mithilfe der Benutzeroberfläche der Anwendung zur Datenbank hinzugefügt haben.

Speichern Sie Ihre Änderungen, und erstellen Sie das Projekt. Öffnen Sie anschließend das Befehlsfenster im Projektordner, und geben Sie folgenden Befehl ein:

dotnet ef migrations add MaxLengthOnNames
dotnet ef database update

Der migrations add-Befehl gibt eine Warnung aus, dass es zu Datenverlust kommen kann, da die maximale Länge durch die Änderung um zwei Spalten verkürzt wurde. Migrationen erstellen eine Datei mit dem Namen <timeStamp>_MaxLengthOnNames.cs. Diese Datei enthält Code in der Up-Methode, durch den die Datenbank dem aktuellen Datenmodell entsprechend aktualisiert wird. Der Code wurde durch den database update-Befehl ausgeführt.

Der Zeitstempel, der dem Namen der Migrationsdatei vorangestellt ist, wird von Entity Framework verwendet, um die Migrationen zu sortieren. Sie können mehrere Migrationen erstellen, bevor Sie den Befehl „update-database“ ausführen. Anschließend werden alle Migrationen in der Reihenfolge angewendet, in der Sie erstellt wurden.

Führen Sie die App aus, wählen Sie die Registerkarte Students aus, klicken Sie dann auf Neu erstellen, und geben Sie einen beliebigen Namen ein, der länger als 50 Zeichen ist. Die Anwendung sollte Sie daran hindern.

Das Column-Attribut

Sie können ebenfalls Attribute verwenden, um zu steuern, wie Ihre Klassen und Eigenschaften der Datenbank zugeordnet werden. Angenommen, Sie haben den Namen FirstMidName für das Feld „first-name“ verwendet, da das Feld ebenfalls einen Zweitnamen enthalten kann. Sie möchten jedoch, dass die Datenbankspalte in FirstName umbenannt wird, damit Benutzer, die Ad-hob-Abfragen für die Datenbank schreiben, an diesen Namen gewöhnt sind. Sie können das Column-Attribut verwenden, um diese Zuordnung durchzuführen.

Das Column-Attribut gibt an, dass bei der Erstellung der Datenbank die Spalte des Student-Elements, die der FirstMidName-Eigenschaft zugeordnet ist, den Namen FirstName erhält. Wenn Ihr Code also auf Student.FirstMidName verweist, stammen die Daten aus der FirstName-Spalte der Student-Tabelle oder werden in dieser aktualisiert. Wenn Sie keine Spaltennamen angeben, erhalten diese den gleichen Namen wie die Eigenschaft.

Fügen Sie in der Datei Student.cs eine using-Anweisung für System.ComponentModel.DataAnnotations.Schema hinzu, und fügen Sie wie im folgenden hervorgehobenen Code dargestellt das Attribut für den Spaltennamen zur FirstMidName-Eigenschaft hinzu:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50)]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Durch das Hinzufügen des Column-Attributs wird das Modell geändert, das SchoolContext unterstützt, sodass keine Übereinstimmung mit der Datenbank vorliegt.

Speichern Sie Ihre Änderungen, und erstellen Sie das Projekt. Öffnen Sie anschließend das Befehlsfenster im Projektordner, und geben Sie folgenden Befehl ein, um eine weitere Migration zu erstellen:

dotnet ef migrations add ColumnFirstName
dotnet ef database update

Öffnen Sie im SQL Server-Objekt-Explorer den Tabellen-Designer „Student“, indem Sie auf die Tabelle Student doppelklicken.

Students table in SSOX after migrations

Bevor Sie die ersten beiden Migrationen angewendet haben, wiesen die Namensspalten den Typ „nvarchar(MAX)“ auf. Diese besitzen nun den Typ „nvarchar(50)“, und der Spaltenname hat sich von „FirstMidName“ in „FirstName“ geändert.

Hinweis

Wenn Sie vor dem Erstellen aller Entitätsklassen in den folgenden Abschnitten versuchen, zu kompilieren, werden möglicherweise Compilerfehler angezeigt.

Ändern der Entität „Student“

Student entity

Ersetzen Sie in Models/Student.cs den Code, den Sie zuvor hinzugefügt haben, durch folgenden Code. Die Änderungen werden hervorgehoben.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50)]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }
        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Das Attribut „Required“

Durch das Required-Attribut werden die Name-Eigenschaften zu Pflichtfeldern. Das Required-Attribut ist für nicht auf NULL festlegbare Typen wie Werttypen (DateTime, int, double, float usw.) nicht erforderlich. Typen, die nicht auf NULL festgelegt werden können, werden automatisch als Pflichtfelder behandelt.

Das Required-Attribut muss mit MinimumLength verwendet werden, damit die MinimumLength erzwungen wird.

[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

Das Display-Attribut

Das Display-Attribut gibt an, dass die Beschriftung der Textfelder in jeder Instanz „First Name“, „Last Name“, „Full Name“ und „Enrollment Date“ lauten soll, statt dem Eigenschaftsnamen zu entsprechen (bei dem die Wörter nicht durch Leerräume getrennt werden).

Die berechnete FullName-Eigenschaft

Bei FullName handelt es sich um eine berechnete Eigenschaft, die einen Wert zurückgibt, der durch das Verketten von zwei weiteren Eigenschaften erstellt wird. Daher verfügt diese nur über einen get-Accessor, und keine FullName-Spalte wird in der Datenbank generiert.

Erstellen der Entität „Instructor“

Instructor entity

Erstellen Sie Models/Instructor.cs, indem Sie den Vorlagencode durch folgenden Code ersetzen:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Instructor
    {
        public int ID { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        [StringLength(50)]
        public string FirstMidName { get; set; }

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get { return LastName + ", " + FirstMidName; }
        }

        public ICollection<CourseAssignment> CourseAssignments { get; set; }
        public OfficeAssignment OfficeAssignment { get; set; }
    }
}

Beachten Sie, dass einige Eigenschaften in den Entitäten „Student“ und „Instructor“ identisch sind. Im folgenden Tutorial Implementing Inheritance (Implementierung von Vererbung) gestalten Sie diesen Code um, um die Redundanzen zu entfernen.

Sie können mehrere Attribute in einer Zeile einfügen, also können Sie das HireDate-Attribut folgendermaßen schreiben:

[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Die Navigationseigenschaften „CourseAssignments“ und „OfficeAssignment“

Bei den Eigenschaften CourseAssignments und OfficeAssignment handelt es sich um Navigationseigenschaften.

Da ein Dozent eine beliebige Anzahl von Kursen geben kann, wird CourseAssignments als Auflistung definiert.

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Wenn eine Navigationseigenschaft mehrere Entitäten enthalten kann, muss deren Typ aus einer Liste bestehen, in der Einträge hinzugefügt, gelöscht und aktualisiert werden können. Sie können ICollection<T> oder einen Typ wie List<T> oder HashSet<T> angeben. Wenn Sie ICollection<T> angeben, erstellt Entity Framework standardmäßig eine HashSet<T>-Auflistung.

Warum es sich dabei um CourseAssignment-Entitäten handelt, wird im Folgenden im Abschnitt zu m:n-Beziehungen erläutert.

Die Geschäftsregeln der Contoso University besagen, dass ein Dozent maximal ein Büro besitzen kann. Die OfficeAssignment-Eigenschaft enthält also eine einzige OfficeAssignment-Entität (diese kann NULL sein, wenn kein Büro zugewiesen ist).

public OfficeAssignment OfficeAssignment { get; set; }

Erstellen der Entität „OfficeAssignment“

OfficeAssignment entity

Erstellen Sie Models/OfficeAssignment.cs mit dem folgenden Code:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class OfficeAssignment
    {
        [Key]
        public int InstructorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Office Location")]
        public string Location { get; set; }

        public Instructor Instructor { get; set; }
    }
}

Das Key-Attribut

Es besteht 1:0- oder 1:1-Beziehung zwischen den Entitäten Instructor und OfficeAssignment. Eine Bürozuweisung ist nur in Beziehung zu der Lehrkraft vorhanden, der sie zugewiesen ist. Daher ist deren Primärschlüssel auch der Fremdschlüssel für die Entität Instructor. Entity Framework erkennt InstructorID jedoch nicht automatisch als Primärschlüssel dieser Entität, da dessen Name nicht der Namenskonvention ID oder classnameID folgt. Deshalb wird das Key-Attribut verwendet, um „InstructorID“ als Schlüssel zu erkennen:

[Key]
public int InstructorID { get; set; }

Sie können ebenfalls das Key-Attribut verwenden, wenn die Entität einen eigenen primären Schlüssel besitzt, Sie der Eigenschaft jedoch einen anderen Namen als „classnameID“ oder „ID“ geben möchten.

Standardmäßig behandelt Entity Framework den Schlüssel nicht als datenbankgeneriert, da die Spalte für eine identifizierende Beziehung vorgesehen ist.

Die Navigationseigenschaft „Instructor“

Die Entität „Instructor“ verfügt über eine auf NULL festlegbare OfficeAssignment-Navigationseigenschaft (da einem Dozenten möglicherweise kein Büro zugewiesen ist). Die OfficeAssignment-Entität verfügt über eine nicht auf NULL festlegbare Instructor-Navigationseigenschaft (da eine Bürozuweisung nicht ohne einen Dozenten vorhanden sein kann – InstructorID ist nicht auf NULL festlegbar). Wenn die Entität „Instructor“ mit der Entität „OfficeAssignment“ verknüpft ist, besitzt jede Entität in den Navigationseigenschaften einen Verweis auf die andere.

Sie können ein [Required]-Attribut zur Navigationseigenschaft „Instructor“ hinzufügen, um anzugeben, dass ein zugehöriger Dozent vorhanden sein muss. Dies ist jedoch nicht notwendig, da der Fremdschlüssel InstructorID (bei dem es sich ebenfalls um den Schlüssel für diese Tabelle handelt) nicht auf NULL festlegbar ist.

Ändern der Entität „Course“

Course entity

Ersetzen Sie in Models/Course.cs den Code, den Sie zuvor hinzugefügt haben, durch folgenden Code. Die Änderungen werden hervorgehoben.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        [Display(Name = "Number")]
        public int CourseID { get; set; }

        [StringLength(50, MinimumLength = 3)]
        public string Title { get; set; }

        [Range(0, 5)]
        public int Credits { get; set; }

        public int DepartmentID { get; set; }

        public Department Department { get; set; }
        public ICollection<Enrollment> Enrollments { get; set; }
        public ICollection<CourseAssignment> CourseAssignments { get; set; }
    }
}

Die Entität „Course“ besitzt die Fremdschlüsseleigenschaft DepartmentID, die auf die zugehörige Entität „Department“ zeigt und die Navigationseigenschaft Department besitzt.

Entity Framework erfordert nicht, dass Sie eine Fremdschlüsseleigenschaft zu Ihrem Datenmodell hinzufügen, wenn eine Navigationseigenschaft für eine verknüpfte Entität vorhanden ist. Entity Framework erstellt an den erforderlichen Stellen automatisch Fremdschlüssel in der Datenbank und erstellt Schatteneigenschaften für diese. Durch Fremdschlüssel im Datenmodell können Updates einfacher und effizienter durchgeführt werden. Wenn Sie beispielsweise die Entität Course zum Bearbeiten abrufen, weist die Entität Department den Wert NULL auf, wenn Sie diese nicht laden. Wenn Sie also die Entität Course aktualisieren, müssen Sie zuerst die Entität Department abrufen. Wenn die Fremdschlüsseleigenschaft DepartmentID im Datenmodell vorhanden ist, müssen Sie die Entität Department vor dem Update nicht abrufen.

Das Attribut „DatabaseGenerated“

Das DatabaseGenerated-Attribut mit dem None-Parameter der CourseID-Eigenschaft gibt an, dass Primärschlüsselwerte vom Benutzer bereitgestellt und nicht von der Datenbank generiert werden.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Standardmäßig geht Entity Framework davon aus, dass Primärschlüsselwerte von der Datenbank generiert werden. Das ist in den meisten Fällen erwünscht. Für Course-Entitäten verwenden Sie jedoch eine benutzerdefinierte Kursnummer, z. B. eine 1000er-Reihe für eine Abteilung, eine 2000er-Reihe für eine andere – und so weiter.

Das DatabaseGenerated-Attribut kann ebenfalls verwendet werden, um Standardwerte zu generieren. Im Fall von Datenbankspalten wird es verwendet, um das Datum zu erfassen, an dem eine Zeile erstellt oder aktualisiert wurde. Weitere Informationen finden Sie unter Generated Properties (Generierte Eigenschaften).

Fremdschlüssel- und Navigationseigenschaften

Die Fremdschlüssel- und Navigationseigenschaften in der Entität Course stellen folgende Beziehungen dar:

Ein Kurs ist einer Abteilung zugeordnet, sodass es aus den oben genannten Gründen eine DepartmentID-Fremdschlüsseleigenschaft und eine Department-Navigationseigenschaft gibt.

public int DepartmentID { get; set; }
public Department Department { get; set; }

In einem Kurs können beliebig viele Studenten angemeldet sein, darum stellt die Enrollments-Navigationseigenschaft eine Auflistung dar:

public ICollection<Enrollment> Enrollments { get; set; }

Ein Kurs kann von mehreren Dozenten unterrichtet werden, darum stellt die CourseAssignments-Navigationseigenschaft eine Auflistung dar (der Typ CourseAssignment wird später erklärt):

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Erstellen der Entität „Department“

Department entity

Erstellen Sie Models/Department.cs mit dem folgenden Code:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Department
    {
        public int DepartmentID { get; set; }

        [StringLength(50, MinimumLength = 3)]
        public string Name { get; set; }

        [DataType(DataType.Currency)]
        [Column(TypeName = "money")]
        public decimal Budget { get; set; }

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Start Date")]
        public DateTime StartDate { get; set; }

        public int? InstructorID { get; set; }

        public Instructor Administrator { get; set; }
        public ICollection<Course> Courses { get; set; }
    }
}

Das Column-Attribut

Zuvor haben Sie das Column-Attribut verwendet, um die Zuordnung des Spaltennamens zu ändern. Im Code für die Entität Department wird das Attribut Column verwendet, um die SQL-Datentypzuordnung zu ändern, sodass die Spalte mithilfe des SQL Server-Typs money in der Datenbank definiert wird:

[Column(TypeName="money")]
public decimal Budget { get; set; }

Die Spaltenzuordnung ist im Allgemeinen nicht erforderlich, da Entity Framework den geeigneten SQL Server-Datentyp auf dem CLR-Typ basierend auswählt, den Sie für die Eigenschaft definieren. Der CLR-Typ decimal wird dem SQL Server-Typ decimal zugeordnet. In diesem Fall wissen Sie jedoch, dass die Spalte Währungsbeträge enthält. Hierfür ist der Datentyp „money“ besser geeignet.

Fremdschlüssel- und Navigationseigenschaften

Die Fremdschlüssel- und Navigationseigenschaften stellen folgende Beziehungen dar:

Eine Abteilung kann einen Administrator besitzen oder nicht, und bei einem Administrator handelt es sich immer um einen Dozenten. Deshalb wird die InstructorID-Eigenschaft als Fremdschlüssel zur Entität „Instructor“ hinzugefügt, und ein Fragezeichen wird nach der Typbezeichnung int hinzugefügt, um die Eigenschaft als auf NULL festlegbar zu markieren. Die Navigationseigenschaft heißt Administrator, enthält jedoch eine Instructor-Entität:

public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }

Eine Abteilung kann viele Kurse aufweisen, darum gibt es die Navigationseigenschaft „Courses“:

public ICollection<Course> Courses { get; set; }

Hinweis

Gemäß der Konvention aktiviert Entity Framework das kaskadierende Delete für nicht auf NULL festlegbare Fremdschlüssel und für m:n-Beziehungen. Dies kann zu zirkulären Regeln für kaskadierende Deletes führen, wodurch eine Ausnahme ausgelöst wird, wenn Sie versuchen, eine Migration hinzuzufügen. Wenn Sie beispielsweise die Eigenschaft Department.InstructorID nicht als Nullable-Typ definiert haben, würde EF eine Regel für kaskadierende Deletes konfigurieren, um die Abteilung zu löschen, wenn Sie die Lehrkraft löschen. Das entspricht jedoch nicht Ihrer Absicht. Wenn Ihre Geschäftsregeln erfordern, dass die InstructorID-Eigenschaft nicht auf NULL festlegbar ist, müssen Sie folgende Fluent-API-Anweisung verwenden, um kaskadierende Deletes für die Eigenschaft zu deaktivieren:

modelBuilder.Entity<Department>()
   .HasOne(d => d.Administrator)
   .WithMany()
   .OnDelete(DeleteBehavior.Restrict)

Ändern der Entität „Enrollment“

Enrollment entity

Ersetzen Sie in Models/Enrollment.cs den Code, den Sie zuvor hinzugefügt haben, durch folgenden Code:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

Fremdschlüssel- und Navigationseigenschaften

Die Fremdschlüssel- und Navigationseigenschaften stellen folgende Beziehungen dar:

Ein Anmeldungsdatensatz gilt für einen einzelnen Kurs, sodass es eine CourseID-Fremdschlüsseleigenschaft und eine Course-Navigationseigenschaft gibt:

public int CourseID { get; set; }
public Course Course { get; set; }

Ein Anmeldungsdatensatz gilt für einen einzelnen Studenten, sodass es eine StudentID-Fremdschlüsseleigenschaft und eine Student-Navigationseigenschaft gibt:

public int StudentID { get; set; }
public Student Student { get; set; }

m:n-Beziehungen

Es besteht eine m:n-Beziehung zwischen den Entitäten Student und Course, und die Enrollment-Entitätsfunktionen liegen als m:n-Jointabelle mit Nutzdaten in der Datenbank vor. Der Ausdruck „mit Nutzdaten“ bedeutet, dass die Tabelle Enrollment außer den Fremdschlüsseln für die verknüpften Tabellen (in diesem Fall ein Primärschlüssel und eine Grade-Eigenschaft) zusätzliche Daten enthält.

Die folgende Abbildung stellt dar, wie diese Beziehungen in einem Entitätsdiagramm aussehen. (Dieses Diagramm wurde mithilfe der Entity Framework Power Tools für Entity Framework 6.x erstellt. Das Erstellen des Diagramms ist nicht Teil des Tutorials, sondern wird hier nur zur Veranschaulichung verwendet.)

Student-Course many to many relationship

Jede Beziehung weist an einem Ende „1“ und am anderen Ende „*“ auf, wodurch eine 1:n-Beziehung dargestellt wird.

Wenn in der Tabelle Enrollment nicht die Grade-Information enthalten wäre, müsste diese nur die beiden Fremdschlüssel (CourseID und StudentID) enthalten. In diesem Fall würde es sich um eine m:n-Jointabelle ohne Nutzlast (oder um eine reine Jointabelle) in der Datenbank handeln. Die Entitäten Instructor und Course weisen diese Art von m:n-Beziehung auf. Als Nächstes sollten Sie also eine Entitätsklasse erstellen, die als Jointabelle ohne Nutzdaten fungiert.

EF Core unterstützt implizite Jointabellen bei n:n-Beziehungen, dieses Tutorial wurde jedoch nicht für die Verwendung einer impliziten Jointabelle aktualisiert. Weitere Informationen finden Sie unter n:n-Beziehungen, der aktualisierten Version dieses Tutorials für Razor Pages.

Die Entität „CourseAssignment“

CourseAssignment entity

Erstellen Sie Models/CourseAssignment.cs mit dem folgenden Code:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class CourseAssignment
    {
        public int InstructorID { get; set; }
        public int CourseID { get; set; }
        public Instructor Instructor { get; set; }
        public Course Course { get; set; }
    }
}

Namen von Joinentitäten

Eine Jointabelle ist in der Datenbank für die m:n-Beziehung zwischen „Instructor“ und „Course“ erforderlich. Diese muss über eine Entitätenmenge dargestellt werden. Es ist üblich, eine Joinentität EntityName1EntityName2 zu benennen, was in diesem Fall CourseInstructor entsprechen würde. Es wird jedoch empfohlen, einen Namen auszuwählen, der die Beziehung beschreibt. Datenmodelle fangen einfach an und werden dann größer. Mit der Zeit werden Joins ohne Nutzlast Nutzlasten hinzugefügt. Wenn Sie mit einem aussagekräftigen Entitätsnamen beginnen, müssen Sie diesen später nicht ändern. Im Idealfall verfügt die Joinentität über einen eigenen, nachvollziehbaren Namen (der möglicherweise aus nur einem Wort besteht) in der Geschäftsdomäne. „Books“ und „Customers“ könnten beispielsweise über „Ratings“ verknüpft werden. Für diese Beziehung ist CourseAssignment besser als CourseInstructor geeignet.

Zusammengesetzte Schlüssel

Da die Fremdschlüssel nicht auf NULL festlegbar sind und zusammen jede Zeile der Tabelle eindeutig identifizieren, ist kein separater Primärschlüssel erforderlich. Die Eigenschaften InstructorID und CourseID fungieren als zusammengesetzter Primärschlüssel. Die einzige Möglichkeit zum Identifizieren von zusammengesetzten Primärschlüsseln für Entity Framework besteht in der Verwendung der Fluent-API. Dies kann nicht mithilfe von Attributen durchgeführt werden. Weitere Informationen zum Konfigurieren von zusammengesetzten Primärschlüsseln finden Sie im nächsten Abschnitt.

Durch den zusammengesetzten Schlüssel wird sichergestellt, dass nicht mehrere Zeilen für denselben Dozenten und Kurs vorhanden sein können, obwohl es mehrere Zeilen für einen Kurs und mehrere Zeilen für einen Dozenten gibt. Die Joinentität Enrollment definiert ihren eigenen Primärschlüssel, dadurch sind Duplikate dieser Art möglich. Sie können einen eindeutigen Index zu den Feldern mit Fremdschlüsseln hinzufügen, um solche Duplikate zu verhindern, oder Enrollment mit einem zusammengesetzten Primärschlüssel konfigurieren, der CourseAssignment ähnelt. Weitere Informationen finden Sie unter Indizes.

Aktualisieren des Datenbankkontexts

Fügen Sie der Datei Data/SchoolContext.cs den folgenden hervorgehobenen Code zu:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<Instructor> Instructors { get; set; }
        public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
        public DbSet<CourseAssignment> CourseAssignments { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
            modelBuilder.Entity<Department>().ToTable("Department");
            modelBuilder.Entity<Instructor>().ToTable("Instructor");
            modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
            modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");

            modelBuilder.Entity<CourseAssignment>()
                .HasKey(c => new { c.CourseID, c.InstructorID });
        }
    }
}

Durch diesen Code werden neue Entitäten hinzugefügt, und der zusammengesetzte Primärschlüssel der Entität „CourseAssignment“ wird konfiguriert.

Informationen zu einer Fluent-API-Alternative

Der Code in der OnModelCreating-Methode der DbContext-Klasse verwendet die Fluent-API zum Konfigurieren des Verhaltens von Entity Framework. Die API wird als „Fluent“ bezeichnet, da sie häufig durch das Verketten mehrerer Methodenaufrufe zu einer einzigen Anweisung verwendet wird, wie in diesem Beispiel aus der EF Core-Dokumentation:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .IsRequired();
}

In diesem Tutorial verwendet Sie die Fluent-API nur für Datenbankzuordnungen, die nicht mithilfe von Attributen durchgeführt werden können. Sie können die Fluent-API jedoch ebenfalls verwenden, um den Großteil der Regeln für die Formatierung, Validierung und Zuordnung anzugeben, die Sie mithilfe von Attributen festlegen können. Manche Attribute, z.B. MinimumLength, können nicht mit der Fluent-API angewendet werden. Wie bereits erwähnt, ändert MinimumLength das Schema nicht, sondern wendet lediglich eine client- und serverseitige Validierungsregel an.

Manche Entwickler*innen bevorzugen es, nur eine Fluent-API zu verwenden, sodass sie ihre Entitätsklassen „sauber“ halten können. Sie können Attribute und die Fluent-API mischen, wenn Sie möchten. Einige Anpassungen können nur mithilfe der Fluent-API vorgenommen werden, im Allgemeinen wird jedoch empfohlen, einen der beiden Ansätze auszuwählen und diesen so konsistent wie möglich zu verwenden. Wenn Sie beide verwenden, beachten Sie, dass Attribute durch die Fluent-API überschrieben werden, wenn ein Konflikt vorliegt.

Weitere Informationen zu Attributen und Fluent-API im Vergleich finden Sie unter Methods of configuration (Konfigurationsmethoden).

Entitätsdiagramm mit angezeigten Beziehungen

Die folgende Abbildung stellt das Diagramm dar, das von Entity Framework Power Tools für das vollständige Modell „School“ erstellt wird.

Entity diagram

Neben den Linien für 1:n-Beziehungen werden die Linien für 1:0- oder 1:1-Beziehungen zwischen den Entitäten Instructor und OfficeAssignment sowie die Linien für 0:1- oder 0:n-Beziehungen zwischen den Entitäten „Instructor“ und „Department“ dargestellt.

Füllen der Datenbank mit Testdaten

Ersetzen Sie den Code in der Datei Data/DbInitializer.cs durch folgenden Code, um Startwertdaten für die neu erstellten Entitäten bereitzustellen.

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            //context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student { FirstMidName = "Carson",   LastName = "Alexander",
                    EnrollmentDate = DateTime.Parse("2010-09-01") },
                new Student { FirstMidName = "Meredith", LastName = "Alonso",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Arturo",   LastName = "Anand",
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Gytis",    LastName = "Barzdukas",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Yan",      LastName = "Li",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Peggy",    LastName = "Justice",
                    EnrollmentDate = DateTime.Parse("2011-09-01") },
                new Student { FirstMidName = "Laura",    LastName = "Norman",
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Nino",     LastName = "Olivetto",
                    EnrollmentDate = DateTime.Parse("2005-09-01") }
            };

            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var instructors = new Instructor[]
            {
                new Instructor { FirstMidName = "Kim",     LastName = "Abercrombie",
                    HireDate = DateTime.Parse("1995-03-11") },
                new Instructor { FirstMidName = "Fadi",    LastName = "Fakhouri",
                    HireDate = DateTime.Parse("2002-07-06") },
                new Instructor { FirstMidName = "Roger",   LastName = "Harui",
                    HireDate = DateTime.Parse("1998-07-01") },
                new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
                    HireDate = DateTime.Parse("2001-01-15") },
                new Instructor { FirstMidName = "Roger",   LastName = "Zheng",
                    HireDate = DateTime.Parse("2004-02-12") }
            };

            foreach (Instructor i in instructors)
            {
                context.Instructors.Add(i);
            }
            context.SaveChanges();

            var departments = new Department[]
            {
                new Department { Name = "English",     Budget = 350000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Abercrombie").ID },
                new Department { Name = "Mathematics", Budget = 100000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").ID },
                new Department { Name = "Engineering", Budget = 350000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").ID },
                new Department { Name = "Economics",   Budget = 100000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").ID }
            };

            foreach (Department d in departments)
            {
                context.Departments.Add(d);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
                },
            };

            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var officeAssignments = new OfficeAssignment[]
            {
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
                    Location = "Smith 17" },
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
                    Location = "Gowan 27" },
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
                    Location = "Thompson 304" },
            };

            foreach (OfficeAssignment o in officeAssignments)
            {
                context.OfficeAssignments.Add(o);
            }
            context.SaveChanges();

            var courseInstructors = new CourseAssignment[]
            {
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Harui").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Harui").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
                    },
            };

            foreach (CourseAssignment ci in courseInstructors)
            {
                context.CourseAssignments.Add(ci);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    Grade = Grade.A
                },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                    Grade = Grade.C
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                        StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                        StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B
                    },
                new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Li").ID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Justice").ID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B
                    }
            };

            foreach (Enrollment e in enrollments)
            {
                var enrollmentInDataBase = context.Enrollments.Where(
                    s =>
                            s.Student.ID == e.StudentID &&
                            s.Course.CourseID == e.CourseID).SingleOrDefault();
                if (enrollmentInDataBase == null)
                {
                    context.Enrollments.Add(e);
                }
            }
            context.SaveChanges();
        }
    }
}

Wie im ersten Tutorial dargestellt werden durch diesen Code überwiegend neue Entitätsobjekte dargestellt und für die Tests erforderliche Beispieldaten in Eigenschaften geladen. Achten Sie darauf, wie die m:n-Beziehungen verarbeitet werden: Der Code erstellt Beziehungen, indem Entitäten in den Joinentitätenmengen Enrollments und CourseAssignment erstellt werden.

Hinzufügen einer Migration

Speichern Sie Ihre Änderungen, und erstellen Sie das Projekt. Öffnen Sie dann das Befehlsfenster im Projektordner, und geben Sie den Befehl migrations add ein (verwenden Sie den Befehl „update-database“ noch nicht):

dotnet ef migrations add ComplexDataModel

Sie erhalten eine Warnung über möglichen Datenverlust.

An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'

Wenn Sie versucht haben, den Befehl database update zu diesem Zeitpunkt auszuführen (führen Sie diesen noch nicht aus), erhalten Sie folgende Fehlermeldung:

Die ALTER TABLE-Anweisung steht in Konflikt mit der FOREIGN KEY-Einschränkung „FK_dbo.Course_dbo.Department_DepartmentID“. Der Konflikt trat in der „ContosoUniversity“-Datenbank, Tabelle „dbo.Department“, Spalte „DepartmentID“ auf.

In einigen Fällen müssen Sie beim Ausführen von Migrationen mit vorhandenen Daten Stub-Daten in die Datenbank einfügen, um die Fremdschlüsseleinschränkungen zu erfüllen. Der generierte Code in der Methode Up fügt den nicht auf NULL festlegbaren Fremdschlüssel DepartmentID zur Tabelle Course hinzu. Wenn beim Ausführen des Codes bereits Zeilen in der Tabelle „Course“ vorhanden sind, schlägt der AddColumn-Vorgang fehl, da SQL Server keinen Wert in eine Spalte einfügen kann, die nicht NULL sein darf. In diesem Tutorial führen Sie die Migration auf eine neue Datenbank durch. In einer Produktionsanwendung müssten Sie jedoch dafür sorgen, dass die Migration vorhandene Daten verarbeiten kann. Ein Beispiel hierfür finden Sie in den folgenden Anweisungen.

Damit diese Migration mit vorhandenen Daten funktioniert, müssen Sie den Code ändern, damit dieser der neuen Spalte einen Standardwert zuweist, und eine Stub-Abteilung namens „Temp“ erstellen, die als Standardabteilung fungiert. Folglich sind alle vorhandenen Course-Zeilen mit der Abteilung „Temp“ verknüpft, nachdem die Methode Up ausgeführt wurde.

  • Öffnen Sie die Datei {timestamp}_ComplexDataModel.cs.

  • Kommentieren Sie die Codezeile aus, die die Spalte „DepartmentID“ zur Tabelle „Course“ hinzufügt.

    migrationBuilder.AlterColumn<string>(
        name: "Title",
        table: "Course",
        maxLength: 50,
        nullable: true,
        oldClrType: typeof(string),
        oldNullable: true);
                
    //migrationBuilder.AddColumn<int>(
    //    name: "DepartmentID",
    //    table: "Course",
    //    nullable: false,
    //    defaultValue: 0);
    
  • Fügen Sie folgenden hervorgehobenen Code nach dem Code hinzu, der die Tabelle „Department“ erstellt:

    migrationBuilder.CreateTable(
        name: "Department",
        columns: table => new
        {
            DepartmentID = table.Column<int>(nullable: false)
                .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
            Budget = table.Column<decimal>(type: "money", nullable: false),
            InstructorID = table.Column<int>(nullable: true),
            Name = table.Column<string>(maxLength: 50, nullable: true),
            StartDate = table.Column<DateTime>(nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Department", x => x.DepartmentID);
            table.ForeignKey(
                name: "FK_Department_Instructor_InstructorID",
                column: x => x.InstructorID,
                principalTable: "Instructor",
                principalColumn: "ID",
                onDelete: ReferentialAction.Restrict);
        });
    
    migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    // Default value for FK points to department created above, with
    // defaultValue changed to 1 in following AddColumn statement.
    
    migrationBuilder.AddColumn<int>(
        name: "DepartmentID",
        table: "Course",
        nullable: false,
        defaultValue: 1);
    

In einer Produktionsanwendung würden Sie Code oder Skripts schreiben, um Department-Zeilen hinzuzufügen und die Course-Zeilen mit den neuen Department-Zeilen zu verknüpfen. Danach benötigen Sie die Abteilung „Temp“ und den Standardwert für die Spalte Course.DepartmentID nicht mehr.

Speichern Sie Ihre Änderungen, und erstellen Sie das Projekt.

Ändern der Verbindungszeichenfolge

Sie haben nun neuen Code zur DbInitializer-Klasse hinzugefügt, der Startwertdaten für neue Entitäten zu einer leeren Datenbank hinzufügt. Ändern Sie den Namen der Datenbank in der Verbindungszeichenfolge in appsettings.json in „ContosoUniversity3“ oder in einen anderen Namen, der auf Ihrem Computer bisher nicht verwendet wird, damit Entity Framework eine neue, leere Datenbank erstellt.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

Speichern Sie die Änderungen in appsettings.json .

Hinweis

Alternativ zum Ändern des Datenbanknamens können Sie die Datenbank auch löschen. Verwenden Sie den SQL Server-Objekt-Explorer (SSOX) oder den CLI-Befehl database drop:

dotnet ef database drop

Aktualisieren der Datenbank

Nachdem Sie den Datenbanknamen geändert oder die Datenbank gelöscht haben, führen Sie den Befehl database update im Befehlsfenster aus, um die Migrationen auszuführen.

dotnet ef database update

Führen Sie die App aus, damit die DbInitializer.Initialize-Methode ausgeführt wird und die neue Datenbank auffüllt.

Öffnen Sie die Datenbank wie zuvor im SSOX, und erweitern Sie den Knoten Tabellen, um festzustellen, dass alle Tabellen erstellt wurden. (Wenn Sie SSOX immer noch geöffnet haben, klicken Sie auf die Schaltfläche Aktualisieren.)

Tables in SSOX

Führen Sie die App aus, um den Initialisierungscode auszulösen, der das Seeding für die Datenbank ausführt.

Klicken Sie mit der rechten Maustaste auf die Tabelle CourseAssignment, und klicken Sie auf Daten anzeigen, um zu überprüfen, dass diese Daten enthält.

CourseAssignment data in SSOX

Abrufen des Codes

Download or view the completed app (Herunterladen oder anzeigen der vollständigen App).

Nächste Schritte

In diesem Tutorial:

  • Datenmodell wurde angepasst
  • Entität „Student“ wurde geändert
  • Entität „Instructor“ wurde erstellt
  • Entität „OfficeAssignment“ wurde erstellt
  • Entität „Course“ wurde geändert
  • Entität „Department“ wurde erstellt
  • Entität „Enrollment“ wurde geändert
  • Datenbankkontext wurde aktualisiert
  • Datenbank wurde mit Testdaten gefüllt
  • Migration wurde hinzugefügt
  • Verbindungszeichenfolge wurde geändert
  • Datenbank wurde aktualisiert

Fahren Sie mit dem nächsten Tutorial fort, um weitere Informationen über den Zugriff auf verwandte Daten zu erhalten.