In the previous tutorial you handled concurrency exceptions. This
tutorial will show you how to implement inheritance in the data model.
In object-oriented programming, you can use inheritance to eliminate redundant code. In this tutorial, you'll change the
Suppose you want to eliminate the redundant code for the properties that are shared by the
There are several ways this inheritance structure could be represented in the database. You could have a
This pattern of generating an entity inheritance structure from a single database table is called table-per-hierarchy (TPH) inheritance.
An alternative is to make the database look more like the inheritance structure. For example, you could have only the name fields in the
This pattern of making a database table for each entity class is called table per type (TPT) inheritance.
TPH inheritance patterns generally deliver better performance in the Entity Framework than TPT inheritance patterns, because TPT patterns can result in complex join queries. This tutorial demonstrates how to implement TPH inheritance. You'll do that by performing the following steps:
Next, perform a global change (all files in the project) to change
In InstructorController.cs, delete the three occurrences of the following line of code:
In Solution Explorer, double-click School.sdf to open the database in Server Explorer. Expand School.sdf and then Tables, and you see that the Student and Instructor tables have been replaced by a Person table. Expand the Person table and you see that it has all of the columns that used to be in the Student and Instructor tables, plus the discriminator column.
The following diagram illustrates the structure of the new School database:
Table-per-hierarchy inheritance has now been implemented for the
In object-oriented programming, you can use inheritance to eliminate redundant code. In this tutorial, you'll change the
Instructor
and Student
classes so that they derive from a Person
base class which contains properties such as LastName
that are common to both instructors and students. You won't add or
change any web pages, but you'll change some of the code and those
changes will be automatically reflected in the database.Table-per-Hierarchy versus Table-per-Type Inheritance
In object-oriented programming, you can use inheritance to make it easier to work with related classes. For example, theInstructor
and Student
classes in the School
data model share several properties, which results in redundant code:Suppose you want to eliminate the redundant code for the properties that are shared by the
Instructor
and Student
entities. You could create a Person
base class which contains only those shared properties, then make the Instructor
and Student
entities inherit from that base class, as shown in the following illustration:There are several ways this inheritance structure could be represented in the database. You could have a
Person
table that includes information about both students and instructors in a
single table. Some of the columns could apply only to instructors (HireDate
), some only to students (EnrollmentDate
), some to both (LastName
, FirstName
). Typically you'd have a discriminator
column to indicate which type each row represents. (In this case , the
discriminator column might have "Instructor" for instructors and
"Student" for students.) This pattern of generating an entity inheritance structure from a single database table is called table-per-hierarchy (TPH) inheritance.
An alternative is to make the database look more like the inheritance structure. For example, you could have only the name fields in the
Person
table and have separate Instructor
and Student
tables with the date fields.This pattern of making a database table for each entity class is called table per type (TPT) inheritance.
TPH inheritance patterns generally deliver better performance in the Entity Framework than TPT inheritance patterns, because TPT patterns can result in complex join queries. This tutorial demonstrates how to implement TPH inheritance. You'll do that by performing the following steps:
- Create a
Person
class and change theInstructor
andStudent
classes to derive fromPerson
. - Add model-to-database mapping code to the database context class.
- Change
InstructorID
andStudentID
references throughout the project toPersonID
.
Creating the Person Class
In the Models folder, create Person.cs and replace the existing code with the following code:using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public abstract class Person { [Key] public int PersonID { get; set; } [Required(ErrorMessage = "Last name is required.")] [Display(Name="Last Name")] [MaxLength(50)] public string LastName { get; set; } [Required(ErrorMessage = "First name is required.")] [Column("FirstName")] [Display(Name = "First Name")] [MaxLength(50)] public string FirstMidName { get; set; } public string FullName { get { return LastName + ", " + FirstMidName; } } } }In Instructor.cs, derive the
Instructor
class from the Person
class and remove the key and name fields. The code will look like the following example:using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Instructor : Person { [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)] [Required(ErrorMessage = "Hire date is required.")] [Display(Name = "Hire Date")] public DateTime? HireDate { get; set; } public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; } } }Make similar changes to Student.cs. The
Student
class will look like the following example:using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Student : Person { [Required(ErrorMessage = "Enrollment date is required.")] [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)] [Display(Name = "Enrollment Date")] public DateTime? EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
Adding the Person Entity Type to the Model
In SchoolContext.cs, add aDbSet
property for the Person
entity type:public DbSet<Person> People { get; set; }This is all that the Entity Framework needs in order to configure table-per-hierarchy inheritance. As you'll see, when the database is re-created, it will have a
Person
table in place of the Student
and Instructor
tables.Changing InstructorID and StudentID to PersonID
In SchoolContext.cs, in the Instructor-Course mapping statement, changeMapRightKey("InstructorID")
to MapRightKey("PersonID")
:modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("PersonID") .ToTable("CourseInstructor"));This change isn't required; it just changes the name of the InstructorID column in the many-to-many join table. If you left the name as InstructorID, the application would still work correctly.
Next, perform a global change (all files in the project) to change
InstructorID
to PersonID
and StudentID
to PersonID
. Make sure that this change is case-sensitive. (Note that this demonstrates a disadvantage of the classnameID
pattern for naming primary keys. If you had named primary keys ID
without prefixing the class name, no renaming would be necessary now.)Adjusting Primary Key Values in the Initializer
In SchoolInitializer.cs, the code currently assumes that primary key values forStudent
and Instructor
entities are numbered separately. This is still correct for Student
entities (they'll still be 1 through 8), but Instructor
entities will now be 9 through 13 instead of 1 through 5, because the
block of code that adds instructors comes after the one that adds
students in the initializer class. Replace the code that seeds the Department
and OfficeAssignment
entity sets with the following code that uses the new ID values for instructors:var departments = new List<Department> { new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"), PersonID = 9 }, new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"), PersonID = 10 }, new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"), PersonID = 11 }, new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"), PersonID = 12 } };
var officeAssignments = new List<OfficeAssignment> { new OfficeAssignment { PersonID = 9, Location = "Smith 17" }, new OfficeAssignment { PersonID = 10, Location = "Gowan 27" }, new OfficeAssignment { PersonID = 11, Location = "Thompson 304" }, };
Changing OfficeAssignment to Lazy Loading
The current version of the Entity Framework doesn't support eager loading for one-to-zero-or-one relationships when the navigation property is on the derived class of a TPH inheritance structure. This is the case with theOfficeAssignment
property on the Instructor
entity. To work around this, you'll remove the code you added earlier to perform eager loading on this property.In InstructorController.cs, delete the three occurrences of the following line of code:
.Include(i => i.OfficeAssignment)
Testing
Run the site and try various pages. Everything works the same as it did before.In Solution Explorer, double-click School.sdf to open the database in Server Explorer. Expand School.sdf and then Tables, and you see that the Student and Instructor tables have been replaced by a Person table. Expand the Person table and you see that it has all of the columns that used to be in the Student and Instructor tables, plus the discriminator column.
The following diagram illustrates the structure of the new School database:
Table-per-hierarchy inheritance has now been implemented for the
Person
, Student
, and Instructor
classes.
No comments:
Post a Comment