1. | Classes in .Net can be inherited and extended, with new properties added for the sub classes. Entity Framework allows you to represent this in three main ways: Table-Per-Type, Table-Per-Hierarchy, Table-Per-ConcreteClass. This article describes Table-Per-Hierarchy. | ||||||||||||||||||||||||
2. |
Firstly, in Table-Per-Hierarchy (TPH) there is only one table for each base class, and any classes which inherit or
extend this base class simply add more columns to this table. Let's have an example.
We define a new POCO class called Animal with a couple of properties:using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace TestEF.Entity { public class Animal { public int Id { get; set; } public bool WarmBlooded { get; set; } public string NotedCountry { get; set; } } }To control how the table is created, we decorate it with the usual Data Annotations: public class Animal { [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] public bool WarmBlooded { get; set; } [Required] [StringLength(100)] public string NotedCountry { get; set; } } |
||||||||||||||||||||||||
3. |
Now we create a context for it, as before:using System.Data; using System.Data.Entity; using System.Data.EntityModel; namespace TestEF.Contexts { public class AnimalContext : DbContext { public DbSet<TestEF.Entity.Animal> Animals { get; set; } } }Add a table generation plan line in Application_Start in Global.asax as before: Database.SetInitializer<TestEF.Contexts.AnimalContext>(new DropCreateDatabaseAlways<TestEF.Contexts.AnimalContext>());And a connection string line in web.config <add name="AnimalContext" connectionString="Persist Security Info=False;Initial Catalog=MySchoolDB;Server=MYSERVER/Instance;Connect Timeout=30" providerName="System.Data.SqlClient" /> |
||||||||||||||||||||||||
4. |
Now we can create the context and populate the Animal object in our code behind or MVC controller:using (TestEF.Contexts.AnimalContext AnimalKingdom = new TestEF.Contexts.AnimalContext()) { Entity.Animal beast = new Entity.Animal(); beast.NotedCountry = "Australia"; beast.WarmBlooded = true; AnimalKingdom.Animals.Add(beast); AnimalKingdom.SaveChanges(); }When we run this we can see that, as expected, we have an Animals table with three columns, and one populated row. This is as expected with Entity Framework. |
||||||||||||||||||||||||
5. |
Now we can add two classes which inherit from Animal, Dog and Catpublic class Dog : Animal { [Required] [StringLength(100)] public string Name { get; set; } [Required] [StringLength(100)] public string Colour { get; set; } } public class Cat : Animal { [Required] [StringLength(100)] public string Breed { get; set; } [Required] [StringLength(100)] public string FavouriteFood { get; set; } }Note on these inherited classes, we don't need to reproduce the properties of the parent class, and unlike TPT, we don't have specify the Table names of where we want the subclasses to go, since they will all go in one table. |
||||||||||||||||||||||||
6. |
Now we can create a Dog and a Cat in our code behind or MVC controller:using (TestEF.Contexts.AnimalContext AnimalKingdom = new TestEF.Contexts.AnimalContext()) { Entity.Dog doggy = new Entity.Dog(); // fill in the base class properties doggy.NotedCountry = "England"; doggy.WarmBlooded = true; // fill in subclass specific properties doggy.Name = "Fido"; doggy.Colour = "Brown"; Entity.Cat pussy = new Entity.Cat(); // fill in the base class properties pussy.NotedCountry = "Ethiopia"; pussy.WarmBlooded = true; // fill in subclass specific properties pussy.Breed = "Abyssinian"; pussy.FavouriteFood = "Fish"; AnimalKingdom.Animals.Add(doggy); AnimalKingdom.Animals.Add(pussy); AnimalKingdom.SaveChanges(); }When we examine the database we see one Animals table: Animals
We see that an Animal row has been created for each one, and the columns from the base class and from each of the sub-classes have all been included in the one table. Where one sub class doesn't have those properties, they are made null (which means you can't use [Required] to insist on non-nullable fields in the TPH structure - other subclasses by nature have to be able to null these fields). The most important feature of this type of hierarchy is that EF has created a Discriminator field which is its way of keeping track of which sub-class object the row belongs to - it's a 'type' field in effect, and EF populates it with the sub-class name. If we added yet more levels of inheritance, EF would create extra Discriminator columns to discriminate between those types. Clever! IMPORTANT: if your sub-classes share a property name, e.g. we added a Name field to both Dog and Cat, then EF does not assume they means the same thing, so it would create two separate columns in the database, and appends a number to the column name of any duplicated peoperty names, e.g. Name, Name1 |
||||||||||||||||||||||||
7. |
We may already have a column which discriminates between sub-class types (e.g. if we had an AnimalType property on the Animal class
which we set to "doggy" or "pussy" when we were creating them. Also, we may be mapping our objects onto existing table schemas, and
we don't want this extra columns created if there is already one which does the job. In this case, we don't need EF to create a
Discriminator column, we can use the Fluent API to determine how to discriminate:
public class AnimalContext : DbContext { public DbSet<TestEF.Entity.Animal> Animals { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Entity.Dog>() .Map<Entity.Dog>(m => m.Requires("AnimalType").HasValue("Doggy")); modelBuilder.Entity<Entity.Cat>() .Map<Entity.Cat>(m => m.Requires("AnimalType").HasValue("Pussy")); } }And this results in the Discriminator column being defined as AnimalType, with values as defined in the Fluent API "Doggy" and "Pussy": Animals
|
||||||||||||||||||||||||
And that's the basics of table-Per-Type class representation in Entity Framework. |