Introduction
Enterprise Applications are architected with the division of responsibilities between different teams of IT professionals, developers, and data base administrators. Despite a very diverse need of information from different application perspectives, data integrity is something which no one would want to compromise. Since data is shared by many applications, no single application programmer has the luxury to modify the shared database schemas according a single application's needs; instead, he/she is supposed to write down the data access layer on top of existing schema to match the application needs with pre-existing database schema.
With the introduction of Entity Framework (EF), it has become very easy to model OO designs on top of any database schema. We would explore a very interesting example where the entire application is modeled visually using the EF designer. A set of classes (with inheritance) were automatically generated by the EF designer without having to write down any single line of code in C#, VB, or even XML. For that specific activity, no knowledge of XML, or SQL was needed -- just clicking, selecting, and specifying the object properties corresponding to table columns in a friendly UI was good enough.
Once the classes were auto-generated; the data was fetched by a simple single-line LINQ statement; which would then create the appropriately typed objects (instances of those classes) corresponding to each row; to-the-point (loaded with only the properties columns relevant to that specific type), and ready to go!
We would also draw interesting parallels between layers of this application and the famous MVC architecture.
Finally, to leverage inheritance, we would see how just a single display()
method (per concrete class) is sufficient to render the most accurate formatting (of course that would in turn leverage from the same method in the base class before rendering the specific data of its own class).
Enterprise Needs: The ultimate goal of this hypothetical exercise is to fulfill the top management's need of adjusting prices of certain product groups according to inflation caused by high fuel prices and consequential rise in transportation costs; and still put seasonal goods on clearance sale -- summer is quickly coming to an end!
We will try to:
- Eliminate coupling (through EF)
- Reduce duplications using inheritance/polymorphism
- Increase cohesion by putting similar methods right next to each other in separate C# files
Despite five different product types (four concrete, and one abstract), and a need to display/update items belonging to only one of the product types; the most interesting part of this code is a complete absence of any of if
, else
, switch
, case
statements in C#; and any LINQ/SQL where
clauses (in manually written code). That way, we have completely eliminated code duplication, and achieved a higher degree of cohesion.
Background
When I first heard of Entity Framework, I tried to look for some examples in Code Project, but couldn’t find one that implements an inheritance hierarchy on top of a single table. So I decided to write a simple C# solution that would be directly usable in Visual Studio 2008 SP1.
In this particular example, we have a single SQL Server table containing several products classified into different types (based on whether they are seasonal or are discontinued). Following four “product type codes” were assigned: 00
for Regular products; 01
for seasonal; 10
for discontinued; and 11
for seasonal, but discontinued. Following is the “Product
” table:
Note that in an actual application, there may be thousands of records for each product type (category). However, for the sake of simplicity, only five have been picked up in this example. Although for data performance efficiency, the two character coding system is great; yet if we literally use these cryptic codes in our program, it would be very confusing/hard to remember what code is used for which type. So it was decided to use the friendlier names instead of code, i.e., RegularProduct
, DiscontinuedProduct
, SeasonalProduct
, and SeasonalDiscontinuedProduct
; instead of binary coded 00
, 01
, 10
, and 11
(for the sake of brevity, let us start calling them “entity
names” or “class
name” depending on whether they are called from the EF designer, or VB/C# program code).
We have decided to do all those mappings using Entity Framework. That way, any person even without any knowledge of VB, C#, XML, or SQL language could maintain and extend the mappings.
We have four concrete product types, so we would create the four entity names corresponding to each one of them. That way, in our application code, we would just use the class name (e.g., SeaonalProduct
) to fetch out records corresponding to ProductType
10
). How about fetching of all records in the Product
table? It would be better if we create yet another class which does not filter rows specific to any of the four conditions. We would call it Product
. We would map ProductID, Name
, and Price
into the Product
entity class; and derive rest of the four concrete classes from it. That way, we would inherit those three fields that are needed (are of common interest) in all of the derived classes, and would not have to unnecessarily duplicate them (into all four of them).
Certain columns only make sense within the context of a specific product type. For example, while analyzing DiscontinuedProduct
, we would like to know when they were discontinued; similarly for SeasonalProduct
, we are supposed to keep records of much off season discount we are planning to give. Therefore, in addition to setting the “When” condition for product type codes, we would also map those additional columns to properties of our entity class. Using the EF designer, we can do all the mappings visually, without having to write a single line of C#/VB/XML code.
Figure 1
The entity “DiscontinueProduct
” maps to the Product
(table) When ProductType
= 01
. It also has the "DisconinuedDate
" property, which is mapped to the DiscontinuedDate
column.
Mapping Review
Let us review what we have done so far – using VS 2008 EF designer, we have automatically generated Objects, which Map to a Relational database table(ORM). All five entity classes map to a single table, filtering rows according to the condition specified in “When
”. This frees us from writing SQL where
clauses, or sprinkling if
/switch
/case
statements throughout our program (making it hard to read/relate/maintain).
The program would be written without a need to have an intimate knowledge of underling database schema. In fact, the compiled program is completely isolated from any knowledge of database, its schema, its location, SQL dialect; or even database brand (besides SQL Server, it could be Oracle, DB2 or any vendor whose EF provider has been developed).
The only mention of a database connection is in App.Config file which could be altered with recompiling of source code. The only one place where database tables are coupled (mapped) with objects is through the UI of “Entity Designer”. The C# code is free of any reference to the usual Connection
, Command
, DataSet
, DataReader
, or any other ADO.NET class. (To get the view of Figure 1, please double click on “TablePerHierarchy.edmx, and then view the mapping of each entity by clicking on them one by one)”.
Using the EF Generated Class Hierarchy
So far, we did not write a single line of code, and the EF automatically created several entity classes behind the scenes. If we open the class diagram "EFHierarchy.cd", we would find the EF generated classes.
After the classes were auto-generated by the EF, we manually added a Display()
method; and a ProductType
property in all of the five classes. To take advantage of polymorphism, we declared both the property and method virtual
in Product
base class; and override them in all four derived classes. This hierarchy of these five classes is accessed (by its consumers) through the TablePerHierarchyContainer
. Shouldn't we call it a "Model"?
Operations on the "Model"
The Operations.cs contains the code with two basic operations on the Model (MVC enthusiasts like to understand software in terms of Model, View, and Controller layers). In this implementation, we would also attempt to draw some parallels from that architecture. It (the Operations.cs file) was used to extend the EF generated partial class TablePerHierarchyContainer
by enriching it with two operations (generic methods): Display<ProductType>()
and ChangeByPercentagePriceOf<ProductType>()
, which takes Product
as an argument. Since Product
is the base class of this hierarchy, we can pass any one of these 5 classes as an argument to these methods, depending on whether we want to have the operations done on all rows of Product
table, or just on the ones which belong to a particular product type (category) in our inventory.
public void Display<ProductType>() where ProductType : Product {
foreach (Product product in (
from product in Products.OfType<producttype />() select product)) {
product.Display(); Console.WriteLine();
}
}
public void ChangeByPercentagePriceOf<ProductType>(
Decimal percent) where ProductType : Product {
foreach (Product product in (
from product in Products.OfType<producttype />() select product)) {
product.Price += product.Price * percent / 100;
}
SaveChanges(true);
}
In both operations, only a single line of LINQ statement iterates though the entire collection of products.
The first line of this method is a foreach
statement (with embedded LINQ block). It fetches all of the Product
table rows, and renders them as instances of one of the four concrete classes (using the base class reference). That way, we can call the polymorphic Display()
method in the subsequent statement to get the table rows, displayed by one of the four derived classes, handling the specific properties of a given Product
Type (passed as an argument to this generic method).
foreach (Product product in (
from product in Products.OfType<ProductType>() select product)) {
product.Display();Console.WriteLine();
}
We would carry out the analysis of product.Dispaly()
method in the View section.
- The "Change By Percentage, price of" Operation
Its first line is exactly the same as the one described in Display
operation above. In the second line, it adds the percentage of increase in a product
's price
, and once it is done updating price
s of all product
s; it saves the changes.
foreach (Product product in (
from product in Products.OfType<ProductType>() select product)) {
product.Price += product.Price * percent / 100;
}
SaveChanges(true);
Rendering the "View"
The DisplayFormatting.cs contains partial classes for the display of model data. They accomplish a very simple View of our EF generated Model. They just dump their fields using the Console.Write
statement. For example, the base class displays Product
ID along with thee other properties common in all five classes:
public virtual void Display() { Console.Write("{0}:{1}: {2}\t {3:$###.00}",
ProductID, ProductType, Name, Price); }
The Seasonal product first calls the base class to do display the regular product fields, afterwards, it also writes the off season discount.
public override void Display() { base.Display(); Console.Write(
"; Discount {0:##.00}%", OffSeasonDiscount); }
I don't think we need any further explanation for other concrete classes for our smart readers, as they are just one line methods with an identical purpose. The only thing special about them is that they are written encapsulated within their specific product type classes, hence they contain reference to the fields which are exclusively available in their own scope.
Implementing Business Application through "Controller"
The entire "Business Application" is written in a single MainProgram.cs. The Main
program is a sequence of one line calls to the operations of "Model
" (we just have display, and price change). If you really want to designate some part of this application as a "Controller
" of MVC, it is this sequence. The executable calls the operations on "Model
" passing different product types. I have added lot of comments to make its purpose obvious.
The only point I want to make is that this could be written by a team of developers who just know the enterprise business needs, and how to activate the two operations of the Model
. They just pass one of the Product
types to get to the right set of rows from the Product
table, and optionally a percentage figure to adjust to cost. I have stripped of all fancy things to keep it very simple to understand by even the beginners.
Console.WriteLine("****** FIRST DISPLAY THE ENTIRE PRODUCT IN INVENTORY ***");
ef.Display<Product>();
Console.WriteLine(
"\n\n****** INFLATION! -- INCREASE PRICE OF THE REGULAR PRODUCTS BY 2%***");
ef.ChangeByPercentagePriceOf<RegularProduct>(2);
Console.WriteLine("\n\n****** NEW PRICE OF THE REGULAR PRODUCTS After 2% INCREASE***");
ef.Display<RegularProduct>();
Console.WriteLine(
"\n\n****** CLEARANCE SALE! -- REDUCE PRICE OF THE SEASONAL PRODUCTS BY ANOTHER 40%***");
ef.ChangeByPercentagePriceOf<SeasonalProduct>(-40);
Console.WriteLine("\n\n****** NEW PRICE OF THE SEASONAL PRODUCTS After 40% DISCOUNT***");
ef.Display<SeasonalProduct>();
Program Output
Following is the program output. You may want to compare it with the Product
table contents, the first illustration in the Background section.
****** FIRST DISPLAY THE ENTIRE PRODUCT IN INVENTORY ***
1:RegularProduct: Nail Box $5.09
2:DiscontinuedProduct: Bubble level $5.99; Since 02/02/07
3:SeasonalProduct: Flowers $9.07; Discount 40.00%
4:SeasonalDiscontinuedProduct: Roses $12.00; Discount 50.00%, Since 04/02/07
5:RegularProduct: Nuts and Bolts $3.18
****** INFLATION! -- INCREASE PRICE OF THE REGULAR PRODUCTS BY 2%***
****** NEW PRICE OF THE REGULAR PRODUCTS After 2% INCREASE***
1:RegularProduct: Nail Box $5.19
5:RegularProduct: Nuts and Bolts $3.24
****** CLEARANCE SALE! -- REDUCE PRICE OF THE SEASONAL PRODUCTS BY ANOTHER 40%**
****** NEW PRICE OF THE SEASONAL PRODUCTS After 40% DISCOUNT***
3:SeasonalProduct: Flowers $5.44; Discount 40.00%
Using the Code
- First, the database
EFHierarchy
containing table Product
needs to be created. To do this, from SQL Server 2005 Management Studio, open the file (File->Open->File) , and select CreateEFHierarchyDB.sql, and press the Open button.
Please edit the default "C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA" path if you want to create the database at another location. Press the execute button. - After refreshing the view so that you see the
EFHierarchy
database, open the empty table Product
. Now open the Excel sheet EFHierarchy.xls, and select its rows 2 through 6, copy and paste them into blank table Product
. Make sure you press the execute button to fill data into table. - Unzip BusinessApplication.zip into a place where you would want to extract project files.
- Upgrade your Visual Studio 2008 with SP1 Visual Studio 2008 Service Pack 1 (SP1) and .NET Framework 3.5 SP1 Downloads are now available for download from MSDN.
- Open the BusinessApplication.sln (File-> Open -> Project/Solution), build and run.
- Each time you will run the project, the regular products (item #1 and 5) will keep on becoming more and more expensive (by 2% each time); and seasonal products would lose their value by 40%
Points of Interest/Credits
Although we started with a single table, the application of Object Oriented principals through the friendly EF designer of the Visual Studio made it possible to write down a pretty involved application with a minimum of hand written code. The splitting of application into three source file/ two assemblies was purposely done so that they are potentially owned/checked in/packaged by different teams of varying skills set/responsibilities. The example in this article was based upon the article of Erick Thompson, ADO.NET team blog. The architectural philosophy is deeply influenced by the teachings of Martin Fowler and Robert C. Martin.
I recently discovered a great tool for advanced modeling. My special thanks for Diego Vega of Microsoft, who pointed me towards this very useful resource while answering my question in ADO.NET Entity Framework and LINQ to Entities (Pre-release) forum. I recommend the readers to download the simple, but exciting Entity Framework Mapping Helper v1.0.