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:
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:
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.
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.
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 dieInstructor
Klassen undStudent
so, dass sie von abgeleitet werdenPerson
. - Fügen Sie der Datenbankkontextklasse Modell-zu-Datenbank-Zuordnungscode hinzu.
- Ändern und
InstructorID
StudentID
verweisen Sie im gesamten Projekt aufPersonID
.
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.
-
Wichtig
Schließen Sie zunächst alle geöffneten Dateien in Visual Studio.
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
.Ö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.
Öffnen Sie das Dialogfeld In Dateien ersetzen , und ändern Sie Look inTo All Open Documents .
Verwenden Sie das Dialogfeld In Dateien ersetzen , um alle
InstructorID
in zu ändern.PersonID.
Suchen Sie alle Dateien im Projekt, die enthalten
StudentID
.Ö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.
Öffnen Sie das Dialogfeld In Dateien ersetzen , und ändern Sie Look inTo All Open Documents .
Verwenden Sie das Dialogfeld In Dateien ersetzen , um alle
StudentID
in zu ändernPersonID
.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.
Klicken Sie mit der rechten Maustaste auf die Tabelle „Person“, und klicken Sie anschließend auf Tabellendaten anzeigen, um die Unterscheidungsspalte anzuzeigen.
Das folgende Diagramm veranschaulicht die Struktur der neuen School-Datenbank:
Zusammenfassung
Die Vererbung von Tabellen pro Hierarchie wurde jetzt für die Person
Klassen , Student
und 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.