0

I am looking to implement Table-per-Hierarchy using EF6 similar to the instructions found here: example.

I have an abstract base class of User with the following derived types:

  • Student
  • Contact
  • Instructor

When I examine the database table Users the discriminator column value is (Undefined) when I pass a student object into my Save method below. Instead I would expect the value to be Student. Otherwise my data is saved correctly in both the Users and Students tables.

While troubleshooting the problem I added a UserType enumerator Get property to the classes to ensure that I am casting from User to Student.

In my UserRepository class my Save method is below.

    public void Save(User user)
    {
      if (Exists(user.Id))
        UpdateUser(user);
      else
      {
        switch (user.Role)
        {
          case UserType.Role.Base:
           _db.Users.Add(user);
            break;
          case UserType.Role.Student:
           _db.Users.Add(user as Student);
           break;
         case UserType.Role.Instructor:
           _db.Users.Add(user as Instructor);
           break;
         case UserType.Role.Contact:
           _db.Users.Add(user as Contact);
           break;
       }
     }
     _db.SaveChanges();
    }

Failed Alternative

I've tried code like the following to explicitly create a new Student.

    private void MapToStudent(User user)
    {
     _db.Users.Add(new Student()
     {
       FirstName = user.FirstName,
       LastName = user.LastName,
       //...
      });
    }

Question

I am not downcasting correctly? Or rather what is the proper/preferred way to save subclasses using EF?

User Base Class

      public abstract class User
      {
         public int Id { get; set; }
         //...
      }

      internal class UserNotFound: User 
      { 
        public override UserType.Role Role 
        { 
          get 
            { 
             return UserType.Role.Base; 
            } 
        }
      }

      public class Student : User 
      {
         //...
         public override UserType.Role Role 
         {
           get { return UserType.Role.Student; }          
         }
      }

      public class Contact : User 
      {
         //...
         public override UserType.Role Role 
         {
           get { return UserType.Role.Contact; }          
         }
      }

      public class Instructor : User 
      {
         //...
         public override UserType.Role Role 
         {
           get { return UserType.Role.Instructor; }          
         }
      }

DatabaseContext Mapping

      public class DatabaseContext : Context
      {
          protected override void OnModelCreating(DbModelBuilder modelBuilder)
          {
              modelBuilder.Entity<Student>().ToTable("Students");
              modelBuilder.Entity<Contact>().ToTable("Contacts");
              modelBuilder.Entity<Instructor>().ToTable("Instructors");
          }
      }
CyberUnDead
  • 147
  • 5
  • 14
  • I think the problem is your base type is not Abstract. – Erik Philips Jul 18 '14 at 21:22
  • @ErikPhilips my _User_ class is specified as: `public abstract class User` – CyberUnDead Jul 18 '14 at 21:26
  • 1
    Then this code should not exist: `case UserType.Role.Base: _db.Users.Add(user);`. However, how is `user.Role` value set? – Erik Philips Jul 18 '14 at 21:28
  • `internal class UserNotFound: User { public override UserType.Role Role { get { return UserType.Role.Base; } } }` is the simplistic case of my null object of _User_. I am overriding the property in each subclass and setting it according to its matching enumerator value. for @ErikPhilips – CyberUnDead Jul 18 '14 at 21:33
  • For future reference, it's best not to add code to the comment, but to you actual question (you can edit it as much as you'd like) so other reads can get a full comprehension of your question without the need to read all the comments. – Erik Philips Jul 18 '14 at 21:36
  • I would also ask that you show the code that defines your TPH inheritance (or are you using Database First?) What happens if you create a save method for a specific subclass and call it directly (for testing), does it create the discriminator? – Erik Philips Jul 18 '14 at 21:41
  • All your Entities are mapped to table *Students*? – Erik Philips Jul 18 '14 at 21:50

1 Answers1

0

It appears your mappings are incorrect for TPH. The linked example in your questions shows:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<BillingDetail>()
    .Map<BankAccount>(m => m.Requires("BillingDetailType").HasValue("BA"))
    .Map<CreditCard>(m => m.Requires("BillingDetailType").HasValue("CC"));
}

which modeled after your question might look like:

  modelBuilder.Entity<User>()
    .Map<Student>(m => m.Requires("Discriminator").HasValue("STU"))
    .Map<Instructor>(m => m.Requires("Discriminator").HasValue("INS"));
jwize
  • 4,230
  • 1
  • 33
  • 51
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • I was mixing my methodologies between TPH and TPT, so when my simple _Administrator_ class was being inserted it was using the *Discriminator* column, while the other subclasses do not use the column and instead placed their data into separate appropriate tables. [concise documentation](http://msdn.microsoft.com/en-us/data/jj591617.aspx#2.4) – CyberUnDead Jul 18 '14 at 22:51