Lernprogramm: Erstellen eines komplexen Datenmodells – ASP.NET MVC mitEF Core

In den vorherigen Lernprogrammen haben Sie mit einem einfachen Datenmodell gearbeitet, das aus drei Entitäten bestand. In diesem Lernprogramm fügen Sie weitere Entitäten und Beziehungen hinzu, und Sie passen das Datenmodell an, indem Sie Formatierungs-, Validierungs- und Datenbankzuordnungsregeln angeben.

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

Entitätsdiagramm

In diesem Tutorial lernen Sie:

  • Anpassen des Datenmodells
  • Änderungen an der Entität "Student" vornehmen
  • Erstellen der Entität „Instructor“
  • OfficeAssignment-Entität erstellen
  • Kursentität ändern
  • Abteilungsentität erstellen
  • Ändern der Entität „Enrollment“
  • Aktualisieren des Datenbankkontexts
  • Datenbank mit Testdaten befüllen
  • Hinzufügen einer Migration
  • Verbindungszeichenfolge ändern
  • Datenbank aktualisieren

Prerequisites

Anpassen des Datenmodells

In diesem Abschnitt wird das Anpassen des Datenmodells mithilfe von Attributen erläutert, die Regeln zur Formatierung, Validierung und Datenbankzuordnung angeben. Anschließend erstellen Sie in mehreren der folgenden Abschnitte das vollständige School-Datenmodell, indem Sie den bereits erstellten Klassen Attribute 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 Namespace System.ComponentModel.DataAnnotations hinzu, und fügen Sie der Eigenschaft DataType die Attribute EnrollmentDate und DisplayFormat hinzu, wie im folgenden Beispiel gezeigt:

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, z. B. Datum, Uhrzeit, PhoneNumber, Währung, EmailAddress und vieles mehr. 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 HTML 5 data--Attribute (ausgesprochen data dash) aus, die HTML 5-Browser verstehen können. Die DataType Attribute stellen keine Überprüfung bereit.

DataType.Date gibt nicht das Format des Datums an, das angezeigt wird. Standardmäßig wird das Datenfeld entsprechend den Standardformaten basierend auf der CultureInfo 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. (Möglicherweise möchten Sie dies für einige Felder nicht – z. B. bei Währungswerten, wo Sie das Währungssymbol im Textfeld bei der Bearbeitung möglicherweise nicht anzeigen möchten.)

Sie können das DisplayFormat Attribut selbst verwenden, aber im Allgemeinen ist es ratsam, das DataType Attribut auch zu verwenden. Das DataType Attribut vermittelt die Semantik der Daten im Gegensatz dazu, wie sie auf einem Bildschirm dargestellt werden, und bietet die folgenden Vorteile, die Sie mit DisplayFormat nicht erhalten:

  • Der Browser kann HTML5-Features aktivieren (z. B. zum Anzeigen eines Kalendersteuerelements, des gebietsspezifischen Währungssymbols, E-Mail-Links, einiger clientseitiger Eingabeüberprüfung usw.).

  • Standardmäßig rendert der Browser Daten im korrekten, auf Ihrer Region basierenden Format.

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

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

Indexseite „Studierende“ mit Datumsangaben ohne Uhrzeit

Das StringLength-Attribut

Sie können auch Datenüberprüfungsregeln und Überprüfungsfehlermeldungen mithilfe von Attributen angeben. Das StringLength Attribut legt die maximale Länge in der Datenbank fest und stellt eine clientseitige und serverseitige Überprüfung 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 Leerzeichen für einen Namen eingibt. Sie können das RegularExpression Attribut verwenden, um Einschrä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 Funktionen bereit, die dem StringLength Attribut ähneln, aber keine clientseitige Überprüfung bereitstellen.

Das Datenbankmodell hat sich nun so geändert, dass eine Änderung des Datenbankschemas erforderlich ist. Sie verwenden Migrationen, um das Schema zu aktualisieren, ohne daten zu verlieren, die Sie möglicherweise mithilfe der Anwendungs-UI zur Datenbank hinzugefügt haben.

Speichern Sie Ihre Änderungen, und erstellen Sie das Projekt. Öffnen Sie dann das Befehlsfenster im Projektordner, und geben Sie die folgenden Befehle ein:

dotnet ef migrations add MaxLengthOnNames
dotnet ef database update

Der migrations add Befehl warnt, dass Datenverlust auftreten kann, da die Änderung die maximale Länge für zwei Spalten verkürzt. Migrationen erstellen eine Datei mit dem Namen <timeStamp>_MaxLengthOnNames.cs. Diese Datei enthält Code in der Up-Methode, der die Datenbank entsprechend dem aktuellen Datenmodell aktualisiert. Der database update Befehl hat diesen Code ausgeführt.

Der Zeitstempel, der dem Migrationsdateinamen vorangestellt ist, wird von Entity Framework verwendet, um die Migrationen zu ordnen. Sie können mehrere Migrationen erstellen, bevor Sie den Befehl "update-database" ausführen, und dann 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 verhindern, dass Sie dies tun.

Das Spaltenattribut

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, werden sie mit demselben Namen wie der Eigenschaftsname versehen.

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

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

Das Hinzufügen des Column Attributs ändert das Modell, das dem SchoolContext zugrunde liegt, sodass es nicht mit der Datenbank übereinstimmt.

Speichern Sie Ihre Änderungen, und erstellen Sie das Projekt. Öffnen Sie dann das Befehlsfenster im Projektordner, und geben Sie die folgenden Befehle 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.

Tabelle „Students“ im SSOX nach der Migration

Bevor Sie die ersten beiden Migrationen angewendet haben, waren die Namensspalten vom Typ nvarchar(MAX). Sie sind jetzt nvarchar(50) und der Spaltenname wurde von "FirstMidName" in "FirstName" geändert.

Note

Wenn Sie versuchen zu kompilieren, bevor Sie die Erstellung aller Entitätsklassen in den folgenden Abschnitten abgeschlossen haben, erhalten Sie möglicherweise Compilerfehler.

Änderungen an der Entität "Student"

Entität „Student“

Ersetzen Sie in Models/Student.cs den Code, den Sie zuvor hinzugefügt haben, durch den 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 Namenseigenschaften zu Pflichtfeldern. Das Required Attribut ist für nicht nullable Typen wie Werttypen (DateTime, int, double, float usw.) nicht erforderlich. Typen, die nicht null sein 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 für die Textfelder "Vorname", "Nachname", "Vollständiger Name" und "Registrierungsdatum" anstelle des Eigenschaftsnamens in jeder Instanz sein soll (ohne Leerzeichen für die Wörter).

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 sie nur über einen Get-Accessor, und in der Datenbank wird keine FullName Spalte generiert.

Erstellen der Entität „Instructor“

Instructor-Entität

Erstellen Sie Models/Instructor.cs und ersetzen Sie dabei den Vorlagencode durch den folgenden Code:

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 mehrere Eigenschaften in den Entitäten "Student" und "Instructor" identisch sind. Im „Vererbung implementieren“-Lernprogramm weiter unten in dieser Reihe werden Sie diesen Code umgestalten, um die Redundanz zu vermeiden.

Sie können mehrere Attribute in eine Zeile einfügen, sodass Sie auch die HireDate Attribute wie folgt schreiben können:

[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 ihr Typ eine Liste sein, 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 EF standardmäßig eine HashSet<T>-Auflistung.

Der Grund, warum es sich bei diesen um CourseAssignment Entitäten handelt, wird unten im Abschnitt zu m:n-Beziehungen erläutert.

Die Geschäftsregeln der Contoso University geben an, dass ein Dozent nur höchstens ein Büro haben kann, sodass die OfficeAssignment-Eigenschaft eine einzelne OfficeAssignment-Entität enthält (die null sein kann, wenn kein Büro zugewiesen ist).

public OfficeAssignment OfficeAssignment { get; set; }

OfficeAssignment-Entität erstellen

OfficeAssignment-Entität

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 eine Eins-zu-Null-oder-Eins-Beziehung zwischen den Entitäten Instructor und OfficeAssignment. Eine Bürozuweisung existiert nur in Bezug auf die Lehrkraft, der sie zugeteilt ist. Daher ist ihr Primärschlüssel zugleich der Fremdschlüssel zur Entität Instructor. Das Entity Framework kann jedoch InstructorID nicht automatisch als Primärschlüssel dieser Entität erkennen, da sein Name nicht der Benennungskonvention ID oder classnameID folgt. Deshalb wird das Key-Attribut verwendet, um es als Schlüssel zu identifizieren.

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

Sie können das Key Attribut auch verwenden, wenn die Entität zwar einen eigenen Primärschlüssel hat, Sie die Eigenschaft aber etwas anderes als classnameID oder ID benennen möchten.

Standardmäßig behandelt EF den Schlüssel als nicht-datenbankgeneriert, da die Spalte für eine identifizierende Beziehung bestimmt ist.

Die Navigationseigenschaft „Instructor“

Die Instructor-Entität verfügt über eine NULL-fähige OfficeAssignment-Navigationseigenschaft (da ein Instructor möglicherweise keine Bürozuweisung hat), und die OfficeAssignment-Entität verfügt über eine nicht NULL-fähige Instructor-Navigationseigenschaft (da eine Office-Zuordnung ohne einen Instructor nicht vorhanden sein kann -- InstructorID ist nicht NULL-fähig). Wenn eine Kursleiterentität über eine zugehörige OfficeAssignment-Entität verfügt, verfügt jede Entität über einen Verweis auf die andere in der Navigationseigenschaft.

Sie können ein [Required] Attribut für die Navigationseigenschaft "Instructor" einfügen, um anzugeben, dass ein zugehöriger Instructor vorhanden sein muss, aber Sie müssen dies nicht tun, da der InstructorID Fremdschlüssel (der auch der Schlüssel zu dieser Tabelle ist) nicht nullfähig ist.

Kursobjekt ändern

Kursentität

Ersetzen Sie in Models/Course.cs den Code, den Sie zuvor hinzugefügt haben, durch den 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 Kursentität verfügt über eine Fremdschlüsseleigenschaft DepartmentID , die auf die zugehörige Abteilungsentität verweist und über eine Department Navigationseigenschaft verfügt.

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. EF erstellt automatisch Fremdschlüssel in der Datenbank, wo sie benötigt werden, und erstellt Schatteneigenschaften für sie. Durch Fremdschlüssel im Datenmodell können Updates einfacher und effizienter durchgeführt werden. Wenn Sie beispielsweise eine Course zu bearbeitende Entität abrufen, ist die Department Entität null, wenn Sie sie nicht laden. Wenn Sie die Course Entität also aktualisieren, müssten Sie zuerst die Department Entität 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 für die CourseID Eigenschaft gibt an, dass Primärschlüsselwerte vom Benutzer bereitgestellt werden, anstatt von der Datenbank generiert zu werden.

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

Entity Framework geht standardmäßig 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 auch verwendet werden, um Standardwerte zu generieren, wie bei Datenbankspalten, die zum Aufzeichnen des Datums, an dem eine Zeile erstellt oder aktualisiert wurde, verwendet werden. Weitere Informationen finden Sie unter 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 Kursleitern unterrichtet werden, sodass die CourseAssignments Navigationseigenschaft eine Sammlung ist (der Typ CourseAssignment wird später erläutert):

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

Erstellen der Entität „Department“

Entität „Department“

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 Spaltenattribut

Zuvor haben Sie das Column-Attribut zum Ändern der Spaltennamenszuordnung verwendet. Im Code für die Entität Department wird das Attribut Column verwendet, um die SQL-Datentypzuordnung so zu ändern, dass 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 das Entity Framework basierend auf dem CLR-Typ, den Sie für die Eigenschaft definieren, den entsprechenden SQL Server-Datentyp auswählt. 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, und der Datentyp "Geld" ist dafür 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; }

Note

Gemäß der Konvention ermöglicht das Entity Framework die kaskadierende Löschung für nicht-nullbare Fremdschlüssel und für viele-zu-viele-Beziehungen. Dies kann zu zirkulären Löschregeln führen, die eine Ausnahme verursachen, 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 für Ihre Geschäftsregeln die InstructorID Eigenschaft nicht nullfähig sein muss, müssen Sie die folgende Fluent API-Anweisung verwenden, um die Löschweitergabe für die Beziehung zu deaktivieren:

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

Ändern der Entität „Enrollment“

Registrierungseinheit

Ersetzen Sie in Models/Enrollment.cs den Code, den Sie zuvor hinzugefügt haben, durch den 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; }

Viele-zu-Viele-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. "Mit Nutzlast" bedeutet, dass die Enrollment Tabelle neben Fremdschlüsseln für die verknüpften Tabellen zusätzliche Daten enthält (in diesem Fall ein Primärschlüssel und eine Grade Eigenschaft).

Die folgende Abbildung stellt dar, wie diese Beziehungen in einem Entitätsdiagramm aussehen. (Dieses Diagramm wurde mithilfe der Entity Framework Power Tools für EF 6.x generiert. Das Erstellen des Diagramms ist nicht Teil des Lernprogramms, es wird hier nur als Illustration verwendet.)

m:n-Beziehung zwischen „Student“ und „Course“

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äre es eine n:n-Verknüpfungstabelle ohne Nutzlast (oder eine reine Verknüpfungstabelle) in der Datenbank. Die Instructor- und Course-Entitäten weisen diese Art von m:n-Beziehung auf, und der nächste Schritt besteht darin, eine Entitätsklasse zu erstellen, die als Verknüpfungstabelle ohne Nutzlast funktioniert.

EF Core unterstützt implizite Verknüpfungstabellen für m:n-Beziehungen, dieses Lernprogramm wurde jedoch nicht aktualisiert, um eine implizite Verknüpfungstabelle zu verwenden. Siehe n:n-Beziehungen, die Razor Seitenversion dieses Lernprogramms, die aktualisiert wurde.

Die Entität „CourseAssignment“

Entität „CourseAssignment“

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

Entitätsnamen verknüpfen

Eine Join-Tabelle ist in der Datenbank für die n:n-Beziehung „Dozent-zu-Kursen“ erforderlich und muss durch einen Entitätssatz dargestellt werden. Es ist üblich, eine Verknüpfungsentität als EntityName1EntityName2 zu bezeichnen; in diesem Fall wäre das CourseInstructor. Es wird jedoch empfohlen, einen Namen auszuwählen, der die Beziehung beschreibt. Datenmodelle beginnen einfach und wachsen, wobei payloadfreie Verknüpfungen später häufig Payloads erhalten. Wenn Sie mit einem beschreibenden Entitätsnamen beginnen, müssen Sie den Namen später nicht ändern. Im Idealfall verfügt die Joinentität über einen eigenen, natürlichen Namen (der möglicherweise aus nur einem Wort besteht) in der Geschäftsdomäne. Beispielsweise könnten Bücher und Kunden über Bewertungen verknüpft werden. Für diese Beziehung ist CourseAssignment eine bessere Wahl als CourseInstructor.

Zusammengesetzter Schlüssel

Da die Fremdschlüssel nicht nullfähig sind und zusammen jede Zeile der Tabelle eindeutig identifizieren, ist kein separater Primärschlüssel erforderlich. Die Eigenschaften InstructorID und CourseID sollten als zusammengesetzter Primärschlüssel funktionieren. Die einzige Möglichkeit, zusammengesetzte Primärschlüssel für EF zu identifizieren, besteht darin, die Fluent-API zu verwenden (dies kann nicht mithilfe von Attributen erfolgen). Im nächsten Abschnitt erfahren Sie, wie Sie den zusammengesetzten Primärschlüssel konfigurieren.

Der zusammengesetzte Schlüssel stellt sicher, dass Sie zwar mehrere Zeilen für einen Kurs und mehrere Zeilen für einen Kursleiter haben können, aber nicht mehrere Zeilen für denselben Kursleiter und Kurs haben können. Die Enrollment Verknüpfungsentität definiert ihren eigenen Primärschlüssel, sodass Duplikate dieser Sorte möglich sind. Um solche Duplikate zu verhindern, können Sie einen Unique-Index auf den Fremdschlüsselfeldern hinzufügen oder Enrollment mit einem zusammengesetzten Primärschlüssel wie CourseAssignment konfigurieren. Weitere Informationen finden Sie unter Indizes.

Aktualisieren des Datenbankkontexts

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

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

Dieser Code fügt die neuen Entitäten hinzu und konfiguriert den zusammengesetzten Primärschlüssel der CourseAssignment-Entität.

Informationen zu einer Fluent-API-Alternative

Der Code in der OnModelCreating Methode der DbContext Klasse verwendet die Fluent-API zum Konfigurieren des EF-Verhaltens. Die API wird als "fluent" bezeichnet, da sie häufig verwendet wird, indem eine Reihe von Methodenaufrufen zu einer einzigen Anweisung verkettet 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 Lernprogramm verwenden Sie die Fluent-API nur für die Datenbankzuordnung, die Sie nicht mit Attributen ausführen 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, es wendet nur eine client- und serverseitige Gültigkeitsprüfungsregel 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.

Entitätsdiagramm

Neben den 1:n-Beziehungslinien (1 bis *) können Sie hier die 1:0..1-Beziehungslinie (1 bis 0..1) zwischen den Entitäten Instructor und OfficeAssignment sowie die 0..1:*-Beziehungslinie (0..1 bis *) zwischen den Entitäten Instructor und Department sehen.

Datenbank mit Testdaten befüllen

Ersetzen Sie den Code in der Data/DbInitializer.cs Datei durch den folgenden Code, um Seeddaten für die neuen Entitäten bereitzustellen, die Sie erstellt haben.

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 Sie im ersten Lernprogramm gesehen haben, erstellt der Großteil dieses Codes einfach neue Entitätsobjekte und lädt Beispieldaten nach Bedarf zum Testen in Eigenschaften. Beachten Sie, wie die m:n-Beziehungen behandelt werden: Der Code erstellt Beziehungen, indem Entitäten in den Join-Entitätssätzen 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 migrations add Befehl ein (führen Sie den Befehl „update-database“ noch nicht aus):

dotnet ef migrations add ComplexDataModel

Sie erhalten eine Warnung über mögliche Datenverluste.

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 an diesem Punkt versuchen würden, den database update Befehl auszuführen (führen Sie ihn jetzt noch nicht aus), würden Sie die folgende Fehlermeldung erhalten:

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.

Manchmal müssen Sie beim Ausführen von Migrationen mit vorhandenen Daten Stubdaten in die Datenbank einfügen, um Fremdschlüsseleinschränkungen zu erfüllen. Der generierte Code in der Up Methode fügt der DepartmentID Tabelle einen nicht nullfähigen Course Fremdschlüssel hinzu. Wenn beim Ausführen des Codes bereits Zeilen in der Tabelle "Kurs" vorhanden sind, schlägt der AddColumn Vorgang fehl, da SQL Server nicht weiß, welcher Wert in der Spalte eingefügt werden soll, die nicht null sein kann. In diesem Lernprogramm führen Sie die Migration in einer neuen Datenbank aus, aber in einer Produktionsanwendung müssen Sie die Migration so anpassen, dass sie vorhandene Daten verarbeitet, daher zeigen die folgenden Anweisungen ein Beispiel dafür, wie das geht.

Damit diese Migration mit vorhandenen Daten funktioniert, müssen Sie den Code ändern, um der neuen Spalte einen Standardwert zu geben, und eine Stubabteilung mit dem Namen "Temp" erstellen, die als Standardabteilung fungiert. Daher werden vorhandene Kurszeilen nach ausführung der Up Methode mit der Abteilung "Temp" verknüpft.

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

  • Kommentieren Sie die Codezeile aus, die der Tabelle "Kurs" die Spalte "DepartmentID" 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 den folgenden hervorgehobenen Code nach dem Code hinzu, der die Abteilungstabelle 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.

Verbindungszeichenfolge ändern

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 .

Note

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

dotnet ef database drop

Datenbank aktualisieren

Nachdem Sie den Datenbanknamen geändert oder die Datenbank gelöscht haben, führen Sie den database update Befehl 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 und die neue Datenbank aufgefüllt wird.

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

Tabellen im SSOX

Führen Sie die App aus, um den Initialisierungscode auszulösen, der die Datenbank mit Seed-Daten befüllt.

Klicken Sie mit der rechten Maustaste auf die Tabelle CourseAssignment und wählen Sie Daten anzeigen aus, um sicherzustellen, dass sie Daten enthält.

CourseAssignment-Daten im SSOX

Code abrufen

Laden Sie die fertige Anwendung herunter, oder zeigen Sie sie an.

Nächste Schritte

In diesem Tutorial lernen Sie:

  • Datenmodell angepasst
  • Änderungen an der Entität "Student" vorgenommen
  • Instructor-Entität erstellt
  • OfficeAssignment-Entität wurde erstellt
  • Entität „Course“ wurde geändert
  • Entität „Department“ wurde erstellt
  • Entität „Enrollment“ wurde geändert
  • Datenbankkontext aktualisiert
  • Datenbank mit Testdaten befüllt
  • Migration hinzugefügt
  • Verbindungszeichenfolge geändert
  • Datenbank wurde aktualisiert

Wechseln Sie zum nächsten Lernprogramm, um mehr über den Zugriff auf verwandte Daten zu erfahren.