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 BestFriend { name = "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