LINQ to SQL Inheritance model in an ASP.Net application
One of the most important concepts in Object Oriented Programming is Inheritance. In this post I would like to talk about LINQ to SQL and the inheritance type that it supports which is Table per Class Hierarchy.
In this type of hierarchy we have a single table/entity and its columns apply to all derived child tables/entities. Obviously the child tables/entities have columns that are only specific to them.
We can create this type of hierarchy in the Object Relational Designer. It is true that in Entity Framework we have more flexibility when it comes to inheritance types but we will talk about LINQ to SQL now.
We will demonstrate this with a hands on example. I am going to create a new database with new tables for this example.
The scenario we have in this case is derived from the institution I am delivering courses at. So we have different roles in there. We have instructors,students,marketing people,administration people.
So we will have this generic table/entity called Human. Well, we do have different roles and skills but we are alla humans after all.
You will need to create a database as well.I am going to name the database CPLS. CPLS is the kind of instution that is authorised by Microsoft to deliver Microsoft approved courses.
Obviously we need some sample data in our table.
So before you go any further make sure you have SQL Server installed and execute the T-SQL code you will find inside the CPLS.zip.
If you do not have SQL Server installed, you can download and install the free SQL Server Express edition from here.
The Human table contains the following columns:
[HumanID] ,[HumanType],[LastName],[FirstName],[BirthDate],[HireDate],[EnrollmentDate],[Address],[City],[Region],[PostalCode],[Country],[Phone]
,[Fax],[Wage],[SalesRegion],[ProgrammingSkills],[Bonus],[Grade]
We can see that we can derive 3 main entities:Instructor,Student and SalesPerson. These are the entities I am going to use in my model after I define the inheritance associations. Make a note that I will not change the underlying database table.
Let's proceed to our hands-on example.
1) Launch Visual Studio 2010/2008 (express editions will work fine). Create a new empty website and choose a suitable name for it. Choose C# as the development language. 2) Add a new item in your site. Add a LINQ to SQL Classes and name it Human.dbml. Place this file in the App_Code special folder. 3) From the Server Explorer/Data Explorer window select the option "Connect to database" and then simply specify the instance of the SQL Server where you installed the CPLS database. 4) From the Server Explorer / Data Explorer window drag the Human table and drop it on the Human.dbml. When you do that you must have something similar like the picture below 5) Let's see how we can transform the entity above to implement inheritance and more specifically Table per class Hierarchy.In order to build this new model we should have defined what we call a Discriminator property. In our case this property is "HumanType". HumanType=1 means Instructor , HumanType = 2 means Student and HumanType=3 means SalesPerson. Those are the 3 values for the HumanType column/property in our Human table/entity. 6) This is the new model. Have a look at the picture below. 7) The way I constructed this model was to drag and drop from the Server Explorer 3 more times the Human table. Then I changed the names of the other entities to Instructor,Student,SalesPerson and Employee. Then I deleted the properties that were obsolete to that particular entity. Then I dragged an Inheritance arrow from the Object Relational Designer and "joined" together the Student to Human and Employee to Human entities. Then I dragged an Inheritance arrow from the Object Relational Designer and "joined" together the Instructor to Employee and SalesPerson to Employee entities. Then I selected the Human entity and changed the Inheritance modifier to abstract. Then I selected the Employee entity and changed the Inheritance modifier to abstract. Then I selected the inheritance arrow from Student to Human and changed those properties Discriminator Property:HumanType Derived Class Discriminator : 2 Then I selected the inheritance arrow from Employee to Human and changed the property Discriminator Property:HumanType Then I selected the inheritance arrow from Instructor to Employee and changed those properties Discriminator Property:HumanType Derived Class Discriminator : 1 Then I selected the inheritance arrow from SalesPerson to Employee and changed those properties Discriminator Property:HumanType Derived Class Discriminator : 3 8) Let's try and get some data out from our new model as it is defined in the .dbml file. We want to get the students from our table. In the Page_Load() event handling routine of the Default.aspx page type, HumanDataContext ctx = new HumanDataContext(); One way to do it is to go to the Human.designer.cs file and find this bit of code and make the necessary changes. LINQ needs to know that if for some reason cannot map an entity to one that exists already, needs to have a default one to map it to that. [Table(Name="dbo.Human")] 9) Run your application and see the results. The main thing is to look carefully at this bit of code var students = from stu in ctx.Humans.OfType <Student>() We use the OfType extension method. If you look more carefully at the properties available for the Student entity you will see that we have available all the properties defined in the parent entity Human (base class) and the specific properties of the Student entity. 10) If we wanted to get data out of our Instructors entity as well, we need to rewrite the code above.In the Page_Load() event handling routine of the Default.aspx page type, HumanDataContext ctx = new HumanDataContext(); 11) Run your application and see both the results from both entities printed out in the screen. If you look more carefully at the properties available for the Instructor entity you will see that we have available all the properties defined in the parent entity Human (base class) and the specific properties of the Instructor entity. 12) Now let's try and add a new Student record into the database.In the Page_Load() comment out everything. In the Page_Load() event handling routine of the Default.aspx page type, Student mystudent = new Student(); 13) Run your application and see the "saved" message printed out in the screen. Have a look at your database table Human and see the new record added.Please note that we do not have to specify a value for the HumanType column for the new record. LINQ to SQL will know what value to fill in for that column. In our case will be 2 which means Student. Email me if you need the source code.Hope it helps.
var students = from stu in ctx.Humans.OfType <Student>()
select stu;
foreach (var student in students)
{
Response.Write("First Name: " + student.FirstName);
Response.Write("---");
Response.Write("Last Name: " + student.LastName);
Response.Write("---");
Response.Write("Enrollment Date : " + student.EnrollmentDate);
Response.Write("---");
Response.Write("Grade: " + student.Grade);
}
Before you run your application, you need to specify a default.Make sure that you do not choose an entity that is marked as abstract.You cannot create instances of classes that are marked as abstract.
[InheritanceMapping(Code="2", Type=typeof(Student))]
[InheritanceMapping(Code="1", Type=typeof(Instructor),IsDefault=true)]
[InheritanceMapping(Code="3", Type=typeof(SalesPerson))]
select stu;
var students = from stu in ctx.Humans.OfType<Student>()
select stu;
var instructors = from inst in ctx.Humans.OfType<Employee>().OfType<Instructor>()
select inst;
foreach (var instructor in instructors)
{
Response.Write(instructor.ProgrammingSkills);
Response.Write("<br/>");
}
foreach (var student in students)
{
Response.Write("First Name: " + student.FirstName);
Response.Write("---");
Response.Write("Last Name: " + student.LastName);
Response.Write("---");
Response.Write("Enrollment Date : " + student.EnrollmentDate);
Response.Write("---");
Response.Write("Grade: " + student.Grade);
}
HumanDataContext ctx = new HumanDataContext();
mystudent.FirstName = "Grace";
mystudent.LastName = "Jennings";
mystudent.BirthDate = DateTime.Now.AddDays(-10000);
mystudent.EnrollmentDate = DateTime.Now.AddDays(-234);
mystudent.Address = "76 Blade Road";
mystudent.City = "Coventry";
mystudent.Region = "Midlands";
mystudent.PostalCode = "SW4567";
mystudent.Country = "England";
mystudent.Phone = "493497294334";
mystudent.Fax = "43243242442";
mystudent.Grade = "A+";
try
{
ctx.Humans.InsertOnSubmit(mystudent);
ctx.SubmitChanges();
Response.Write("saved!!!");
}
catch (Exception ex)
{
Response.Write(ex.Message);
}