Freigeben über


Implementieren der Vererbung mit dem Entity Framework in einer ASP.NET MVC-Anwendung (8 von 10)

von Tom Dykstra

Die Contoso University-Beispielwebanwendung veranschaulicht, wie Sie ASP.NET MVC 4-Anwendungen mithilfe von Entity Framework 5 Code First und Visual Studio 2012 erstellen. Informationen zu dieser Tutorialreihe finden Sie im ersten Tutorial der Reihe.

Hinweis

Wenn ein Problem auftritt, das nicht behoben werden kann, laden Sie das abgeschlossene Kapitel herunter , und versuchen Sie, das Problem zu reproduzieren. Im Allgemeinen können Sie die Lösung für das Problem finden, indem Sie Ihren Code mit dem abgeschlossenen Code vergleichen. Einige häufige Fehler und deren Behebung finden Sie unter Fehler und Problemumgehungen.

Im vorherigen Tutorial haben Sie Parallelitätsausnahmen behandelt. In diesem Tutorial erfahren Sie, wie Sie die Vererbung in das Datenmodell implementieren können.

Bei der objektorientierten Programmierung können Sie die Vererbung verwenden, um redundanten Code zu eliminieren. In diesem Tutorial ändern Sie die Klassen Instructor und Student so, dass sie von einer Person-Basisklasse abgeleitet werden, die Eigenschaften wie LastName enthält. Diese Eigenschaften sind für Dozenten und Studenten gängig. Sie fügen keine Webseiten hinzu oder ändern diese, aber Sie werden Teile des Codes ändern. Diese Änderungen werden automatisch in der Datenbank widergespiegelt.

Table-per-Hierarchy versus Table-per-Type-Vererbung

Bei der objektorientierten Programmierung können Sie die Vererbung verwenden, um die Arbeit mit verwandten Klassen zu erleichtern. Beispielsweise verwenden die Instructor Klassen und Student im School Datenmodell mehrere Eigenschaften, was zu redundantem Code führt:

Screenshots: Kursteilnehmer und Kursleiter mit hervorgehobenen redundanten Codes

Angenommen, Sie möchten den redundanten Code für die Eigenschaften löschen, die von den Entitäten Instructor und Student gemeinsam genutzt werden. Sie können eine Person Basisklasse erstellen, die nur diese freigegebenen Eigenschaften enthält, und dann die Instructor Entitäten und Student von dieser Basisklasse erben lassen, wie in der folgenden Abbildung gezeigt:

Screenshot, der die Klassen Student und Instructor zeigt, die von der Person-Klasse abgeleitet sind.

Es gibt mehrere Möglichkeiten, wie diese Vererbungsstruktur in der Datenbank dargestellt werden kann. Beispielsweise könnte die Tabelle Person vorhanden sein, die Informationen zu Studierenden und Lehrkräften in einer einzigen Tabelle enthält. Einige der Spalten können nur für Kursleiter (HireDate), andere nur für Kursteilnehmer (EnrollmentDate), andere für beide (LastName, FirstName) gelten. In der Regel verfügen Sie über eine Diskriminatorspalte , um anzugeben, welchen Typ jede Zeile darstellt. So kann die Unterscheidungsspalte beispielsweise „Instructor“ für Dozenten und „Student“ für Studenten enthalten.

Screenshot: Vererbungsstruktur der Entitätsklasse Person

Dieses Muster zum Generieren einer Entitätsvererbungsstruktur aus einer einzelnen Datenbanktabelle wird als TPH-Vererbung (Table-per-hierarchy ) bezeichnet.

Alternativ kann die Datenbank so gestaltet werden, dass sie mehr wie die Vererbungsstruktur aussieht. Die Tabelle Person könnte beispielsweise nur die Namensfelder aufweisen, während die Datumsfelder in den separaten Tabellen Instructor und Student vorhanden sind.

Screenshot: Neue Datenbanktabellen

Dieses Muster zum Erstellen einer Datenbanktabelle für jede Entitätsklasse wird als TPT-Vererbung ( Table per Type ) bezeichnet.

TPH-Vererbungsmuster bieten im Entity Framework im Allgemeinen eine bessere Leistung als TPT-Vererbungsmuster, da TPT-Muster zu komplexen Joinabfragen führen können. Dieses Tutorial veranschaulicht die Implementierung der TPH-Vererbung. Führen Sie dazu die folgenden Schritte aus:

  • Erstellen Sie eine Person Klasse, und ändern Sie die Instructor Klassen und Student so, dass sie von abgeleitet werden Person.
  • Fügen Sie der Datenbankkontextklasse Modell-zu-Datenbank-Zuordnungscode hinzu.
  • Ändern und InstructorIDStudentID verweisen Sie im gesamten Projekt auf PersonID.

Erstellen der Person-Klasse

Hinweis: Sie können das Projekt nach dem Erstellen der folgenden Klassen erst kompilieren, wenn Sie die Controller aktualisieren, die diese Klassen verwenden.

Erstellen Sie im Ordner ModelsPerson.cs , und ersetzen Sie den Vorlagencode durch den folgenden Code:

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

namespace ContosoUniversity.Models
{
   public abstract class Person
   {
      [Key]
      public int PersonID { get; set; }

      [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
      [StringLength(50, MinimumLength = 1)]
      [Display(Name = "Last Name")]
      public string LastName { get; set; }

      [Column("FirstName")]
      [Display(Name = "First Name")]
      [StringLength(50, MinimumLength = 2, ErrorMessage = "First name must be between 2 and 50 characters.")]
      public string FirstMidName { get; set; }

      public string FullName
      {
         get
         {
            return LastName + ", " + FirstMidName;
         }
      }
   }
}

Leiten Sie in Instructor.cs die Instructor Klasse von der Person Klasse ab, und entfernen Sie die Felder Schlüssel und Name. Der Code sieht aus wie im folgenden Beispiel:

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

namespace ContosoUniversity.Models
{
    public class Instructor : Person
    {
        [DataType(DataType.Date)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

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

Nehmen Sie ähnliche Änderungen an Student.cs vor. Die Student Klasse sieht wie im folgenden Beispiel aus:

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

namespace ContosoUniversity.Models
{
    public class Student : Person
    {
        [DataType(DataType.Date)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

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

Hinzufügen des Personenentitätstyps zum Modell

Fügen Sie in SchoolContext.cs eine DbSet Eigenschaft für den Entitätstyp Person hinzu:

public DbSet<Person> People { get; set; }

Das ist alles, was Entity Framework für die Konfiguration der „Tabelle pro Hierarchie“-Vererbung benötigt. Wie Sie sehen werden, enthält die Datenbank, wenn sie neu erstellt wird, eine Person Tabelle anstelle der Student Tabellen und Instructor .

Ändern von InstructorID und StudentID in PersonID

Ändern MapRightKey("InstructorID")Sie in SchoolContext.cs in der Instructor-Course-Zuordnungsanweisung in MapRightKey("PersonID"):

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

Diese Änderung ist nicht erforderlich. Es ändert nur den Namen der InstructorID-Spalte in der Tabelle mit vielen Verknüpfungen. Wenn Sie den Namen Als InstructorID belassen, funktioniert die Anwendung weiterhin ordnungsgemäß. Hier ist die vollständige SchoolContext.cs:

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; }
      public DbSet<Person> People { 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("PersonID")
                 .ToTable("CourseInstructor"));
      }
   }
}

Als Nächstes müssen Sie im gesamten Projekt zu PersonID und StudentID zu PersonID wechseln InstructorID, mit Ausnahme der Zeitstempelmigrationsdateien im Ordner Migrationen. Dazu suchen Und öffnen Sie nur die Dateien, die geändert werden müssen, und führen sie dann eine globale Änderung für die geöffneten Dateien durch. Die einzige Datei im Ordner Migrationen , die Sie ändern sollten, ist Migration\Configuration.cs.

  1. Wichtig

    Schließen Sie zunächst alle geöffneten Dateien in Visual Studio.

  2. Klicken Sie auf Suchen und Ersetzen – Suchen Sie alle Dateien im Menü Bearbeiten , und suchen Sie dann nach allen Dateien im Projekt, die enthalten InstructorID.

    Screenshot: Fenster

  3. Öffnen Sie jede Datei im Fenster Ergebnisse suchenmit Ausnahme der <Migrationsdateien time-stamp>_.cs im Ordner Migration , indem Sie für jede Datei auf eine Zeile doppelklicken.

    Screenshot: Fenster

  4. Öffnen Sie das Dialogfeld In Dateien ersetzen , und ändern Sie Look inTo All Open Documents .

  5. Verwenden Sie das Dialogfeld In Dateien ersetzen , um alle InstructorID in zu ändern. PersonID.

    Screenshot: Fenster

  6. Suchen Sie alle Dateien im Projekt, die enthalten StudentID.

  7. Öffnen Sie jede Datei im Fenster Ergebnisse suchenmit Ausnahme der <Migrationsdateien time-stamp>_*.cs im Ordner Migration , indem Sie für jede Datei auf eine Zeile doppelklicken.

    Screenshot: Fenster

  8. Öffnen Sie das Dialogfeld In Dateien ersetzen , und ändern Sie Look inTo All Open Documents .

  9. Verwenden Sie das Dialogfeld In Dateien ersetzen , um alle StudentID in zu ändern PersonID.

    Screenshot: Fenster

  10. Erstellen Sie das Projekt.

(Beachten Sie, dass dies einen Nachteil des Musters für die classnameID Benennung von Primärschlüsseln darstellt. Wenn Sie die Id der Primärschlüssel ohne vorangestellt hätten, wäre jetzt keine Umbenennung erforderlich.)

Erstellen und Aktualisieren einer Migrationsdatei

Geben Sie in der Paket-Manager-Konsole (PMC) den folgenden Befehl ein:

Add-Migration Inheritance

Führen Sie den Update-Database Befehl im PMC aus. Der Befehl schlägt an diesem Punkt fehl, da wir über vorhandene Daten verfügen, die von Migrationen nicht zu behandeln sind. Sie erhalten den folgenden Fehler:

Die ALTER TABLE-Anweisung steht in Konflikt mit der FOREIGN KEY-Einschränkung "FK_dbo. Department_dbo. Person_PersonID". Der Konflikt ist in der Datenbank "ContosoUniversity", Tabelle "dbo. Person", Spalte "PersonID".

Migrationen< timestamp>_Inheritance.cs, und ersetzen Sie die Up -Methode durch den folgenden Code:

public override void Up()
{
    DropForeignKey("dbo.Department", "InstructorID", "dbo.Instructor");
    DropForeignKey("dbo.OfficeAssignment", "InstructorID", "dbo.Instructor");
    DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
    DropForeignKey("dbo.CourseInstructor", "InstructorID", "dbo.Instructor");
    DropIndex("dbo.Department", new[] { "InstructorID" });
    DropIndex("dbo.OfficeAssignment", new[] { "InstructorID" });
    DropIndex("dbo.Enrollment", new[] { "StudentID" });
    DropIndex("dbo.CourseInstructor", new[] { "InstructorID" });
    RenameColumn(table: "dbo.Department", name: "InstructorID", newName: "PersonID");
    RenameColumn(table: "dbo.OfficeAssignment", name: "InstructorID", newName: "PersonID");
    RenameColumn(table: "dbo.Enrollment", name: "StudentID", newName: "PersonID");
    RenameColumn(table: "dbo.CourseInstructor", name: "InstructorID", newName: "PersonID");
    CreateTable(
        "dbo.Person",
        c => new
            {
                PersonID = c.Int(nullable: false, identity: true),
                LastName = c.String(maxLength: 50),
                FirstName = c.String(maxLength: 50),
                HireDate = c.DateTime(),
                EnrollmentDate = c.DateTime(),
                Discriminator = c.String(nullable: false, maxLength: 128),
                OldId = c.Int(nullable: false)
            })
        .PrimaryKey(t => t.PersonID);

    // Copy existing Student and Instructor data into new Person table.
    Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, StudentId AS OldId FROM dbo.Student");
    Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, HireDate, null AS EnrollmentDate, 'Instructor' AS Discriminator, InstructorId AS OldId FROM dbo.Instructor");

    // Fix up existing relationships to match new PK's.
    Sql("UPDATE dbo.Enrollment SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = Enrollment.PersonId AND Discriminator = 'Student')");
    Sql("UPDATE dbo.Department SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = Department.PersonId AND Discriminator = 'Instructor')");
    Sql("UPDATE dbo.OfficeAssignment SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = OfficeAssignment.PersonId AND Discriminator = 'Instructor')");
    Sql("UPDATE dbo.CourseInstructor SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = CourseInstructor.PersonId AND Discriminator = 'Instructor')");

    // Remove temporary key
    DropColumn("dbo.Person", "OldId");

    AddForeignKey("dbo.Department", "PersonID", "dbo.Person", "PersonID");
    AddForeignKey("dbo.OfficeAssignment", "PersonID", "dbo.Person", "PersonID");
    AddForeignKey("dbo.Enrollment", "PersonID", "dbo.Person", "PersonID", cascadeDelete: true);
    AddForeignKey("dbo.CourseInstructor", "PersonID", "dbo.Person", "PersonID", cascadeDelete: true);
    CreateIndex("dbo.Department", "PersonID");
    CreateIndex("dbo.OfficeAssignment", "PersonID");
    CreateIndex("dbo.Enrollment", "PersonID");
    CreateIndex("dbo.CourseInstructor", "PersonID");
    DropTable("dbo.Instructor");
    DropTable("dbo.Student");
}

Führen Sie den Befehl update-database erneut aus.

Hinweis

Es ist möglich, andere Fehler beim Migrieren von Daten und beim Vornehmen von Schemaänderungen zu erhalten. Wenn Sie Migrationsfehler erhalten, die Sie nicht beheben können, können Sie mit dem Tutorial fortfahren, indem Sie die Verbindungszeichenfolge in der Web.config-Datei ändern oder die Datenbank löschen. Der einfachste Ansatz besteht darin, die Datenbank in der Web.config-Datei umzubenennen. Ändern Sie beispielsweise den Datenbanknamen in CU_test, wie im folgenden Beispiel gezeigt:

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

Bei einer neuen Datenbank sind keine Daten zu migrieren, und der update-database Befehl wird viel wahrscheinlicher ohne Fehler ausgeführt. Anweisungen zum Löschen der Datenbank finden Sie unter Löschen einer Datenbank aus Visual Studio 2012. Wenn Sie diesen Ansatz verwenden, um mit dem Tutorial fortzufahren, überspringen Sie den Bereitstellungsschritt am Ende dieses Tutorials, da der bereitgestellte Standort denselben Fehler erhält, wenn Migrationen automatisch ausgeführt werden. Wenn Sie einen Migrationsfehler beheben möchten, ist die beste Ressource eines der Entity Framework-Foren oder StackOverflow.com.

Testen

Führen Sie die Website aus, und probieren Sie verschiedene Seiten aus. Alles funktioniert genauso wie vorher.

Erweitern Sie in Server ExplorerSchoolContext und dann Tabellen, und Sie sehen, dass die Tabellen Student und Instructor durch eine Person-Tabelle ersetzt wurden. Erweitern Sie die Tabelle Person , und Sie sehen, dass sie alle Spalten enthält, die sich früher in den Tabellen Student und Instructor befinden.

Screenshot: Fenster

Klicken Sie mit der rechten Maustaste auf die Tabelle „Person“, und klicken Sie anschließend auf Tabellendaten anzeigen, um die Unterscheidungsspalte anzuzeigen.

Screenshot: Tabelle

Das folgende Diagramm veranschaulicht die Struktur der neuen School-Datenbank:

Screenshot: Diagramm der Schuldatenbank

Zusammenfassung

Die Vererbung von Tabellen pro Hierarchie wurde jetzt für die PersonKlassen , Studentund Instructor implementiert. Weitere Informationen zu dieser und anderen Vererbungsstrukturen finden Sie im Blog von Morteza Manavi unter Vererbungszuordnungsstrategien . Im nächsten Tutorial werden einige Möglichkeiten zum Implementieren des Repositorys und der Arbeitseinheitsmuster beschrieben.

Links zu anderen Entity Framework-Ressourcen finden Sie im ASP.NET Data Access Content Map.