Sunday, July 13, 2014

Inheritance in Entity Framework : Table per concrete type

This is Entity Framework article series, In our previous articles we have discussed various important concepts of Entity Framework and learned various strategy and approaches to implement Entity Framework and learned inheritance handling technique by Table per type and Table per hierarchy approach

In this article we will learn one more (and last) inheritance handling strategy in Entity Framework, called Table per concrete type. The approaches says that, one table will get generate if the type is only concrete type.

For example, if there is abstract base class called “Person” and two classes are derived from it called “Friend” and “BestFriend” which are both concrete classes, then two tables will get create for “Friend” and “BestFriend”. The class hierarchy is like below.


Ok, so we have understood, how table per concrete type works and relate with database table. Now, let’s implement the hierarchy which we have shown in diagram.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public abstract class Person
    {
        public int PersonId { get; set; }
        public string name { get; set; }
        public string surname { get; set; }
    }
    public class Friend : Person
    {
        public Boolean IsClassmate { get; set; }
    }
    public class BestFriend : Person
    {
        public string Address { get; set; }
    }
   
    public class personContext : DbContext
    {
        public personContext()
            : base("DBConnectionString")
        {
        }
        public DbSet<Person> Person { get; set; }

        protected override void OnModelCreating(DbModelBuilder builder)
        {
            builder.Entity<Friend>().Map(m => {
                m.MapInheritedProperties();
                m.ToTable("Friend");
            });

            builder.Entity<BestFriend>().Map(m =>{
                m.MapInheritedProperties();
                m.ToTable("BestFriend");
            });
        }
    }
}

The approach is pretty simple and straight forward , here is the connection string which I have define in web.config file.


<connectionStrings>
        <add name="DBConnectionString"
        connectionString="Data Source=SOURAV-PC;Initial Catalog=personDB;Integrated Security=true"
        providerName="System.Data.SqlClient"/>
  </connectionStrings>


Once we run the application and look into database, we will see that two tables named “Friend”and “BestFriend” has created, because as per discussion it will create table only for concrete class not for abstract class.
As we have defined “Person” as abstract class, no table is created for the class. Ok, so the fact is clear that, if we want to implement table per concrete type in code first approach , we have to define the classes as abstract class, when we don’t want to create table for that class.


Fine, now the tables has created and tries to save some data into it.

Save data in Database
Here is code to save data into tables, we are trying to persist object of “Friend” and “Best Friend” class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Company;
using System.Collections;

namespace ConsoleApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using (var ctx = new personContext())
            {
Friend friend = new Friend{name = "foo" ,surname = "bar"};
                BestFriend bestfriend = new BestFriendname = "sourav", surname = "kayal", Address = "kolkata" };

                ctx.Person.Add(friend);
                ctx.Person.Add(bestfriend);
                ctx.SaveChanges();
            }
        }
    }
}
When we will try to save those objects , we will encounter below exception. 



The reason we got this exception is because DbContext.SaveChanges() internally invokes SaveChanges method of its internal ObjectContext. ObjectContext's SaveChanges method on its turn by default calls AcceptAllChanges after it has performed the database modifications. AcceptAllChanges method merely iterates over all entries in ObjectStateManager and invokes AcceptChanges on each of them. Since the entities are in Added state, AcceptChanges method replaces their temporary EntityKey with a regular EntityKey based on the primary key values (i.e. PersonId) that come back from the database and that's where the problem occurs since both the entities have been assigned the same value for their primary key by the database (i.e. on both PersonId = 1) and the problem is that ObjectStateManager cannot track objects of the same type (i.e. PersonId) with the same EntityKey value hence it throws.


Solution !
Let’s think about the solution in code level, not from database level. We can turn off identity column explicitly like below. Modification only in Person class is enough.

public abstract class Person
    {
        [DatabaseGenerated(DatabaseGenerationOption.None)]
        public int PersonId { get; set; }
        public string name { get; set; }
        public string surname { get; set; }
    }

If you are using Fluent API then here is the code snippet for you. Just implement OnModelCreation function like below.

protected override void OnModelCreating(DbModelBuilder builder)
        {
            builder.Entity<Friend>().Map(m => {
                m.MapInheritedProperties();
                m.ToTable("Friend");
            });

            builder.Entity<BestFriend>().Map(m =>{
                m.MapInheritedProperties();
                m.ToTable("BestFriend");
            });

            builder.Entity<Person>()
            .Property(p => p.PersonId).
            HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
        }

Now, we have turn off the Auto increment primary key feature, now we will supply value of “PersonId” explicitly, here is modified code to save data.

public static void Main(string[] args)
        {
            using (var ctx = new personContext())
            {
                Friend friend = new Friend{PersonId =1, name = "foo" ,surname = "bar"};
                BestFriend bestfriend = new BestFriend { PersonId = 2, name = "sourav", surname = "kayal", Address = "kolkata" };

                ctx.Person.Add(friend);
                ctx.Person.Add(bestfriend);
                ctx.SaveChanges();
            }
        }

And we are seeing that the data has saved in database.


Read data from table

Here we have shown to read data from tables. We can use OfType to get data from particular object type. Have a look on below code.

public static void Main(string[] args)
        {
            using (var ctx = new personContext())
            {
                var Friend = (from m in ctx.Person.OfType<Friend>() select m).FirstOrDefault();
                Console.WriteLine("Name :" + Friend.name);
                Console.WriteLine("Surname :" + Friend.surname);
                Console.WriteLine("Is Classmate :" + Friend.IsClassmate);


                var BestFriend = (from m in ctx.Person.OfType<BestFriend>() select m).FirstOrDefault();
                Console.WriteLine("Name : " + BestFriend.name);
                Console.WriteLine("Surname : " + BestFriend.surname);
                Console.WriteLine("Address : "  + BestFriend.Address);

            }
        }

Here is the sample output.


Border line:
In this article we have learned table per concrete type approach to implement inheritance in Entity Framework, hope it will help you to understand the inheritance concept using Entity Framework, In our next few article , we will understand few more concepts of Entity Framework.


No comments:

Post a Comment