Freigeben über


Lernprogramm: Erstellen eines komplexeren Datenmodells für eine ASP.NET MVC-App

In den vorherigen Lernprogrammen haben Sie mit einem einfachen Datenmodell gearbeitet, das aus drei Entitäten besteht. In diesem Lernprogramm fügen Sie weitere Entitäten und Beziehungen hinzu und passen das Datenmodell an, indem Sie Formatierungs-, Validierungs- und Datenbankzuordnungsregeln angeben. In diesem Artikel werden zwei Möglichkeiten zum Anpassen des Datenmodells gezeigt: durch Hinzufügen von Attributen zu Entitätsklassen und Hinzufügen von Code zur Datenbankkontextklasse.

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

School_class_diagram

In diesem Tutorial:

  • Anpassen des Datenmodells
  • Schülerentität aktualisieren
  • Erstellen der Entität „Instructor“
  • Erstellen der Entität „OfficeAssignment“
  • Ändern der Kursentität
  • Erstellen der Entität „Department“
  • Ändern der Entität „Enrollment“
  • Hinzufügen von Code zum Datenbankkontext
  • Füllen der Datenbank mit Testdaten
  • Hinzufügen einer Migration
  • 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. 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 System.ComponentModel.DataAnnotations Namespace hinzu, und fügen Sie der EnrollmentDate Eigenschaft Attribute hinzu DataType DisplayFormat, 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 virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Das DataType-Attribut wird verwendet, um einen spezifischeren Datentyp als den systeminternen Datenbanktyp anzugeben. In diesem Fall soll nur das Datum verfolgt werden, nicht das Datum und die Zeit. Die DataType-Aufzählung stellt viele Datentypen bereit, z . B. Datum, Uhrzeit, PhoneNumber, Währung, EmailAddress und 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 für DataType.Date in Browsern bereitgestellt werden, die HTML5 unterstützen. Die DataType-Attribute geben HTML 5-Daten - (ausgeprägte Datenstrich)-Attribute 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 ApplyFormatInEditMode Einstellung gibt an, dass die angegebene Formatierung auch angewendet werden soll, wenn der Wert in einem Textfeld zum Bearbeiten angezeigt wird. (Möglicherweise möchten Sie dies für einige Felder nicht, z. B. für Währungswerte, das Währungssymbol im Textfeld zur Bearbeitung nicht verwenden.)

Sie können das DisplayFormat-Attribut selbst verwenden, in der Regel ist es jedoch ratsam, auch das DataType-Attribut zu verwenden. Das DataType Attribut vermittelt die Semantik der Daten im Gegensatz zum Rendern auf einem Bildschirm und bietet die folgenden Vorteile, die Sie nicht erhalten DisplayFormat:

  • 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 mithilfe des richtigen Formats basierend auf Ihrem Gebietsschema.
  • Mit dem DataType-Attribut kann MVC die richtige Feldvorlage auswählen, um die Daten zu rendern (das DisplayFormat verwendet die Zeichenfolgenvorlage). Weitere Informationen finden Sie unter Brad Wilsons ASP.NET MVC 2 Templates. (Obwohl für MVC 2 geschrieben, gilt dieser Artikel weiterhin für die aktuelle Version von ASP.NET MVC.)

Wenn Sie das DataType Attribut mit einem Datumsfeld verwenden, müssen Sie das DisplayFormat Attribut auch angeben, um sicherzustellen, dass das Feld in Chrome-Browsern ordnungsgemäß gerendert wird. Weitere Informationen finden Sie in diesem StackOverflow-Thread.

Weitere Informationen zum Behandeln anderer Datumsformate in MVC finden Sie in der MVC 5-Einführung: Untersuchen der Bearbeitungsmethoden und Bearbeiten der Ansicht und Suche auf der Seite nach "Internationalisierung".

Führen Sie die Seite "Kursteilnehmerindex" erneut aus, und beachten Sie, dass zeiten für die Anmeldedaten nicht mehr angezeigt werden. Das gleiche gilt für jede Ansicht, die das Student Modell verwendet.

Students_index_page_with_formatted_date

The StringLengthAttribute

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 clientseitige und serverseitige Überprüfung für ASP.NET 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. Um diese Einschränkung hinzuzufügen, fügen Sie stringLength-Attribute zu den LastName und FirstMidName Eigenschaften 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; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual 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. Der folgende Code erfordert beispielsweise, dass das erste Zeichen Großbuchstaben und die verbleibenden Zeichen alphabetisch sein müssen:

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

Das MaxLength-Attribut bietet ähnliche Funktionen wie das StringLength-Attribut , bietet aber keine clientseitige Überprüfung.

Führen Sie die Anwendung aus, und klicken Sie auf die Registerkarte "Kursteilnehmer ". Folgende Fehlermeldung wird angezeigt:

Das Modell, das den Kontext "SchoolContext" unterstützt, wurde seit der Erstellung der Datenbank geändert. Erwägen Sie die Verwendung von Code First-Migrationen zum Aktualisieren der Datenbank (https://go.microsoft.com/fwlink/?LinkId=238269).

Das Datenbankmodell hat sich auf eine Weise geändert, die eine Änderung des Datenbankschemas erfordert, und Entity Framework hat erkannt, dass dies erfolgt. Sie verwenden Migrationen, um das Schema zu aktualisieren, ohne daten zu verlieren, die Sie der Datenbank mithilfe der Benutzeroberfläche hinzugefügt haben. Wenn Sie Daten geändert haben, die von der Seed Methode erstellt wurden, wird dies aufgrund der AddOrUpdate-Methode , die Sie in der Seed Methode verwenden, wieder in den ursprünglichen Zustand geändert. (AddOrUpdate entspricht einem Upsert-Vorgang aus der Datenbankterminologie.)

Geben Sie die folgenden Befehle in die Paket-Manager-Konsole ein:

add-migration MaxLengthOnNames
update-database

Der add-migration Befehl erstellt 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 update-database-Befehl 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 update-database Befehl ausführen, und dann werden alle Migrationen in der Reihenfolge angewendet, in der sie erstellt wurden.

Führen Sie die Seite "Erstellen" aus, und geben Sie einen der Namen ein, der länger als 50 Zeichen ist. Wenn Sie auf "Erstellen" klicken, zeigt die clientseitige Überprüfung eine Fehlermeldung an: Das Feld "Nachname" muss eine Zeichenfolge mit einer maximalen Länge von 50 sein.

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 sie denselben Namen wie der Eigenschaftsname.

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

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, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Das Hinzufügen des Column-Attributs ändert das Modell, das schoolContext zurückgibt, sodass es nicht mit der Datenbank übereinstimmt. Geben Sie die folgenden Befehle im PMC ein, um eine weitere Migration zu erstellen:

add-migration ColumnFirstName
update-database

Öffnen Sie im Server-Explorer den Tabellen-Designer "Student", indem Sie auf die Tabelle "Student" doppelklicken.

Die folgende Abbildung zeigt den ursprünglichen Spaltennamen wie vor der Anwendung der ersten beiden Migrationen. Neben dem Spaltennamen, der von FirstMidName zu FirstName" geändert wird , haben sich die beiden Namensspalten von MAX Länge zu 50 Zeichen geändert.

Zwei Screenshots, die die Unterschiede in den Tabellen

Sie können auch Änderungen an der Datenbankzuordnung mithilfe der Fluent-API vornehmen, wie Sie weiter unten in diesem Lernprogramm sehen.

Hinweis

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

Schülerentität aktualisieren

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, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [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 virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Das erforderliche Attribut

Das Attribut "Required " macht die Nameneigenschaften erforderlich. Dies Required attribute ist für Werttypen wie DateTime, Int, Double und Float nicht erforderlich. Werttypen können keine NULL-Werte zugewiesen werden, daher werden sie inhärent als erforderliche Felder 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; }

MinimumLength und Required lassen Leerzeichen zu, um die Überprüfung zu bestehen. Verwenden Sie das RegularExpression Attribut für die vollständige Kontrolle über die Zeichenfolge.

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

Erstellen der Entität „Instructor“

Erstellen Sie Modelle\Instructor.cs, und ersetzen Sie 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 virtual ICollection<Course> Courses { get; set; }
        public virtual 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 eine Zeile setzen, sodass Sie auch den Kursleiter wie folgt schreiben können:

public class Instructor
{
   public int ID { get; set; }

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

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

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

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

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

Die Kurs- und OfficeAssignment-Navigationseigenschaften

Bei den Eigenschaften Courses und OfficeAssignment handelt es sich um Navigationseigenschaften. Wie bereits erläutert, werden sie in der Regel als virtuell definiert, sodass sie ein Entity Framework-Feature nutzen können, das als faules Laden bezeichnet wird. Wenn eine Navigationseigenschaft mehrere Entitäten enthalten kann, muss der Typ die ICollection<T-Schnittstelle> implementieren. Beispiel: IList<T> qualifiziert, aber nicht IEnumerable<T>, da IEnumerable<T> Add nicht implementiert wird.

Ein Kursleiter kann eine beliebige Anzahl von Kursen unterrichten, also Courses als Eine Sammlung von Course Entitäten definiert.

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

Unsere Geschäftsregeln geben an, dass ein Kursleiter höchstens ein Büro haben kann. Dies ist also OfficeAssignment als einzelne OfficeAssignment Entität definiert (dies kann sein null , wenn kein Büro zugewiesen ist).

public virtual OfficeAssignment OfficeAssignment { get; set; }

Erstellen der Entität „OfficeAssignment“

Erstellen Sie Modelle\OfficeAssignment.cs mit dem folgenden Code:

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

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

        public virtual Instructor Instructor { get; set; }
    }
}

Erstellen Sie das Projekt, das Ihre Änderungen speichert und überprüft, ob Sie keine Kopier- und Einfügefehler vorgenommen haben, die der Compiler abfangen kann.

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. Das Entity Framework kann jedoch nicht automatisch als Primärschlüssel dieser Entität erkannt werden InstructorID , da der Name nicht der ID Benennungskonvention für Klassennamen ID entspricht. Deshalb wird das Key-Attribut verwendet, um „InstructorID“ als Schlüssel zu erkennen:

[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }

Sie können das Key Attribut auch verwenden, wenn die Entität über einen eigenen Primärschlüssel verfügt, aber Sie die Eigenschaft anders benennen möchten als classnameID oder ID. Standardmäßig behandelt EF den Schlüssel als nicht datenbankgeneriert, da die Spalte für eine identifizierende Beziehung bestimmt ist.

Das ForeignKey-Attribut

Wenn eine 1:0-oder-1-Beziehung oder eine 1:1-Beziehung zwischen zwei Entitäten (z. B. zwischen OfficeAssignment und Instructor) vorhanden ist, kann EF nicht herausfinden, welches Ende der Beziehung der Prinzipal und welches Ende abhängig ist. 1:1-Beziehungen weisen eine Referenznavigationseigenschaft in jeder Klasse auf die andere Klasse auf. Das ForeignKey-Attribut kann auf die abhängige Klasse angewendet werden, um die Beziehung herzustellen. Wenn Sie das ForeignKey-Attribut weglassen, wird beim Versuch, die Migration zu erstellen, die folgende Fehlermeldung angezeigt:

Das Hauptende einer Zuordnung zwischen den Typen "ContosoUniversity.Models.OfficeAssignment" und "ContosoUniversity.Models.Instructor" kann nicht ermittelt werden. Das Prinzipalende dieser Zuordnung muss explizit mithilfe der Beziehungs-Fluent-API oder Datenanmerkungen konfiguriert werden.

Später im Lernprogramm erfahren Sie, wie Sie diese Beziehung mit der Fluent-API konfigurieren.

Die Instructor Navigation-Eigenschaft

Die Instructor Entität verfügt über eine nullfähige OfficeAssignment Navigationseigenschaft (da ein Kursleiter möglicherweise keine Bürozuweisung hat), und die OfficeAssignment Entität verfügt über eine nicht nullable Instructor Navigationseigenschaft (da eine Bürozuweisung nicht ohne Einen Kursleiter InstructorID vorhanden sein kann - ist nicht nullable). Wenn eine Instructor Entität über eine zugehörige OfficeAssignment Entität verfügt, weist jede Entität einen Verweis auf das andere in der Navigationseigenschaft auf.

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

Ändern der Kursentität

Ersetzen Sie in "Models\Course.cs" den Code, den Sie zuvor hinzugefügt haben, durch den folgenden Code:

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 virtual Department Department { get; set; }
      public virtual ICollection<Enrollment> Enrollments { get; set; }
      public virtual ICollection<Instructor> Instructors { get; set; }
   }
}

Die Kursentität verfügt über eine Fremdschlüsseleigenschaft DepartmentID , die auf die zugehörige Department Entitä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. Durch Fremdschlüssel im Datenmodell können Updates einfacher und effizienter durchgeführt werden. Wenn Sie z. B. eine Kursentität zum Bearbeiten abrufen, ist die Department Entität null, wenn Sie sie nicht laden. Wenn Sie die Kursentität aktualisieren, müssen Sie die Department Entität zuerst abrufen. Wenn die Fremdschlüsseleigenschaft DepartmentID im Datenmodell vorhanden ist, müssen Sie die Entität Department vor dem Update nicht abrufen.

Das DatabaseGenerated-Attribut

Das DatabaseGenerated-Attribut mit dem Parameter None 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; }

Standardmäßig geht das 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.

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 virtual Department Department { get; set; }
    
  • In einem Kurs können beliebig viele Studenten angemeldet sein, darum stellt die Enrollments-Navigationseigenschaft eine Auflistung dar:

    public virtual ICollection<Enrollment> Enrollments { get; set; }
    
  • Ein Kurs kann von mehreren Dozenten unterrichtet werden, darum stellt die Instructors-Navigationseigenschaft eine Auflistung dar:

    public virtual ICollection<Instructor> Instructors { get; set; }
    

Erstellen der Entität „Department“

Erstellen Sie Modelle\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 virtual Instructor Administrator { get; set; }
      public virtual ICollection<Course> Courses { get; set; }
   }
}

Das Column-Attribut

Zuvor haben Sie das Column-Attribut verwendet, um die Spaltennamenszuordnung zu ändern. Im Code für die Department Entität wird das Attribut verwendet, um die Column SQL-Datentypzuordnung so zu ändern, dass die Spalte mithilfe des SQL Server-Geldtyps in der Datenbank definiert wird:

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

Die Spaltenzuordnung ist im Allgemeinen nicht erforderlich, da das Entity Framework normalerweise den entsprechenden SQL Server-Datentyp basierend auf dem CLR-Typ 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, und der Datentyp "Geld " ist dafür besser geeignet. Weitere Informationen zu CLR-Datentypen und deren Übereinstimmung mit SQL Server-Datentypen finden Sie unter SqlClient für Entity FrameworkTypes.

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. Daher wird die InstructorID Eigenschaft als Fremdschlüssel für die Instructor Entität eingeschlossen, und nach der int Typbezeichnung wird ein Fragezeichen hinzugefügt, um die Eigenschaft als Nullwerte zu kennzeichnen. Die Navigationseigenschaft wird benannt Administrator , enthält jedoch eine Instructor Entität:

    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
    
  • Eine Abteilung verfügt möglicherweise über viele Kurse, daher gibt es eine Courses Navigationseigenschaft:

    public virtual 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 die Department.InstructorID Eigenschaft z. B. nicht als Nullwerte definiert haben, erhalten Sie die folgende Ausnahmemeldung: "Die referenzielle Beziehung führt zu einem zyklischen Verweis, der nicht zulässig ist." Wenn Für Ihre Geschäftsregeln eine Eigenschaft erforderlich InstructorID ist, die nicht nullwertebar ist, müssen Sie die folgende Fluent-API-Anweisung verwenden, um die Löschweitergabe für die Beziehung zu deaktivieren:

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

Ändern der Entität „Enrollment“

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

using System.ComponentModel.DataAnnotations;

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 virtual Course Course { get; set; }
        public virtual 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 virtual 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 virtual Student Student { get; set; }
    

n: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. Dies 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; das Erstellen des Diagramms ist nicht Teil des Lernprogramms, es wird nur hier als Abbildung 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 entspricht sie einer n:n-Verknüpfungstabelle ohne Nutzlast (oder eine reine Verknüpfungstabelle) in der Datenbank, und Sie müssten überhaupt keine Modellklasse dafür erstellen. Die Instructor Entitäten Course haben diese Art von n:n-Beziehung, und wie Sie sehen können, gibt es keine Entitätsklasse zwischen ihnen:

Instructor-Course_many-to-many_relationship

Eine Verknüpfungstabelle ist jedoch in der Datenbank erforderlich, wie im folgenden Datenbankdiagramm dargestellt:

Instructor-Course_many-to-many_relationship_tables

Das Entity Framework erstellt die CourseInstructor Tabelle automatisch, und Sie lesen und aktualisieren sie indirekt, indem Sie die Instructor.Courses Eigenschaften Course.Instructors und Navigationseigenschaften lesen und aktualisieren.

Entitätsbeziehungsdiagramm

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

School_data_model_diagram

Neben den n:n-Beziehungslinien (* zu *) und den 1:n-Beziehungslinien (1 bis *) können Sie hier die 1:0-1-Beziehungslinie (1 bis 0,.1) zwischen den Instructor Entitäten und OfficeAssignment den Entitäten und der 1:1:n-Beziehungslinie (0,.1 bis *) zwischen den Entitäten "Ausbilder" und "Abteilung" sehen.

Hinzufügen von Code zum Datenbankkontext

Als Nächstes fügen Sie der Klasse die neuen Entitäten hinzu SchoolContext und passen einige der Zuordnungen mithilfe von Fluent-API-Aufrufen an. Die API ist "fluent", da sie häufig durch Zeichenfolgen einer Reihe von Methodenaufrufen in eine einzelne Anweisung verwendet wird, wie im folgenden Beispiel gezeigt:

modelBuilder.Entity<Course>()
     .HasMany(c => c.Instructors).WithMany(i => i.Courses)
     .Map(t => t.MapLeftKey("CourseID")
         .MapRightKey("InstructorID")
         .ToTable("CourseInstructor"));

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, MinimumLength ändert 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.

Ersetzen Sie den Code in DAL\SchoolContext.cs durch den folgenden Code, um dem Datenmodell die neuen Entitäten hinzuzufügen und eine Datenbankzuordnung durchzuführen, die Sie nicht mithilfe von Attributen ausgeführt haben:

using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace ContosoUniversity.DAL
{
   public class SchoolContext : DbContext
   {
      public DbSet<Course> Courses { get; set; }
      public DbSet<Department> Departments { get; set; }
      public DbSet<Enrollment> Enrollments { get; set; }
      public DbSet<Instructor> Instructors { get; set; }
      public DbSet<Student> Students { get; set; }
      public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

         modelBuilder.Entity<Course>()
             .HasMany(c => c.Instructors).WithMany(i => i.Courses)
             .Map(t => t.MapLeftKey("CourseID")
                 .MapRightKey("InstructorID")
                 .ToTable("CourseInstructor"));
      }
   }
}

Die neue Anweisung in der OnModelCreating-Methode konfiguriert die n:n-Verknüpfungstabelle:

  • Für die m:n-Beziehung zwischen den Instructor Und-Entitäten Course gibt der Code die Tabellen- und Spaltennamen für die Verknüpfungstabelle an. Code First kann die m:n-Beziehung für Sie ohne diesen Code konfigurieren. Wenn Sie dies jedoch nicht aufrufen, erhalten Sie Standardnamen wie InstructorInstructorID z. B. für die InstructorID Spalte.

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Instructors).WithMany(i => i.Courses)
        .Map(t => t.MapLeftKey("CourseID")
            .MapRightKey("InstructorID")
            .ToTable("CourseInstructor"));
    

Der folgende Code enthält ein Beispiel dafür, wie Sie die Fluent-API anstelle von Attributen verwendet haben könnten, um die Beziehung zwischen den Entitäten und OfficeAssignment den Instructor Entitäten anzugeben:

modelBuilder.Entity<Instructor>()
    .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

Informationen dazu, welche "fluent API"-Anweisungen hinter den Kulissen ausführen, finden Sie im Blogbeitrag der Fluent-API .

Füllen der Datenbank mit Testdaten

Ersetzen Sie den Code in der Datei "Migration\Configuration.cs " durch den folgenden Code, um Seeddaten für die neuen Entitäten bereitzustellen, die Sie erstellt haben.

namespace ContosoUniversity.Migrations
{
    using ContosoUniversity.Models;
    using ContosoUniversity.DAL;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    
    internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(SchoolContext context)
        {
            var students = new List<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") }
            };

            students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var instructors = new List<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") }
            };
            instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var departments = new List<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 }
            };
            departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
            context.SaveChanges();

            var courses = new List<Course>
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
            };
            courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
            context.SaveChanges();

            var officeAssignments = new List<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" },
            };
            officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, s));
            context.SaveChanges();

            AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
            AddOrUpdateInstructor(context, "Chemistry", "Harui");
            AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
            AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");

            AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
            AddOrUpdateInstructor(context, "Trigonometry", "Harui");
            AddOrUpdateInstructor(context, "Composition", "Abercrombie");
            AddOrUpdateInstructor(context, "Literature", "Abercrombie");

            context.SaveChanges();

            var enrollments = new List<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();
        }

        void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
        {
            var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
            var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
            if (inst == null)
                crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
        }
    }
}

Wie Sie im ersten Lernprogramm gesehen haben, aktualisiert der Großteil dieses Codes einfach neue Entitätsobjekte oder erstellt neue Entitätsobjekte und lädt Beispieldaten nach Bedarf zum Testen in Eigenschaften. Beachten Sie jedoch, wie die Course Entität, die eine n:n-Beziehung mit der Instructor Entität aufweist, behandelt wird:

var courses = new List<Course>
{
    new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
      DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
      Instructors = new List<Instructor>() 
    },
    ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

Wenn Sie ein Course Objekt erstellen, initialisieren Sie die Instructors Navigationseigenschaft mithilfe des Codes Instructors = new List<Instructor>()als leere Auflistung. Dadurch können Mithilfe der Methode Entitäten hinzugefügt Instructor werden, die mit dieser Course Instructors.Add verknüpft sind. Wenn Sie keine leere Liste erstellt haben, können Sie diese Beziehungen nicht hinzufügen, da die Instructors Eigenschaft null wäre und keine Methode hätte Add . Sie können dem Konstruktor auch die Listeninitialisierung hinzufügen.

Hinzufügen einer Migration

Geben Sie im PMC den add-migration Befehl ein (führen Sie den update-database Befehl noch nicht aus):

add-Migration ComplexDataModel

Wenn Sie versucht haben, den Befehl update-database 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.

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, und das müssen Sie jetzt tun. Der generierte Code in der ComplexDataModel-Methode Up fügt der Course Tabelle einen nicht nullablen DepartmentID Fremdschlüssel hinzu. Da beim Ausführen des Codes bereits Zeilen in der Course Tabelle vorhanden sind, schlägt der AddColumn Vorgang fehl, da SQL Server nicht weiß, welcher Wert in die Spalte eingefügt werden soll, die nicht null sein kann. Daher müssen Sie den Code ändern, um der neuen Spalte einen Standardwert zu geben, und erstellen Sie eine Stubabteilung mit dem Namen "Temp", um als Standardabteilung zu fungieren. Daher werden vorhandene Course Zeilen nach ausführung der Up Methode mit der Abteilung "Temp" verknüpft. Sie können sie mit den richtigen Abteilungen in der Seed Methode verknüpfen.

Bearbeiten Sie den <Zeitstempel>_ComplexDataModel.cs Datei, kommentieren Sie die Codezeile aus, in der die Spalte "DepartmentID" zur Tabelle "Kurs" hinzugefügt wird, und fügen Sie den folgenden hervorgehobenen Code hinzu (die kommentierte Zeile ist ebenfalls hervorgehoben):

CreateTable(
        "dbo.CourseInstructor",
        c => new
            {
                CourseID = c.Int(nullable: false),
                InstructorID = c.Int(nullable: false),
            })
        .PrimaryKey(t => new { t.CourseID, t.InstructorID })
        .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
        .ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
        .Index(t => t.CourseID)
        .Index(t => t.InstructorID);

    // Create  a department for course to point to.
    Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    //  default value for FK points to department created above.
    AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1)); 
    //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));

    AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));

Wenn die Seed Methode ausgeführt wird, fügt sie Zeilen in die Department Tabelle ein, und es bezieht sich auf vorhandene Course Zeilen zu diesen neuen Department Zeilen. Wenn Sie keine Kurse in der Benutzeroberfläche hinzugefügt haben, benötigen Sie die Abteilung "Temp" oder den Standardwert in der Course.DepartmentID Spalte nicht mehr. Um die Möglichkeit zu ermöglichen, dass jemand Kurse mithilfe der Anwendung hinzugefügt hat, sollten Sie auch den Seed Methodencode aktualisieren, um sicherzustellen, dass alle Course Zeilen (nicht nur die von früheren Läufen der Seed Methode eingefügten) gültige DepartmentID Werte haben, bevor Sie den Standardwert aus der Spalte entfernen und die Abteilung "Temp" löschen.

Aktualisieren der Datenbank

Nachdem Sie die Bearbeitung des <Zeitstempels> abgeschlossen haben_ComplexDataModel.cs Datei, geben Sie den update-database Befehl in der PMC-Datei ein, um die Migration auszuführen.

update-database

Hinweis

Es ist möglich, andere Fehler beim Migrieren von Daten zu erhalten und Schemaänderungen vorzunehmen. Wenn Sie Migrationsfehler erhalten, die Sie nicht beheben können, können Sie den Datenbanknamen in der Verbindungszeichenfolge ändern oder die Datenbank löschen. Der einfachste Ansatz besteht darin, die Datenbank in der Datei "Web.config " umzubenennen. Im folgenden Beispiel wird der Name in CU_Test geändert:

<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;" 
      providerName="System.Data.SqlClient" />

Bei einer neuen Datenbank gibt es keine Zu migrierenden Daten, und der update-database Befehl wird wahrscheinlicher ohne Fehler abgeschlossen. Anweisungen zum Löschen der Datenbank finden Sie unter How to Drop a Database from Visual Studio 2012.

Wenn dies fehlschlägt, können Sie versuchen, die Datenbank erneut zu initialisieren, indem Sie den folgenden Befehl in das PMC eingeben:

update-database -TargetMigration:0

Öffnen Sie die Datenbank wie zuvor im Server-Explorer , und erweitern Sie den Tabellenknoten , um zu sehen, dass alle Tabellen erstellt wurden. (Wenn Sie immer noch Der Server-Explorer wird aus der früheren Zeit geöffnet, und klicken Sie auf die Schaltfläche "Aktualisieren ".)

Screenshot des Fensters

Sie haben keine Modellklasse für die CourseInstructor Tabelle erstellt. Wie bereits erläutert, ist dies eine Verknüpfungstabelle für die m:n-Beziehung zwischen den Instructor Und-Entitäten Course .

Klicken Sie mit der rechten Maustaste auf die CourseInstructor Tabelle, und wählen Sie " Tabellendaten anzeigen" aus, um zu überprüfen, ob sie daten als Ergebnis der Instructor Entitäten enthält, die Sie der Course.Instructors Navigationseigenschaft hinzugefügt haben.

Table_data_in_CourseInstructor_table

Abrufen des Codes

Abgeschlossenes Projekt herunterladen

Zusätzliche Ressourcen

Links zu anderen Entity Framework-Ressourcen finden Sie im ASP.NET Datenzugriff – Empfohlene Ressourcen.

Nächste Schritte

In diesem Tutorial:

  • Angepasstes Datenmodell
  • Aktualisierte Schülerentität
  • Entität „Instructor“ wurde erstellt
  • Entität „OfficeAssignment“ wurde erstellt
  • Die Kursentität wurde geändert.
  • Die Abteilungsentität wurde erstellt.
  • Die Registrierungsentität wurde geändert.
  • Code zum Datenbankkontext hinzugefügt
  • Datenbank wurde mit Testdaten gefüllt
  • Migration wurde hinzugefügt
  • Datenbank wurde aktualisiert

Wechseln Sie zum nächsten Artikel, um zu erfahren, wie Sie verwandte Daten lesen und anzeigen, die das Entity Framework in Navigationseigenschaften lädt.