Entity Framework Learning Guide
Entity Framework Learning Guide
[email protected]
http://weblogs.asp.net/zeeshanhirani
3.2 Using Load Operator to Lazy Load Collection and entity reference ................................. 113
3.3 CreateSourceQuery ....................................................................................................... 120
3.3.1 CreateSourceQuery to filter associations .....................................................................................120
3.3.2 CreateSourceQuery to Execute Aggregate operation on Child collections...................................122
3.3.3 CreateSourceQuery to retrieve specific derived type from entity collection ............................... 123
Problem: You have a database and want to generate the entity data model
using the database. You also would like to explore various options in the
designer to customize entity classes. You also like discover how to update the
model when a change is made in the database.
Discussion:
1. In your project add a new item of type Entity Data Model. I will call
the model NorthWind.edmx since we are going to generate the model
for NorthWind database.
4. Choose the tables, views and stored procedures that you would like to
represent and use in your conceptual model. Notice that among our list
of database objects, there is no option to bring functions either scalar or
table valued function. Version 1 release of the entity framework does
not support functions. I will select tables, views and stored procedures.
In this part of the wizard, I also get to choose the namespace where my
object context will be created.
After clicking Finish, entity framework creates NorthWind.edmx file
which contains entity data model. Additionally it also creates an
app.config which contains the connection string to connect to the data
model and the database. Usually you will create the entity data model
in a business layer and the connection string will be created in
app.config file inside of business layer which will not be of any use if
you will add the business layer to a console, asp.net or windows
application. You will have to copy the connection string from
app.config to the app.config file of either the asp.net windows form or
console application. Example below shows a small section of the
generated entity data model.
Entity data model generates different relations based on the schema
defined in the database. Orders have o to 1 relationship with customer.
The reason the relation is marked as 0 to 1 is because in the Orders
table customer id column is defined as allow null so an order can
optionally have 0 to 1 relationship to customer. In the case of
OrderDetails, OrderId is a required field, so the generated relation is 1
order can have many OrderDetails and an OrderDetail must belong to
an Order. When you create linq to sql model, the designer
automatically fixes the names of entities from plural to singular.
However EDM designer leaves the names as they are defined in the
database. You have few options if you want to change the name of an
entity. Clicking on the name of the entity in the designer twice will let
you change the name. You can also right click an entity in the designer
and access its properties which will also let you change the name of the
entity. Example below shows the change in action.
Not only can you can the name of the navigation properties, but you
can also change association of the relationship defined between two
entities. For example, on the EDM model, customer and orders are
related to each other with a line indicating 0 to many relationships. We
can alter the relationship by selecting the relationship line and
accessing its properties window. Below is the screen shot that shows
how the properties window for the relationship between customer and
order.
The screen above shows that relation has two ends. One end is the
Orders which is the many side of the relationship indicated by
Multiplicity. Other end of the relationship is the customer which is 0-1.
If you decide that relationship inferred for Customer side is incorrect
and that an order will always have a customer, you can change the
multiplicity from the Customer side to 1 as shown below.
Left side of the window is a section to map insert, update and delete
operations performed on an entity to stored procedures. If you will take
advantage of the dynamic sql statement generated by the entity
framework to perform crud operations, you don’t need to map stored
procedures for inserts, updates and deletes. Figure below shows screen
that maps crud operations on an entity to stored procedure.
Problem: You want to know different ways edmx schema files can be loaded
in an application from embedding in output assembly to looking for the
schema file in output directory.
Solution:
Discussion:
If you are not using entity framework designer meaning hand coding your
schema files or generating the schema files using edmgen utility that comes
with entity framework, you will start with 3 physical files in your class
library project. On the properties window of the files, you can set copy to
output directory to true. Thus when you add a reference to you class library
project, all 3 files will be copied to the bin directory of the consuming
project whether it be a console, windows form or asp.net application. Screen
shot below how to set up copy to output directory to true on a schema file.
On the above screen shot, I have set my Build Action to None and Copy to
Output directory to always. Copy to Output has 3 options; Never Copy,
Copy always and copy when changed. After setting the copy action to true,
every time you build the project, schema files will be copied to the bin
directory of the project. If schema files are not in root of the class library
project, than when files gets copied over to the bin directory, the directory
structure remains intact. Example below shows schema files reside in
ComplexType directory of my class library project. When you build the
solution, schema files are inside Complex Type folder of the bin directory of
the project.
When you create an instance of ObjectContext, the constructor is passed the
name of the connectionstring to look for in web.config or app.config. Inside
the Meta section of the connection string, you must explicitly specify the
folder name ComplexType created in the bin directory and where the
schema file resides.
Other option you can take is embed all 3 files as an embedded resource in
the assembly and when the assembly gets copied over in the build process to
the bin directory, you will have all 3 schema files. Screen shot below shows
setting all 3 files as embedded resource and confirming that all files are
actually stored as resource for our NorthWind.business.EF class library
project.
Since we have configured the schema files to reside inside the dll we need to
change the connection to as follows.
<add name="NWComplexTypeEntities"
connectionString="metadata=res://*/NorthWind.Business.EF.ComplexType.No
rthWindEFComplexType.csdl
|res://*/NorthWind.Business.EF.ComplexType.NorthWindEFComplexType.ssdl|
res://*/NorthWind.Business.EF.ComplexType.NorthWindEFComplexType.msl;”
/>
For clarity purpose I am not showing the entire connection string except how
to load the schema files. I am using res to find the specified csdl, msl and
ssdl in any assembly it can find. To reference our csdl, msl and ssdl, we are
fully qualifying the file with the assembly name where the files are
embedded as a resource. The res option has different options you can use to
ease the search of finding schema files. Following table defines the different
options you can use with res to search for schema files.
Also when you open up the properties window for edmx designer, the
Metadata Artifacts Processing is set to Embed in Output Assembly which
means to embed our 3 schema files as an embedded resource as shown
below.
Problem: You have Plain Old CLR Objects and you need to know how to use
entity framework mapping feature to map your business entities and its
attribute to columns in a table.
(NamespaceName="LinqCookBook.EFUsingPOCO",Name="Customer")]
public class Customer : IEntityWithChangeTracker,
IEntityWithKey
{
//IEntity tracker is required to participate in
change tracking.
changetracker.EntityMemberChanging(propertyname);
}
}
protected void ReportPropertyChanged(string
propertyname)
{
if (changetracker != null)
{
changetracker.EntityMemberChanged(propertyname);
}
}
EntityKey entitykey;
public System.Data.EntityKey EntityKey
{
get
{
return entitykey;
}
set
{
entitykey = value;
}
}
string customerid;
[EdmScalarPropertyAttribute
(EntityKeyProperty=true,IsNullable=false)]
public string CustomerID
{
get{return customerid;}
set
{
ReportPropertyChanging("CustomerID");
customerid = value;
ReportPropertyChanged("CustomerID");
}
}
string contacttitle;
[EdmScalarPropertyAttribute]
public string ContactTitle
{
get
{
return contacttitle;
}
set
{
ReportPropertyChanging("ContactTitle");
contacttitle = value;
ReportPropertyChanged("ContactTitle");
}
}
string companyname;
[EdmScalarPropertyAttribute(IsNullable=false)]
public string CompanyName
{
get{return companyname;}
set
{
companyname = value;
}
}
}
}
Discussion:
Listing 1-2
private IEntityChangeTracker changetracker;
public void SetChangeTracker(IEntityChangeTracker
changeTracker)
{
this.changetracker = changeTracker;
}
changetracker.EntityMemberChanging(propertyname);
}
}
protected void ReportPropertyChanged(string
propertyname)
{
if (changetracker != null)
{
changetracker.EntityMemberChanged(propertyname);
}
}
Listing 1-5
string customerid;
[EdmScalarPropertyAttribute
(EntityKeyProperty=true,IsNullable=false)]
public string CustomerID
{
get{return customerid;}
set
{
ReportPropertyChanging("CustomerID");
customerid = value;
ReportPropertyChanged("CustomerID");
}
}
In Listing 1-5, I am exposing my customerid property by attributing the
property with EdmScalarPropertyAttribute. I am also passing two additional
parameters to the attribute. First parameter EntityKeyProperty is set to true to
tell that customerid is the key property on the customer object. Since
cutomerid cannot be null, I am also setting isnullable to false. The other
properties on the Customer object also work the same way so I am not going
to cover them. In the setter of all the 3 properties, I am calling
ReportingPropertyChanging and Changed to notify change tracker when a
property is changing and has changed. This information is useful for the
framework to identity the state of the object.
Once I have defined my customer object, I have to create 3 xml files. First file
will be our conceptual schema which defines how our customer object and its
properties. Second file would be our schema file that defines customer table
in the database meaning what columns the table has, its datatype and what is
the primary key column. The third file will be our mapping file which defines
how to map our customer object to customer table in the database. Listing 1-6
shows conceptual schema of our customer object represented in xml format.
Listing 1-7
<?xml version="1.0" encoding="utf-8" ?>
<Schema Namespace="LinqCookBook.EFUsingPOCO" Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="NorthwindEntities">
<EntitySet Name="Customers"
EntityType="LinqCookBook.EFUsingPOCO.Customer" />
</EntityContainer>
<EntityType Name="Customer">
<Key>
<PropertyRef Name="CustomerID" />
</Key>
<Property Name="CustomerID" Type="String"
Nullable="false" />
<Property Name="ContactTitle" Type="String" />
<Property Name="CompanyName" Type="String"
Nullable="false" />
</EntityType>
</Schema>
In listing 1-6, we start with EntityContainer that defines the namespace where
customer object reside. Entity container has EntitySet which defines the
entities that we are going to be exposing through our objectcontext. In our
case we are going to be exposing Customers which would be of Customer
type. When we work on building our ObjectContext, you will see that we use
Customers to query for customer object. Next we define our entity in
EntityType attribute by defining the key property in the customer table
followed by other properties with their data type and whether the property can
allow null or not.
Next we define our customer schema in NorthwindModel.ssdl schema file as
shown in listing 1-7. Schema file in listing 1-7 contains an entity set with
Name being the table name and entity type being the type for customer. Than
using the Entitytype element, I am defining my customer table with the
column information, column’s data type, length, and whether the column is
nullable or not. I am also defining the primary key column in the customer
table by using key element and specifying CustomerId as the PropertyRef.
Listing 1-7
<?xml version="1.0" encoding="utf-8" ?>
<Schema Namespace="NorthwindEFModel.Store"
Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl"
Provider="System.Data.SqlClient"
ProviderManifestToken="2008">
<EntityContainer Name="dbo">
<EntitySet Name="Customers"
EntityType="NorthwindEFModel.Store.Customers"/>
</EntityContainer>
<EntityType Name="Customers">
<Key>
<PropertyRef Name="CustomerID" />
</Key>
<Property Name="CustomerID" Type="nchar"
Nullable="false" MaxLength="5" />
<Property Name="CompanyName" Type="nvarchar"
Nullable="false" MaxLength="40" />
<Property Name="ContactTitle" Type="nvarchar"
MaxLength="30" />
</EntityType>
</Schema>
Once I have defined the storage model, I have to create a mapping file which
defines how customer entity maps to customer table. Listing 1-8 shows the
mapping file I have created. Mapping file has an attribute
CdmEntityContainer that defines the namespace where my entities reside.
Within in the EntityTypeMapping, I am specifiying my customer type with
TypeName maps to Customers table defined by StoreEntitySet. Inside the
MappingFragment, I am mapping scalar properties on customer entity to
column name in customer table.
Listing 1-8
<Mapping Space="C-S" xmlns="urn:schemas-microsoft-
com:windows:storage:mapping:CS">
<EntityContainerMapping
StorageEntityContainer="dbo"
CdmEntityContainer="NorthwindEntities">
<EntitySetMapping Name="Customers">
<EntityTypeMapping TypeName="
LinqCookBook.EFUsingPOCO.Customer">
<MappingFragment StoreEntitySet="Customers">
<ScalarProperty Name="CustomerID"
ColumnName="CustomerID" />
<ScalarProperty Name="CompanyName"
ColumnName="CompanyName" />
<ScalarProperty Name="ContactTitle"
ColumnName="ContactTitle" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
Now that we have defined our conceptual modal, mapping and storage model,
we need to create our datacontext class which can talk to the model we have
defined in the xml file. Listing 1-9 shows the code for NorthwindEntities
class. In the example, I am creating NorthWindEntities class which inherits
from ObjectContext. ObjectContext class is responsible for querying and
working with entity data as objects. The constructor of the class takes two
parameters. First parameter represents the name of connectionstring defined
in either web.config or app.config. For this example, I have defined the
connectionstring inside app.config as follows
<add name="nwefpoco"
connectionString="provider=System.Data.SqlClient;metadata=EF
UsingPOCO\Schemas\NorthwindEFModel.csdl|EFUsingPOCO\Schemas\
NorthwindEFModel.msl|EFUsingPOCO\Schemas\NorthwindEFModel.ss
dl;provider connection string="Data
Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\DB\Nort
hwindEF.mdf;Integrated Security=True;User
Instance=True;MultipleActiveResultSets=True""
providerName="System.Data.EntityClient" />
2. Modeling Entities
Problem: Figure below shows the database diagram for different types of
Media belong to many categories.
On the figure above, we have MediaCategories table which has Categories for
various types of Media including Videos and Articles. Each Category can
have subcategories which are identified by ParentCategoryId column in the
MediaCategory table. This makes MediaCategory a self referencing table
where Categories and SubCategories are stored in 1 table and to find out the
parent category for a category, we have to look at ParentCategoryId. When
the ParentCategoryId is null, we are at root Category. Media table contains
common fields across both Articles and Videos. Fields specific to Video and
Articles are stored in Article and Video table. You want to import the above
table structure using Self referencing entity which would allow us to get
Subcategories for a given Category. Additionally each Category should
expose a navigation property Medias which should be collection containing
Articles and Videos.
Solution: When a table with self referencing relationship is imported into
EDM, entity framework automatically creates an association to the entity
itself. Since the names generated by the designer for the navigation properties
are obscure, you will have to changes the names of the relationship. To import
the above table structure use entity framework designer. The wizard will
create an association to MediaCategory itself. In addition, Many to Many
relationship between MediaCategory and Media entity will be created because
MediaInCategories is a join table with no payloads, EF will ignore this table.
Since the Media table will contain two types of Media, extend the Article and
Video class to inherit from Media entity and configure the mappings for both
entities using mapping window.
Now that the model is complete we can write some queries against the
model to return data. If want to access all the top level categories and
the total Articles and videos associated with the category, we can write
the following query.
cat.Medias.OfType<Article>().Count(),cat.Medias.OfType<Video>().Count());
}
On the code above I am retrieving the top level category by checking if parent
category is null. If the parent or root category is null, the category is a root level
category. I am also filtering the categories to ones that have some kind media
associated with it. Along with Category I am also retrieving the Media association
by using Include operator. Since Media is comprised of Article and Video entity,
the collection may contain both types of Media. To explicitly find the articles and
videos in a given category, I am using OfType operator. Figure below shows the
output on the console window.
Problem: Figure below shows the database diagram for Employee table that
contain Employees with different role.
The above employee table contains Employee with different roles. An
employee could be President, Manager, Supervisor or a SalesAgent identified
by the EmployeeType column.Each Employee reports to an employee
identified by ReportTo EmployeeId; for instance a sales agent would report to
a Supervisor and a supervisor would report to Manager and Manager would
report to the President. You want to import the above table schema using
Table per Hierarchy and each Employee should have a navigation property
ReportsTo that tells which Employee, the employee reports to. Completed
entity data model should look as follows.
Solution:
Discussion:
1. Import Employee table into EDM wizard. Figure below shows the
model created by the wizard.
Change the name of Employee1 on the figure to Employees as it is the
Many side of the relationship and change Employee2 to ReportsTo.
Also change the names of the roles for the self referencing association
created by the designer. Call Many side of the role as Employees and 1
side of the role as ReportTo. Figure below shows the name for the
association.
<EntityTypeMapping TypeName="IsTypeOf(SFTPHModel.Manager)">
<MappingFragment StoreEntitySet="Employee" >
<ScalarProperty Name="EmployeeID"
ColumnName="EmployeeID" />
<ScalarProperty Name="Salary" ColumnName="Salary"/>
<Condition ColumnName="EmployeeType" Value="Manager" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(SFTPHModel.President)">
<MappingFragment StoreEntitySet="Employee" >
<ScalarProperty Name="EmployeeID"
ColumnName="EmployeeID" />
<ScalarProperty Name="Salary" ColumnName="Salary"/>
<Condition ColumnName="EmployeeType"
Value="President" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(SFTPHModel.Supervisor)">
<MappingFragment StoreEntitySet="Employee" >
<ScalarProperty Name="EmployeeID"
ColumnName="EmployeeID" />
<ScalarProperty Name="Salary" ColumnName="Salary"/>
<Condition ColumnName="EmployeeType"
Value="Supervisor" />
</MappingFragment>
</EntityTypeMapping>
On the code below, I am mapping EmployeeId and Salary property to
each entity deriving from SalariedEmployee. The reason we are doing
it individually is because SalariedEmployee only serves as abstract
class on our conceptual model and does not have any table mapping on
the database.
To test the model, we can retrieve our top level employee which is the
president. An immediate child for the president would be manager,
which will have children of supervisor and that will have SalesAgents
working under him. Code below shows how we retrieve four level deep
hierarchies.
Solution: EF does not support recursive queries that can return all employees
that indirectly reports to the President. The ReportsTo navigation property
only returns the immediate employees that report to presidents which are
Managers. If we want our result to also include employees that report to
Manager and so on, we need to make use of Common Table Expressions.
CTE is a feature introduced in Sql server 2005 that allows recursive queries
until you reach to the end of the list. To use CTE, we need to create a stored
procedure on the database that takes an EmployeeId and returns all employees
that directly and indirectly reports to the employeeid passed in. The stored
procedure then needs to be imported into the store model. To use the stored
procedure inside our ObjectLayer, we need to use FunctionImport to import
the stored procedure into the conceptual model and set the return type to
Employee entity. Since the employees returned by the stored procedure would
contain different types of employees ranging from Manager, Supervisor and
SalesAgent, we need to map each row returned by the stored procedure to
appropriate derived entity based on the EmployeeType column. After setting
up the mapping, we can call the method created on the ObjectContext that
takes in the EmployeeId and returns a list of Employees that directly or
indirectly reports to the that employee.
Discussion: Steps below outline the process of returning recursive queries for
a self referencing entity.
We could have created a stored procedure and referenced the stored procedure
inside of the function but for demo purposes, it is easier to declare the sql
inline using CommandText option. Beware if you use this technique, your
function will get overwritten the next time you try to update the model from
the database.
2. Import the stored procedure on the store model into the conceptual
model by using Function Import option which can be accessed by right
clicking the stored procedure on the model browser and choose Create
Function Import. On the dialog, set the return type for the method to
Employee entity. Figure below shows the values inputted on the dialog.
<FunctionImportMapping FunctionImportName="GetSubEmployees"
FunctionName="EcommerceModel.Store.GetSubEmployees">
<ResultMapping>
<EntityTypeMapping
TypeName="SelfReferencing.SalesAgent">
<Condition ColumnName="EmployeeType"
Value="SalesAgent"/>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="SelfReferencing.Manager">
<Condition ColumnName="EmployeeType"
Value="Manager"/>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="SelfReferencing.Supervisor">
<Condition ColumnName="EmployeeType"
Value="Supervisor"/>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="SelfReferencing.President">
<Condition ColumnName="EmployeeType"
Value="President"/>
</EntityTypeMapping>
</ResultMapping>
</FunctionImportMapping>
On the above code, based on the value on the EmployeeType returning
I am mapping the record to a particular derived entity. For instance if
EmployeeType has a value of SalesAgent, then we need to create an
instance of SalesAgent entity.
Code below test the above recursive method we have created on the
ObjectContext.
var db = new SFCTEPHEntities();
var president = db.Employees.First(e => e.ReportsTo == null);
var emps = db.GetSubEmployees(president.EmployeeID);
Console.WriteLine("All Employees working under president\r\n");
foreach (var emp in emps)
{
Console.WriteLine("Name:{0} Type:{1}", emp.Name,
emp.GetType().Name);
}
You want to import the above model as a User entity which has a Many to
Many relationship to itself.
Solution: When we import the above model using EF model wizard, EF will
create a User entity and also create an M-M relation to the User entity by
picking up that there is a link table between User-Friends and User. Figure
below shows the model configured for the above table structure.
With the current conceptual model shown above, if I wanted to find all my
contacts, I need to use Contacts navigation property. Contacts navigation
property returns all the contacts I have made. But if I am the contact for
someone else then he also becomes my contact as well. To access those
Contacts, I can use OtherContacts navigation property to return Users who
made me their contact.
Discussion:
1. Import User and Friends table using EDM wizard. When the wizard
completes, you will have a User entity that has a M-M association to
itself. Figure below shows the model created by the wizard.
2. Change the navigation property User1 to Contacts and User2 to
OtherContacts. Both navigation property should return a collection of
Contacts because the both ends of the association refer back to User
entity.
To test the above Self referencing User entity with M-M association to
itself, we can create a user and add contacts to the user. Then using a
second datacontext we can query for all contacts for a user by using both
navigation properties Contact and OtherContacts. Code below creates
multiple contacts for a user.
var db = new MMSFEntities();
var user = new User
{
Name = "Zeeshan",
Contacts =
{
new User
{
Name = "Chuck",
Contacts = {new User{Name="Kirk"}}
},
new User
{
Name="Larry"
}
}
};
db.AddToUsers(user);
db.SaveChanges();
On the above code, user Zeeshan has two contacts, Chuck and Larry. However
Chuck has one Contact Kirk but because Chuck became a contact for Zeeshan,
Zeeshan should be considered as one of OtherContacts for Chuck. Now using the
second datacontext, we can access the contacts for Zeeshan as follows.
var db2 = new MMSFEntities();
var zeeshan = db2.Users.Include("Contacts").First(u => u.Name ==
"Zeeshan");
Console.WriteLine("Zeeshan's Friends");
foreach (var User in zeeshan.Contacts)
{
Console.WriteLine(User.Name);
}
To access all contacts for Zeeshan, we are eagerly loading Contacts navigation
property for Zeeshan and printing the result to console window. However to access
all contacts for Chuck, we have to eagerly load Contacts and OtherContacts
navigation property. Code below retrieves all contacts for Chuck.
var chuck = db2.Users.Include("Contacts").Include("OtherContacts").First(u =>
u.Name == "Chuck");
//chuck's friend is Kirk
Console.WriteLine("\r\nChuck's Friends");
foreach (var User in chuck.Contacts)
{
Console.WriteLine(User.Name);
}
//zeeshan has Chuck as his contact which makes zeeshan as Chuck's
otherContact.
foreach (var User in chuck.OtherContacts)
{
Console.WriteLine(User.Name);
}
On the above code, we can see that when we access Contacts navigation property
for chuck, we only get Kirk because Chuck only made Krik as his contact.
However since the user Zeeshan made Chuck his contact, we can access Zeeshan
through Chuck’s OtherContact navigation property to find out who made Chuck as
his contact. Screenshot below shows the final result printed on the console window
which shows that Chuck has two contacts; Kirk and Zeeshan.
When we import the model, we get two entities MusicalShow and Sponsors.
MusicalShow has a navigation property Sponsors that lets you access all the
sponsors for a given MusicalShow. Similarly if you have sponsor entity, you
can access all the musicalshows the sponsor has contributed to by using the
MusicalShows navigation property. Both navigation properties are exposed as
an EntityCollection. The association line between MusicalShow and Sponsor
is a many to many which means both ends of the association have a
multiplicity of Many. To see that clearly we can select the association line
and look in the property window to see the End role for each side of the
association set. Screen shot below shows the properties windows for many to
many association set.
Now that we have modeled our entities, we can program against these entities
to retrieve many side of relationship using eager loading or lazy loading. In
the code below I am using Include and Load operator to load many side of
relationship for sponsors.
var db = new ManyToManyEntities();
var miller = db.Sponsors.First(s => s.Name ==
"Miller Lite");
//lazy load the miller
if (!miller.MusicalShows.IsLoaded)
{
miller.MusicalShows.Load();
}
Console.WriteLine("Lazy Loading");
Console.WriteLine("Shows for miller lite " +
miller.MusicalShows.Count());
//eager loading
var db2 = new ManyToManyEntities();
var miller2 =
db2.Sponsors.Include("MusicalShows").First(s => s.Name ==
"Miller Lite");
Console.WriteLine("Eager Loading");
Console.WriteLine("Shows for miller lite " +
miller2.MusicalShows.Count());
On the above code, to load the MusicalShows entity collection, I am calling
Load on MusicalShows if it is not already loaded. This process is called lazy
loading of entities. Similarly to eager load MusicalShows, I can use the
include operator with Sponsor to indicate that when I bring sponsor, also
retrieve MusicalShows for the sponsor. It is important to mention at this point
that EF does not support relationship span for many to many relationship.
Relationship span is a feature which allows EF framework to fix relationships
of objects that are loaded separately. For instance if I load customers and
orders separately and if the customers in the object context have an
association to any of the orders tracked in ObjectContext, Ef framework will
fix the relationship automatically and associate those orders to customer. With
Many to Many relationships, retrieving the many side of the relationship can
be an expensive operation as it requires an extra join to link table and is not
take care by the framework. In the code below, I have retrieved the shows and
the sponsor separately and when I access the shows for the sponsor, I get a
return value of 0 which indicates that EF did not fix the navigation
relationship for me although both sides of relationship were loaded in the
objectcontext.
var db = new ManyToManyEntities();
var miller = db.Sponsors.First(s => s.Name ==
"Miller Lite");
var shows = (from show in db.MusicalShows
from sponsor in show.Sponsors
where sponsor.Name == "Miller Lite"
select show
).ToList();
Console.WriteLine("using relationship span");
Console.WriteLine("shows for miller sponsor " +
miller.MusicalShows.Count());
To delete and add relationship between show and a sponsor we can make use
of Add Delete method exposed on entity collection for both sides of the
navigation. Example below shows how to add and delete MusicalShows from
a sponsor.
var db = new ManyToManyEntities();
var show1 = new MusicalShow { ShowName = "Johnny and
the Sprites" };
var show2 = new MusicalShow { ShowName = "Sesame
Street" };
db.SaveChanges();
//to delete show relationship with miller
//sponsor simply remove it from collection
miller.MusicalShows.Remove(show1);
Solution: When you define a link table between two tables, entity framework
by default represents the relationship as many to many. This makes it easy to
retrieve both sides of the relationship. However if we just care of about the
relationship without additional columns, there is no direct support from entity
framework because link table is not exposed. With a simple linq query
containing nested from clause, you can retrieve only the relationship
information and return the results as anonymous class.
Discussion: To retrieve the show and the sponsor without any hierarchy, we
have to flatten the list. You can accomplish this either by using SelectMany
operator in a method syntax or use nested from clauses to flatten the
hierarchy. Code below shows two different ways of retrieving only the
relationship between Show and Sponsor without extra columns.
Console.WriteLine(showsponsor.AsObjectQuery().ToTraceString());
showsponsor.ToList().ForEach(
sp =>
Console.WriteLine("ShowId {0} SponsorId
{1}",sp.ShowId,sp.SponsorId));
//using method syntax
var showsp = db.Sponsors.
SelectMany(s => s.MusicalShows,
(sponsor, show) => new {
sponsor.SponsorId, show.ShowId });
Console.WriteLine( "Using Method syntax");
showsp.ToList().ForEach(
sp =>
Console.WriteLine("ShowId {0} SponsorId
{1}", sp.ShowId, sp.SponsorId));
On the above code, I am only retrieving the showId and sponsorId for every
relationship. First option uses the query syntax and second option uses
method syntax by making use of SelectMany operator. To confirm that query
only returns the relationship column, I am also printing the sql query send to
the database. Screen shot below shows the results of along with the query
send to the database.
Solution: By default when you import two tables joined by a link table that
does not have any payloads, entity framework will remove the link table and
create many to many association between the entities. If you want to keep the
link table intact because later down you will have additional columns that
pertain to the relationship, you can edit the model generated by the designer
and introduce the link table. Although with the introduction of link table, the
query would get complicated as now you would have to travel the link table
to get to the many side of the entity. Customizing the model generated by the
designer is fully supported.
To change the model, we will first delete the many to many relationship link
and a new entity called StudentCourse that has 2 properties CourseId and
StudentId mark both of them as entity key. You can either create a property
by right clicking StudentCourse entity and choosing Add a Scalar Property.
Since I already have both these entities available on Course and Student
entity, I will go ahead and copy the properties and paste it on my
StudentCourse entity. Screen shot below shows how StudentCourse entity
look like.
Creating Student Course entity.
We are not done because we need to also map the new entity StudentCourse
that we created earlier. To map StudentCourse entity to a table, right click the
entity and select table mapping. On the table mapping window choose
StudentCourse table and the designer will map the properties on
StudentCourse entity with StudentCourse table on the database. Screen shot
below shows the StudentCourse entity mapping.
Now if you try to validate the model, you should not get any errors. Screen shot
below shows the completed EDM model transformed from many to many to two 1
to many association.
To query against the model would be little extra steps because to get to a course for
a student we have to travel an additional entity StudentCourse and access its
Course property to access course instance. Example below shows how to access
courses for a student.
var db = new ManyToManyEntities();
var st =
db.Students.Include("StudentCourse.Course").First(s => s.Name ==
"Zeeshan");
foreach (var stcourse in st.StudentCourse)
{
Console.WriteLine(stcourse.Course.CourseName);
}
On the above code, I retrieve student Zeeshan and since I will be accessing all
its relationship, I am loading all these relations ahead of time by using Include
operator. Include method allows me to specify a path which can be any level
deep. After getting reference to student instance, I loop through all the
StudentCourses and for each StudentCourse I access its Course property and
print the Name of the course the student is enrolled in. Screen shot below
shows the output on the console window.
Problem: You have defined 3 tables in the database; Club, Members and
Membership. A club can have many members and a member can be part of
many clubs. This relationship is stored in Membership table. Membership
table carries a payload column MemberDues that stores how much Dues a
member has towards a club. Because Membership table contains additional
columns apart from the primary keys from Club and Members table, entity
framework models the relationship as two 1 to Many associations. This is
good for inserts and updates but for querying purpose to know what clubs a
member is associated with, extra joins with MemberShip table, makes the
query harder to read and adds noise to the code. You want your entity data
model to support Many to Many relationship in addition to two 1 to many
relationship. For querying purpose you want to leverage the Many to Many
relationships but Inserts and Updates can still be processed using a separate
Membership link table.
Solution: When we import Club and Members table, entity framework does
not get rid of the link table because it has additional columns that are required
for the relationship and cannot be specified on insert and update if link entity
did not exists. For querying purpose, we can also create an additional
association that has Many To Many relationship between Club and Members
that is void of additional columns. To accomplish that we have to manually
modify the SSDL file as this is not supported in the designer. In the ssdl
model, we have to create an EntityType that contains only the primary keys
from Club and Members entity. In addition we have to define an entityset that
will use a DefiningQuery to only retrieve the columns we mentioned in the
entity type earlier and also map the results of the query to the EntityType.
After creating EntityType and EntitySet, we can go into the designer and
create a Many To Many association between Clubs and Members and map the
association to the entityset we created in the ssdl.
After creating the tables, we will import the model into EDM by using Update
Model from the database. Screen shot below shows the entity data model
created by EF when we import the tables.
The next step is to create a new entity type called ClubMember with only
primary keys from Club and MemberShip table which is ClubId and
MemberShipId. The entity would be part of an entityset which we will call
ClubMembers and data for the entityset would come from a defining query.
Now you must be wondering why we cannot simply create an association
between club and members and map the association to existing Membership
table we imported earlier. The reason is because you cannot map two entities
to a single row in a table. Since we already have mapping for a record in
membership table that was created when the model got imported as two 1 to
many relationship, we cannot reuse that. When you try to map the Many to
Many associations to the same Membership table, edmx validation would
raise the following error.
Error 3034: Problem in Mapping Fragments. Two entities with possibly different keys are
mapped to the same row.
SSDL below shows the EntityType and EntitySet created that will later be
used in mapping Many To Many associations between club and Members.
<EntitySet Name="ClubMember" EntityType="Self.ClubMember">
<DefiningQuery>
SELECT ClubId,MemberId from
MemberShip
</DefiningQuery>
</EntitySet>
<EntityType Name="ClubMember">
<Key>
<PropertyRef Name="ClubId" />
<PropertyRef Name="MemberId" />
</Key>
<Property Name="ClubId" Type="int"
Nullable="false" />
<Property Name="MemberId" Type="int"
Nullable="false" />
</EntityType>
On ssdl above, ClubMember only contains entity keys from Club and
Members table. The results of ClubMember entity set comes from a defining
query which only retrieves ClubId and MembershipId columns and is mapped
to ClubMember entity type. Since we are editing the SSDL model manually,
it is important to know that if you try to update the model again, you will lose
all your changes as the ssdl model gets overwritten. In the next release of EF,
the update model wizard would preserve changes made to ssdl model even
after updating the model from the database. After creating entity type and
entityset we can go back to the designer and add Many to Many association
between Club and Member by right clicking Club entity, selecting Add and
choosing association. Screen shot shown below captures the association we
have created.
The above assocication has Club and Members as both sides of the
association with a multiplicity of Many. To access the Members for Club
from Club entity, we will use Members navigation property. Similarly to
access Clubs collection from member, we will use Clubs navigation property.
Next step is to map the association to the virtual table we created earlier on
the stored. To do that, right click the Many to Many associations we created
earlier and select table mapping. When you select ClubMember table, the
designer auto populates the fields with entity keys defined on both ends of the
association.
Problem: You have 3 tables in the database called Actors, Movies and
ActorMovie link table. An actor can be part of many movies and a movie can
have many actors. The link table also contains an additional column
IsLeadingRole which determines if an actor is playing a leading role in the
movie or not. You want to represent the Many To Many table as two types of
associations in EDM. An Actor should have two types of Navigation to
Movie entity; MoviesWithLeadingRole and MoviesWithSupportingArtist.
MoviesWithLeadingRole will return Movies where the actor has a leading
role. MoviesWithSupportingArtist will return Movies where the actor is a
supporting actor.
Solution: To map the link table as two different Entityset, we need to create
two views, one view will return ActorMovie table where IsLeadingRole is 1
and other view will return ActorMovie table where IsLeadingRole is 0. Next
step is to import the views, Actor and Movie table into entity data model and
then create two many to many association between actor and Movies. One
association would map to a view where IsLeadingRole equal to 1 and other
association would map to view where IsLeadingRole is 1. Since views does
not support inserts and updates, we will have to create separate stored
procedure which will be responsible for inserting into our link table.
Discussion: First step is creating the 3 tables Actor, Movies and ActorMovie
link table. For Actor table define ActorId as the primary key. For Movies
table, define MovieId as the primary key and ActorMovie link table will have
ActorId and MovieId as the primary key. In addition ActorMovie link table
will also contain IsLeadingRole bit field that tells if the actor has a leading
role in the movie or not. Database diagram below shows how the relationship
between tables looks like.
Since we want to maintain two different types of movie collection for an
actor; one with leading role and other with supporting roles, we need to create
two views that return appropriate data from Actor_Movies table. Views
shown below achieve our requirement.
create view dbo.MoviesWithLeadingActor
as
select ActorId,MovieId from Actors_Movies where IsLeadingRole = 1
Later we will create two many to many associations on the EDM and map
them to our view. Since views do not support insert and deletes, we need to
create two stored procedures that will be responsible for inserting and deleting
into the Actor_Movies link table. Stored procedures below serve our need.
create proc dbo.InsertActor_Movies
(@ActorId int,@MovieId int,@IsLeadingRole bit)
as
begin
insert into Actors_Movies values (@ActorId,@MovieId,@IsLeadingRole)
end
Since Many To Many table does not have a concept of Update, we only need
stored procedure for Insert and Update cases. The insert stored procedure
takes ActorId, MovieId and IsLeadingRole to insert into Actor_Movies table.
To delete an entry from Actor_Movies table we are passing ActorId and
MovieId since they are primary key columns in the link table.
The next step is to import Actor and Movies table, our two views, and two
stored procedure we created for inserts and delete to link table. Screen shots
below shows how EDM model and model browser window looks like after
importing objects from the database.
First step is to delete the two entities MoviesWithLeadingActors and
MoviesWithSupportingActor which are based on views. Next step is to create
Many to Many association between Actor and Movies. This association will
represent a collection containing only movies where the actor has leading
role. To perform this operation, right click on Actors and Add Association.
You will get an association dialog. Screen shot below shows the values we
filled for the association.
On the Add Association dialog above, I have indicated that both ends of the
association will have a multiplicity of Many. To access Movies collection
from actor we will use MoviesWithLeadingActor navigation property. This is
because we will map this association to MoviesWithLeadingActor view.
After clicking okay on the dialog, you model should look like this.
To map the association we created, right clicking on the association line and
select table mapping. On the table mapping select MoviesWithLeadingActor
view and the designer would map the columns to properties on the association
automatically.
Similarly to create our second association right click on actors, select add
association and make both ends of the association as Many To Many and
name the navigation property that goes from Actor to Movies as
MoviesWithSupportingActor. Screen shot below shows the values we filled
for the association.
To map the association, right click the association line and select table
mapping. Map the association to MoviesWithSupportingActor view as shown
below.
If you try to validate the model, you will not get any errors. However when
we try to add items to the two collections created earlier, we will get an
exception because the associations are mapped to views and views are not
updatable. We need to map the associations to Insert and Delete stored
procedures imported earlier. Since mapping associations to stored procedure
is not supported by the designer, we have to manually edit the msdl and
specify insert and delete stored procedure. If we look at the insert stored
procedure created earlier, it has an additional parameter called IsLeadingRole
for which we cannot specify any mapping as it is not returned from our view
definition. To be able to use the same stored procedure for inserting either
ActorsWithLeadingRoles and ActorsWithSupportingRoles associations, we
need to leverage the CommandText property of the function definition on the
sddl and specify default values for IsLeadingRole depending on the
association being inserted. Example below shows the updated version of the
function that inserts LeadingActors and SupportingActors relationship.
<Function Name="InsertLeadingActor_Movies" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<CommandText>
exec dbo.InsertActor_Movies @ActorId = @ActorId,@MovieId =
@MovieId,@IsLeadingRole = 1
</CommandText>
<Parameter Name="ActorId" Type="int" Mode="In" />
<Parameter Name="MovieId" Type="int" Mode="In" />
</Function>
<Function Name="InsertSupporingActor_Movies" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<CommandText>
exec dbo.InsertActor_Movies @ActorId = @ActorId,@MovieId =
@MovieId,@IsLeadingRole = 0
</CommandText>
<Parameter Name="ActorId" Type="int" Mode="In" />
<Parameter Name="MovieId" Type="int" Mode="In" />
</Function>
We then need to map the stored procedures to insert and delete function of
associationSetMapping for both associations.
Now we can program against the model by adding the movie to right
collection depending on if the actor participated in a leading role or
supporting role. When we retrieve the movies, both collections would have
their correct types of movies filled. Code below creates three movies and adds
two movies to MoviesWithLeadingActor collection and one movie to
MoviesWithSupportingActor. Then using a different datacontext, I am
retrieving the collection count to ensure that I have two movies with
LeadingActor and 1 one movie with SupportingActor. When I am retrieving
the actor, I am using Include operator to load both collections ahead of time
rather then call load separately on each collection to do lazy loading.
var db = new MultipleAssocationsEntities();
var george = new Actors { Name = "George Clooney" };
db.AddToMovies(Syriana);
db.AddToMovies(BabyTalk);
db.AddToMovies(Roseanne);
george.MoviesWithLeadingActor.Add(BabyTalk);
george.MoviesWithLeadingActor.Add(Syriana);
george.MoviesWithSupportingRole.Add(Roseanne);
db.SaveChanges();
2. Since we only want to have a single entity that is mapped to all 3 tables,
move ClubName, Dues, AccountNumber and AccountBalance to Customer
entity and delete ClubMember and CustomerAccount entity created by the
designer. Figure below shows the updated model.
3. Map the additional fields moved from ClubMember and CustomerAccount
entity. To map the properties, right click on Customer entity and choose
mapping window. On the mapping window select Clubmember table and the
designer would auto map ClubName and Dues to properties defined on
Customer entity. Similarly select CustomerAccount table and EF would auto
map AccountNo and AccountBalance properties to columns on
CustomerAccount table. Figure below show the updated mapping configured
for Customer entity.
Ef is explicit about only loading items that you have requested. If you request
for a Customer entity and Customer entity has orders associated with it, EF
will not load the Orders entitycollection on behalf for you. There are two
ways to load related navigation properties of an entity. You can either use
Load or Include operator. You would use Load operator to lazy load a certain
navigation property. However in a case where along with Customer, you also
want to retrieve its Orders, you can Include operator. Include operator avoids
an additional database roundtrip, by fetching Customer and its Orders
collection in one single query.
On the above code, we are retrieving all the Orders for the every customer in
the list. If we did not use Include operator and tried to access Orders
collection, the return value we will get is zero.
When a query uses Include to load navigation properties, EF under the covers
rewrites the query into a projection which has in one column containing the
original entity type and other columns contains rest of the related entities.
Additionally it stores the Meta data about how the query needs to be mapped
back to the originally entity requested and fix up the relationship between the
original entity and the related entities when data is brought over from the
database. This ensures that data is not returned as DbDataRecord where the
first column contains original entity and rest of the columns has related
entities.
Console.WriteLine("ProdID:{0}\tCategory:{1}\tSupplier:{2}\tODS Count:{3}",
prod.ProductID,prod.Category.CategoryName,prod.Supplier.ContactName,
prod.OrderDetails.Count());
}
Figure below shows the Category and their related navigation properties
printed on the console window.
Problem: Figure below shows EDM model for Orders and its related and its
related entities.
Based on EDM model above you want to load top 2 orders which were
shipped city of London. For each of these orders, you want to load its Customer, its
OrderDetails and for each OrderDetails load its product information.
Solution: To load related entities for Order entity, we will use Include
operator. Since Customer entity is a navigation property on the Order entity, it will
only require adding an include of Customers. However if we want to load Products
which is a navigation property of OrderDetails entity which is a navigation
property of Order, we will have use a query path. Query path allows us to load
object graph defined by the path specified as string inside the include operator. For
instance to load OrderDetails and its product information for given Order, we can
use OrderDetails.Products.
Discussion: When you specify query path, you can go as many level deep in
the object graph as you want. However this would lead to a fairly complex query
which may result in a timeout in the execution of the query. Currently in v1, Ef
does not have the smartness to identify that query is quite big and it may be more
efficient to break the execution into two separate queries and then execute to
achieve better performance. Additionally an entity can have several includes and
each include can contain a query path or just a simple navigation property. Code
below shows how to load related entities for Order entity defined on the above
model.
var db = new LazyLoadingEntities();
var orders = from o in
db.Orders.Include("Customer").Include("OrderDetails.Products")
where o.ShipCity == "Caracas"
select o;
foreach (var order in orders)
{
Console.WriteLine("OrdID:{0} Customer:{1} TotalDetails:{2}
TotalProducts:{3}",
order.OrderID, order.Customer.ContactTitle,
order.OrderDetails.Count(),
order.OrderDetails.Select(od =>
od.Products.ProductID).Count()
);
}
On the code above I am getting all orders shipped in the city of Carcas.
Additionally I am loading each order’s customer, OrderDetails and for each
OrderDetail loading its product. On the console window I am printing OrderId,
Customer’s ContactTitle, count of OrderDetails for an order and for each order the
count of distinct products ordered. Figure below shows the result on printed on the
console window.
3.1.3 Eagerly loading navigation properties on derived Types
Problem: Figure below shows the EDM model for Gunsmith, its associated
company and Phone entity.
On the above conceptual mode, GunSmith extends Contact entity. A
Gunsmith belongs to a Company. The Company extends a Location entity
which has an association to Phone entity. In addition a company has Many
departments represented by 1 to Many association between Company and
Departments. You want to retrieve gunsmith, the company he belongs to, the
company’s phone and all the departments the company has in one single
query.
Db.Contacts.Include(“Company”)
The above query will lead to runtime error saying the there is no Company
property available on Contact. If we look at the model we can confirm that
surely there is no Company navigation property on Contact. Company
property is available on a type of Contact, GunSmith. Therefore to eagerly
load Company, we need to use OfType operator first to reach to Gunsmith
and then call Include for Company. While we are at the Company entity we
can use query path twice, first to load the Departments for the company and
second to load the Phone for the company exposed on the base entity location.
Figure below shows the output of the above query on the console window.
Console.WriteLine("Category:{0} Total
Medias:{1}",gunhistory.Name,gunhistory.Medias.Count());
Console.WriteLine(" SubCategories");
foreach (var subcategory in gunhistory.SubCategories)
{
Console.WriteLine(" Category:{0}
TotalMedias:{1}",subcategory.Name,subcategory.Medias.Count());
}
On the above code, as discussed I have two includes; first include retrieves all
the Medias associated with gunhistory category and the second include
retrieves both SubCategories for gunhistory and all its medias. To confirm the
result I am printing the name of the category and Total Medias in that
category. In addition I am looping through the subcategories for gunhistory
and also printing the sub category name and the Medias in those
subcategories. Figure below shows the result printed on the console window.
Solution: Using Include with Many to Many relationship is not any different
than with any other types of relationship offered in EF. To access both types
of movies, we will have to use include multiple times. Beware that using with
Many to Many relationship could turn to be an expensive because both
includes would require a join to the link table under the covers to access the
many side of the relationship.
Discussion: When you multiple includes to the same table EF creates a union
statement bring both results together in the same query. As metioned earlier
beware that an Include on M-M relationship could be expensive as it would
require an extra join cost which 1-M and 1-M relationship won’t incur. Code
below shows the linq query required to retrieve both types of movies for an
actor.
var db = new MultipleAssocationsEntities();
var actor = db.Actors
.Include("MoviesWithLeadingActor")
.Include("MoviesWithSupportingRole").First();
Console.WriteLine("Actor:{0} TotalMoviesWithLead:{1}
TotalMoviesWithSupport:{2}",
actor.Name,actor.MoviesWithLeadingActor.Count(),actor.MoviesWithSupportin
gRole.Count());
Just for the sake of understanding the complexity of using Include with M-M
relationship, I am also display the sql generated by EF when eager loading
multiple M-M relationship.
SELECT
..
FROM (SELECT
..
FROM (SELECT TOP (1)
[Extent1].[ActorId] AS [ActorId],
[Extent1].[Name] AS [Name],
1 AS [C1]
FROM [dbo].[Actors] AS [Extent1] ) AS [Limit1]
LEFT OUTER JOIN (SELECT
[Extent2].[ActorId] AS [ActorId],
[Extent3].[MovieId] AS [MovieId],
[Extent3].[Title] AS [Title],
1 AS [C1]
FROM (SELECT
[MoviesWithLeadingActor].[ActorId] AS [ActorId],
[MoviesWithLeadingActor].[MovieId] AS [MovieId]
FROM [dbo].[MoviesWithLeadingActor] AS [MoviesWithLeadingActor]) AS
[Extent2]
INNER JOIN [dbo].[Movies] AS [Extent3] ON [Extent3].[MovieId] =
[Extent2].[MovieId] ) AS [Project2] ON [Limit1].[ActorId] =
[Project2].[ActorId]
UNION ALL
...
FROM (SELECT TOP (1)
[Extent4].[ActorId] AS [ActorId],
[Extent4].[Name] AS [Name],
1 AS [C1]
FROM [dbo].[Actors] AS [Extent4] ) AS [Limit2]
INNER JOIN (SELECT [Extent5].[ActorId] AS [ActorId],
[Extent5].[MovieId] AS [MovieId2], [Extent6].[MovieId] AS [MovieId1],
[Extent6].[Title] AS [Title]
FROM (SELECT
[MoviesWithSupportingActor].[ActorId] AS [ActorId],
[MoviesWithSupportingActor].[MovieId] AS [MovieId]
FROM [dbo].[MoviesWithSupportingActor] AS [MoviesWithSupportingActor])
AS [Extent5]
INNER JOIN [dbo].[Movies] AS [Extent6] ON [Extent6].[MovieId] =
[Extent5].[MovieId] ) AS [Join3] ON [Limit2].[ActorId] =
[Join3].[ActorId]) AS [UnionAll1]
A contact can have two addresses, a billing address and shipping address.
You want to retrieve contact using esql query. Along with the contact you
want to retrieves billing and shipping address.
Solution: Include operator not only works with linq queries but you can also
use Include operator with esql queries as well. To retrieve contact and its
related address, we need to create an ObjectQuery that returns the contact
based on the criteria we request and then append the Include statements
specifying additional navigation properties that we want to load with Contact
entity.
On the code above, I am using ObjectQuery to build a query for contacts and
then using Builder methods I am filtering the query on ContactName to
retrieve contact Zeeshan. To retrieve the contact’s Billing and shipping
address, I using Include operator twice followed by First operator to retrieve
the first contact as I know the query will only return only one result. To
confirm that I have Billing and shipping addresses load, I am printing the
contact’s address on the console window. Figure below shows the screen for
contact information on the console window.
When Include is used to eagerly load related enteritis on derived entities, you
must use OfType operator to reach to derive entity before you can apply
include operator otherwise EF will try to look for a navigation property on
base entity which would not exist and cause runtime errors.
When Include is used with an entity that participate in an existing query such
as join or nested from clause, EF will lose the include operation and the
related entities would not be eagerly loaded. To ensure Include works
correctly, ensure that Include is the last operation performed on the query.
Since a customer has many addresses, EF will flatten the query to retrieve
customer and its address in one single query. To identity the problem of
redundancy, I have captured the sql statement that was executed for the above
linq query.
SELECT ..
FROM ( SELECT
..,
CASE WHEN ([Extent2].[AddressId] IS NULL) THEN CAST(NULL AS int) ELSE 1
END AS [C2],
CASE WHEN ([Extent2].[AddressId] IS NULL) THEN CAST(NULL AS int) ELSE 1
END AS [C3]
FROM (SELECT TOP (1)
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[ContactName] AS [ContactName],
1 AS [C1]
FROM [onetomany].[Customer] AS [Extent1]
WHERE N'Zeeshan' = [Extent1].[ContactName] ) AS [Limit1]
LEFT OUTER JOIN [onetomany].[Address] AS [Extent2] ON
[Limit1].[CustomerId] = [Extent2].[CustomerId]
) AS [Project2]
ORDER BY [Project2].[CustomerId] ASC, [Project2].[C3] ASC
Screen shot below shows the above query executed on the sql server.
Although there is only one contact Zeeshan, from the above result you can see
that Zeeshan is repeated twice because there are two addresses which results
in duplication of data. You can imagine duplication of data could increase
rapidly if we start to introduce more includes that are many side of the
relationship. So beware when using includes that sometimes lazy loading a
collection may outperform eager loading a collection that could potentially
have lot of redundant data.
If you are performing any grouping operation in a linq query, you must use
Include as the last operation. For instance the code below shows an incorrect
and correct usage of grouping.
var db = new NorthwindEFEntities();
//incorrect query Include is lost
//var prods = from p in db.Products.Include("Category")
// group p by p.Category.CategoryID into g
// select g.FirstOrDefault(p1 => p1.UnitPrice ==
g.Max(p2 => p2.UnitPrice));
The above commented linq query will not cause Category to be eagerly
loaded with Product entity because when we apply grouping operation,
Include is lost. To ensure that eager loading works as desired make sure that
Include is the last operation performed on the query. Not all query operators
cause this behavior with Include. For instance if you use orderby or where
operators, Include works correctly when you use it early in the query.
When include is used with nested from clause, Include operator is also as
well. Code below shows an example of that.
//nested from clause would also cause the include to get lost
var db = new NorthwindFullEntities();
//var orders = from o in db.Orders.Include("Customer")
// from od in o.Order_Details
// where od.Quantity > 120
// select o;
The commented out query where we are eagerly loading Customer for an
Order, does not work as expected and we do not get the customer for an order.
However the next query applies the Include operator as the last operation
which ensures that customer for the Order is loaded.
Another subtle case to be aware of is the join clause. Join clause meets the
same fate where using Include early in the query results in losing the Include
operation. Code below shows an example of that.
var db = new NorthwindFullEntities();
var ods = from od in db.Order_Details
join o in db.Orders on od.OrderID equals o.OrderID
where o.ShipCity == "Caracas"
select od;
ods = ods.Include("Products");
foreach (var od in ods)
{
Console.WriteLine("Product:{0}
Quantity:{1}",od.Products.ProductName,od.Quantity);
}
Code above shows the correct usage where I am loading product information
for every order detail item. As suggested the correct usage is to ensure that
Include is the last operation on the query.
Console.WriteLine("Category:{0} TotalProducts:{1}",
beverage.CategoryName,beverage.Products.Count());
On the above code, I am retrieving the Category and its related products.
Since I am only interested in related products where supplierid equal to one, I
am apply the where to filter the Products returned for a given category. Then
using the Select operator, I retrieve the first category from the anonymous
types. Similarly to retrieve the Products I use SelectMany operator to fetch
the product collection from the anonymous type. To build the complete graph
in memory I attach the products retrieved from the anonymous type to the
product collection of the category. This is one of the ways you can return a
partial collection for products for a given category.
Listing 1-1
var db = new LazyLoadingEntities();
var alfki = db.Customers.First(c => c.CustomerID == "ALFKI");
if (!alfki.Orders.IsLoaded)
{
alfki.Orders.Load();
}
In Listing 1-1, I am also loading Orders with ship city of London. Order
entity exposes a navigation relationship Customer which by default is not
loaded. I am loading the customer only if the customer has not been loaded
before. Checking for isloaded is very crucial in this case because there are 33
orders returned from the query. However those 33 orders only belong to 5
unique customers. Therefore we really do not want to make 33 database calls.
By checking the IsLoaded property we only make 5 database calls that equal
the unique customers for the 33 orders retrieved from the query.
In the above example, I am clearing all the orders for ALFKI customer. To
discard changes and reload again from database, I use Load again. However,
the count value for orders still remains 0. The reason is, if source entity
customer, is being tracked, calling Load on Orders collection uses
MergeOption of AppendOnly. If you have made changes to collection on the
client side such as changing a property on an order, adding new orders or
removing an order, those changes will be preserved and will not get
overwritten by Load Call. To make sure that we take changes from the
database and overwrite the changes we have made on the client side, we must
call Load with OverWriteChanges. Following code use Load with
MergeOption set to OverWriteChanges.
Listing 1-2
//we need to reload our orders and discard our client changes.
ALFKI.Orders.Load(MergeOption.OverwriteChanges);
If you makes changes to properties on the Order and call Load with
AppendOnly option, your changes will stay intact. In listing 1-3 I am calling
Load with AppendOnly option and therefore property changes on my Order
entity is not lost.
Listsing 1-3
var ALFKIorder = ALFKI.Orders.Single(o => o.OrderID == 10643);
ALFKIorder.ShipCity = "London";
Console.WriteLine("existing order's city changed to London");
ALFKI.Orders.Load(MergeOption.AppendOnly);
//ship city still remains as London
Console.WriteLine("After load with appendly only existing
order city " + ALFKIorder.ShipCity);
In listing 1-4, I am grabbing order id 10643 for alfki customer and assigning it
to ANTARA customer. This results in ALFKI customer’s orders collection to
reduce by 1 and ANTARA’s order collection to increase by 1. When I call
Load on ALFKI customer to reload its order collection using
OverWriteChanges it not only fixes the orders for ALFKI but also removes
the order that we assigned earlier to ANTARA customer since the order
10643 once again belongs to ALFKI as shown in the code below.
Listing 1-4
var ALFKIorder = ALFKI.Orders.Single(o => o.OrderID == 10643);
If you have a scenario where you have loaded part of the orders for a
customer and later you decide that you need to get all the order for customer,
you can call Load with AppendOnly option which will append all the orders
that were previously not present in the collection. It will not overwrite any
existing order in the collection. In Listing 1-5, I am retrieving part of the
order by attaching orders with ship freight less than and equal to 20. Later I
am loading rest of the collection from the database by calling Load.
Listing 1-5
//loading only pat of the orders.
ANATR.Orders.Attach(ANATR.Orders.CreateSourceQuery().Where(o
=> o.Freight <= 20));
Console.WriteLine("Antarr orders partly loaded " +
ANATR.Orders.Count());
//calling load will load the entire graph.
ANATR.Orders.Load(MergeOption.OverwriteChanges);
Console.WriteLine("ANATR orders after load with overwrite "
+ ANATR.Orders.Count());
MergeOptions has another option that I have not covered which is No
Tracking. When you use No Tracking, you’re hinting that entity or collection
you want to load should not be tracked. You can only use No Tracking option
in Load when the source entity is also loaded with No Tracking option.
Listing 1-6 demonstrates using Load with No Tracking.
Listing 1-6
var ANTON = db.Customers.First(c => c.CustomerID == "ANTON");
//code crashes because you cant load related entity with no
tracking
ANTON.Orders.Load(MergeOption.NoTracking);
In Listing 1-6, when I load orders for anton with NoTracking option, I get an
exception because the source query customer was loaded with tracking option
an therefore Loading orders with NoTracking is not allowed. Entity
framework will only allow navigation relations to be loaded with No Tracking
when source entity is also loaded with No Tracking option. When I change
the query for AROUT customer to use NoTracking option and call Load, I get
no errors. The reason I don’t get exception is because by default Load uses
the same option that was used to retrieve the source entity customer. Since I
fetched customer using no Tracking option, Load uses NoTracking option to
retrieve orders as well. You also have the ability to call Load with explicit
Merge option of NoTracking which is same as calling Load with no options.
If you have loaded an entity collection such as orders with no tracking, calling
Load again will raise an exception stating that collection loaded with
NoTracking cannot be reloaded. It is defined as one of the constraints in
entity framework that you cannot reload an entity collection that was initially
loaded with No Tracking option.
There are certain states of entity when calling load is not allowed such as
when source entity is in Added, Deleted or Detached. Calling Load in these
states causes invalid operation exception. Code in listing 1-7 illustrates some
of these issues.
Listing 1-7
db = new LazyLoadingEntities();
var BERGS = db.Customers.First(c => c.CustomerID == "BERGS");
db.DeleteObject(BERGS);
//discuss the error because cant call load when sourc entity
is in deleted status.
//BERGS.Orders.Load();
In listing 1-7, I am adding a new customer and then calling Load on its order
collection. This operation results in an exception because customer is in
added state and calling load is not permitted. Similarly I am marking BERGS
customer for deletion and then calling Load on its orders collection. This also
results in an exception because load is also not permitted on source entities
marked for deletion. Also if you detach source entity from the object context
such as customer in our case, calling Load will raise an exception.
When Load is called on entity reference, it does not make use of the entity
key exposed on the entity reference to load related entity. When order entity
is loaded, order.CustomerReference.EntityKey contains the CustomerId for
the order. However when Load call is issued against
order.CustomerReference, EF does not use that customerid to load the
customer. Instead to get the customerid for the order, the query for the
customer is joined against order table to retrieve the customerid from the
order’s table.
The above code uses MergeOption.NoTracking to retrieve the order from the
database. The side effect of notracking is there won’t be any customerid for
the order entity which is confirmed by printing this information on the
console. Then to load the customer for the order, I am calling Load. Notice
that despite that there was no customerid for the order, EF managed to load
the customer reference because it queried the order table to get the customerid
to load instead of reading the client side value from
order.CustomerReference.EntityKey. The sql below contains the profile
capture for the above load query.
exec sp_executesql N'SELECT
[Extent2].[Address] AS [Address],
[Extent2].[City] AS [City],
[Extent2].[CompanyName] AS [CompanyName],
[Extent2].[ContactName] AS [ContactName],
[Extent2].[ContactTitle] AS [ContactTitle],
[Extent2].[Country] AS [Country],
[Extent2].[CustomerID] AS [CustomerID],
[Extent2].[Fax] AS [Fax],
[Extent2].[Phone] AS [Phone],
[Extent2].[PostalCode] AS [PostalCode],
[Extent2].[Region] AS [Region]
FROM [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[CustomerID] =
[Extent2].[CustomerID]
WHERE ([Extent1].[CustomerID] IS NOT NULL) AND ([Extent1].[OrderID] =
@EntityKeyValue1)',N'@EntityKeyValue1 int',@EntityKeyValue1=10248
3.3 CreateSourceQuery
To get the total orders for ALFKI customer, I am accessing the Orders query
with CreateSourceQuery and applying count operation on it. To confirm the
count operation is applied on the database I have captured the sql statement
send to the database.
SELECT
COUNT(cast(1 as bit)) AS [A1]
FROM [dbo].[Orders] AS [Extent1]
WHERE [Extent1].[CustomerID] = @EntityKeyValue1
@EntityKeyValue1=N'ALFKI'
Solution: There are various ways to approach the above problem. First
approach is to do eager loading by using Include operator. When we load
customers, we can use Include operator to eagerly load all their phone
numbers. This approach would ensure that object graph is maintained when
entities are returned from Object Services. Additionally it would require only
one single database trip where both customer and their phones are retrieved in
a single database call. However the problem with Include approach is, Phone
entity is the Many side of the association which means that when EF brings
Customers with Many Phones in a single query, the data returned to the
application would have customer information repeated. Although with Meta
data on the client, EF will remove the redundancy but bringing redundant data
on the network and the cost of processing in removing the redundant data may
make this option not the best approach.
The last approach is to load Customers and Phones in a separate query which
would lead to two separate but clean queries that simply return data from a
Customer and Phone table. When entity framework brings Phone, it will also
bring back the relationships for anything that is a reference and store that as
stubs in ObjectStateManager. In our case those stubs would have relationship
info for Customer and Phone but Customer end of the relationship would not
point to any customer. When we iterate the Phones entity either using for each
or ToList operator, EF will replace the stub with a full Customer entity
because it will see that the stub points to a Customer that is actually present in
the state manager and will fix the relationship between Customer and Phone
as if they were retrieved together. Code below shows an example of using
relationship span.
var db = new RsCustomerEntities();
var customer1 = new Customer
{
Name = "Zeeshan",
Phones =
{
new Phone{Number = "123-455-9876"},
new Phone{Number = "123-455-9877"}
}
};
The code above creates two customer and assigns two phones to each
customer. To retrieve all customers and their phone numbers we can
separately query for customers and their phones. When we call toList on
Phones EF will automatically fix the relationship between customer and
Phones by escalating the customer stub to a full customer entity. Code below
shows the relationship span being applied.
var customers = db.Customers.ToList();
var phones = db.Phones.ToList();
foreach (var customer in customers)
{
Console.WriteLine("Customer:{0} Total
Phones:{1}",customer.Name,customer.Phones.Count());
}
To confirm the phones are attached to the customer, I am looping through the
customer and printing the Total phones for a customer. Figure below shows
the result on the console window.
Phone.CustomerReference.CustomerId
The above code allows us to return customerid even though the Customer
instance is null because there is a stub entry created for the relationship
between phone and customer entity.
So when does stub entries get upgraded to real entities. It could happen either
when you are retrieve entities using query or attach an entity to the
ObjectContext or load an entity using Load operator. All those options trigger
EF to check in the state manager for a stub and if found replace it with a full
entity causing entire object graph to be fixed. So in our case when we loaded
customers, EF found stubs for customer entity created by loading of Phone
entities and replaced it with full customer. Another important fact to take
away is the order in which the entities are loaded is not important. For
instance in the above case we loaded phones followed by customer entity. We
could have loaded customers first and then Phones. When phones are loaded
with their relationship info, it would check the state manager to for the
customer end of the relationship info. If customer entity is found, the graph
would be hooked and relationship will be fixed.
Another important use case of Relationship span comes when we try to delete
a phone entity. Since Phone entity brings along the relationship info, EF will
ensure that when a phone is deleted, the relationship between Customer and
Phone is also deleted. When the relationship info entry is deleted from the
state manager, Customer entity will reflect the correct phones in its collection
as well. Currently this is not possible with linq to sql because it does not make
relationship a first class citizen as EF does. Code below shows an example
where marking the phones for deletion causes Phone count for customer to
reach 0 indicating that customer no longer has any phones.
var db = new RsCustomerEntities();
var phones = db.Phones.ToList();
var customers = db.Customers.ToList();
foreach (var phone in db.Phones)
{
db.DeleteObject(phone);
}
foreach (var customer in customers)
{
Console.WriteLine("Customer:{0} Total
Phones:{1}",customer.Name,customer.Phones.Count());
}
Code above confirms that as soon as we marked all phones for deletion, EF
removed the association between the customer and the phone causing the
Customer’s phone collection to reach zero.
On the code above, I am retrieving phone based on a query. The query returns
a single phone for each customer and when we iterate through the customer
and print the total phones count for each customer, the result is 1. The result is
certainly not true because both customers have two phones. To differentiate if
the customer’s phone collection was loaded as result of fixing the object
graph using relationship span or explicitly loading of the each customer’s
phone we can check IsLoaded property on the Phone collection. If the
customer’s Phone collection was not loaded explicitly then IsLoaded property
would return false. If that is the case we can fetch all phones for the customer
by calling Load on the Phone collection. Code below uses IsLoaded property
to check if the phone collection was populated using relationship span, then
explicitly call load to ensure that we have all the phones for a customer.
var db = new RsCustomerEntities();
var customers = db.Customers.ToList();
var phones = db.Phones.Where("it.Number like '%455%'").ToList();
foreach (var customer in customers)
{
if (!customer.Phones.IsLoaded)
{
customer.Phones.Load();
}
Console.WriteLine("Customer:{0} Total Phones:{1}",
customer.Name, customer.Phones.Count());
}
After calling Load, we can see in the console window that each customer has
the correct phones filled.
On the above code, we are retrieving the top 1 address from the
database using NoTracking option. This tells entity framework that
since address entity is not going to be tracked there is no reason to
bring additional relationship information with the query. Code
below shows the sql capture for the above NoTracking query which
only queries against address table to retrieve the first address.
SELECT TOP (1)
[c].[AddressId] AS [AddressId],
[c].[FullAddress] AS [FullAddress],
[c].[AddressType] AS [AddressType]
FROM [rs].[Address] AS [c]
4. Views
4.1 QueryView
Problem: You have customer and account data in two different tables called
Customer and CustomerAccount as shown below
Customer and CustomerAccount are joined by 1 to 1 relationship. You want
to expose both tables as a single entity in the EDM. In addition there are
additional columns on Customer table that have grown over the period of time
that you are no longer interested in. You do not want these columns to surface
on the entity data model. On your entity data model, you want to have an
additional computed column that says if the Customer has an account or not.
You want to know how to create an entity data model that validates against
the given schema above.
Solution: You can use a queryview that can query the ssdl definition to join
both the tables and only project columns which you have defined on the
entity. To populate a computed column HasAccount, you can do a left join
and check to see if CustomerAccount is not null. If CustomerAccount is null
means HasAccount should be set to False.
The above code queries for two customers, one which has a customer account and
other one do not. Screen shot below confirms the result of the query for HANAR
which does not have an account.
Note: Since queryviews are readonly, there is no support for insert, update and
delete of an entity by entity framework. You are required to create stored
procedures for each entity and associations need to be saved to the database. The
mapping of the stored procedures also has to be done manually by editing edmx
file because in the current version of the designer does not support mapping stored
procedures to entities that use queryviews.
Problem: You have customers and Orders defined on the database. Recently
the application was retrieving all the orders for a given customer but new
business rules states that application should only retrieve orders for customers
that were placed after 1998. Any orders place before 1998 year should not be
fetched. In addition any orders that are marked as deleted should not be
retrieved. You are told to implement this logic across the entire application.
Solution: Although we could write a query that would filter the orders to the
requirement stated above. However that would require fixing the orders query
at numerous places and may lead to bugs. Instead we can modify the Orders
ObjectQuery exposed on the Objectcontext so that anytime Orders
entitycollection is accessed; the filter is already applied on it. The best way to
do is to remove the entityset mapping which is mapped to a table. Instead
Orders EntitySet should be mapped to a custom query that filters the orders to
remove deleted orders and orders place before 1998. If there are entities
associated with Orders, they will also have to rewritten to use QueryView
even though their implementation does not change. This is a requirement
imposed by the entity framework because it cannot confirm the graph
consistency and provide validation on part of the model that uses default
mapping with the rest which uses QueryView.
Discussion: The diagram below shows how customer and orders are related
to each other.
To apply filter on Orders we need to define a QueryView which will filter the
orders. Since Orders are related to customer, we also need to define
QueryView for Customers and the association between customer and orders.
When we import the tables shown above using Update Model Wizard into
EDM, we get default mappings of entities to tables. Screenshot below show
how the entity data model looks like.
Since we are going to be using custom mapping we need to remove the Table
mapping for both Customer and Order from mapping window. Since
QueryView is not supported by the designer, we will modify the EntitySet
mapping for Customers Orders and the association between customer and
Orders. Example below shows the mapping for Customers entityset.
<EntitySetMapping Name="Customers">
<QueryView>
select value
QueryWithFilterModel.Customer(c.CustomerID,c.ContactName)
from
QueryWithFilterModelStoreContainer.Customers as c
</QueryView>
</EntitySetMapping>
Since customers are related to Orders, we are forced to provide mapping for
customers. The esql query above retrieves customers from the store model
and sets the value for Customer entity. QueryWithFilterModel is the name of
the conceptual model namespace and QueryWithFilterModelStoreContainer
is the StorageEntityContainer name.
On the above model, I am querying the store model for only orders that are
not deleted and which have an orderdate greater than the date specified in the
query. Notice to cast my string as date, I am using cast operator available on
esql and specifying the type to be Edm.DateTime.
createref(QueryViewWithFilter.Customers,row(o.CustomerID)),
createref(QueryViewWithFilter.Orders,row(o.OrderID))
)
from
QueryWithFilterModelStoreContainer.Orders as o
</QueryView>
</AssociationSetMapping>
To confirm that on fetching orders for a customer, we do not retrieve all the
orders, we can write a query that returns all the orders for a customer. In the
example below I am using a regular sql query to get the count of the orders
placed by SAVEA customer and then using linq, I am querying my
conceptual model which applies my QueryView to only retrieve orders that
meet our query criteria. Screen shot below shows our result.
var db = new QueryViewWithFilter.QueryViewWithFilter();
var cmd = db.CreateStoreCommand("SELECT COUNT(*)
FROM ORDERS WHERE CUSTOMERID = 'SAVEA'");
cmd.Connection.Open();
var totalorders =
Convert.ToInt32(cmd.ExecuteScalar());
cmd.Connection.Close();
In addition to the primary key field from Clubs and Members, Membership
table also contains an additional column MemberShipType that defines what
kind of membership the member holds. Membership type could be of two
types; Gold and Platinum. You want to expose the database relationship as
Many to Many association between Club and Members. There should be two
types of associations between Clubs and Members. First association should
expose a navigation property GoldMembers entity collection and second
association should expose a navigation property PlatiniumMembers.
GoldMembers should only retrieve GoldMembers from the membership table
and PlatiniumMembers should only retrieve Members who have platinum
membership with the club. In addition you want to be able to insert and
update and delete different types of members.
Solution: To map our relational model to entity model shown above, we will
use the import wizard to get head start which will help us generate SSDL,
CSDL and MSL. Screen shot below shows the model as it looks like when
we use the import wizard to import Clubs, Membership and Members table.
We will first delete Membership entity because we want to have a direct
Many to Many association between club and Member. To create association
returning GoldMembers, right click on Club entity and select Association.
On the screen shot below, we have selected Members with a multiplicity of
Many and Clubs with multiplicity of Many. We have given the association
Name Gold and use GoldMembers navigation property to access
GoldMembers for the club.
Next we need to create an association for Platinum members for the club.
We use the similar step to create PlatiniumMembers association between
Club and Members entity. On the screen shot below we have set Many
multiplicity for Club and Members and use PlatiniumMembers navigation
property to access Platinum members for the club.
To map our two associations, we will use QueryView to extract appropriate
data from Membership link table. Using QueryView requires that all entities
to also use queryview and cannot use the default mapping from the entity
framework. Therefore we also need to remove the Club and Membership
association created by the import wizard by selecting the entity, choose the
mapping window and delete the mapping.
The QueryView for member queries the store model and map the results to
member entity. After defining the QueryView, we do not get any support for
insert, update and delete for member entity. Therefore we need to create
stored procedures and map the stored procedure’s parameter to properties on
the entity. The msl below shows mapping required for member entity.
MSL
<EntitySetMapping Name="Members">
<QueryView>
select value
QueryViewWithManyToMany.Member(m.MemberId,m.Name)
from
QueryViewWithManyToManyStoreContainer.Members as m
</QueryView>
<EntityTypeMapping
TypeName="QueryViewWithManyToMany.Member">
<ModificationFunctionMapping>
<InsertFunction
FunctionName="QueryViewWithManyToMany.Store.InsertMember">
<ScalarProperty Name="Name"
ParameterName="Name" />
<ResultBinding
Name="MemberId" ColumnName="MemberId" />
</InsertFunction>
<UpdateFunction
FunctionName="QueryViewWithManyToMany.Store.UpdateMember">
<ScalarProperty Name="Name"
ParameterName="Name" Version="Current"/>
<ScalarProperty
Name="MemberId" ParameterName="MemberId" Version="Current"/>
</UpdateFunction>
<DeleteFunction
FunctionName="QueryViewWithManyToMany.Store.DeleteMember">
<ScalarProperty
Name="MemberId" ParameterName="MemberId" />
</DeleteFunction>
</ModificationFunctionMapping>
</EntityTypeMapping>
</EntitySetMapping>
To define the stored procedure called inside MSL, we can either import the
stored procedure from database using the designer or declare the store
procedure right inside the command text property of function declaration
inside the ssdl. SSDL shown below uses the second option by defining the
code for the stored procedure inside the CommandText property. This is for
demonstration purpose or if u do not have permissions to create database
objects on the database. If you have the appropriate permissions, it would be
preferable to create stored procedure, this way you would get compilation for
the stored procedure and benefit the compile time check offered by the
database engine. SSDL below shows function declaration need to insert,
update and delete member entity.
<Function Name="InsertMember" IsComposable="false"
Schema="dbo">
<CommandText>
insert into QueryView.Members(Name) values
(@Name)
select SCOPE_IDENTITY() as MemberId
</CommandText>
<Parameter Name="Name" Type="varchar" Mode="In"
/>
</Function>
<Function Name="UpdateMember" IsComposable="false"
Schema="dbo">
<CommandText>
update QueryView.Members set Name = @Name
</CommandText>
<Parameter Name="Name" Type="varchar" Mode="In"
/>
<Parameter Name="MemberId" Type="int" Mode="In"
/>
</Function>
<Function Name="DeleteMember" IsComposable="false"
Schema="dbo">
<CommandText>
delete QueryView.Members where MemberId =
@MemberId
</CommandText>
<Parameter Name="MemberId" Type="int" Mode="In"
/>
</Function>
MSL
<EntitySetMapping Name="Clubs">
<QueryView>
select value
QueryViewWithManyToMany.Club(c.ClubId,c.ClubName)
from
QueryViewWithManyToManyStoreContainer.Clubs as c
</QueryView>
<EntityTypeMapping
TypeName="QueryViewWithManyToMany.Club">
<ModificationFunctionMapping>
<InsertFunction
FunctionName="QueryViewWithManyToMany.Store.InsertClub">
<ScalarProperty
Name="ClubName" ParameterName="ClubName" />
<ResultBinding Name="ClubId"
ColumnName="ClubId" />
</InsertFunction>
<UpdateFunction
FunctionName="QueryViewWithManyToMany.Store.UpdateClub">
<ScalarProperty
Name="ClubName" ParameterName="ClubName" Version="Current"/>
<ScalarProperty Name="ClubId"
ParameterName="ClubId" Version="Current"/>
</UpdateFunction>
<DeleteFunction
FunctionName="QueryViewWithManyToMany.Store.DeleteClub">
<ScalarProperty Name="ClubId"
ParameterName="ClubId" />
</DeleteFunction>
</ModificationFunctionMapping>
</EntityTypeMapping>
</EntitySetMapping>
SSDL
<Function Name="InsertClub" IsComposable="false"
Schema="dbo">
<CommandText>
insert into QueryView.Clubs(ClubName) values
(@ClubName)
select SCOPE_IDENTITY() as ClubId
</CommandText>
<Parameter Name="ClubName" Type="varchar"
Mode="In" />
</Function>
<Function Name="UpdateClub" IsComposable="false"
Schema="dbo">
<CommandText>
update QueryView.Clubs set ClubName =
@ClubName
</CommandText>
<Parameter Name="ClubName" Type="varchar"
Mode="In" />
<Parameter Name="ClubId" Type="int" Mode="In" />
</Function>
<Function Name="DeleteClub" IsComposable="false"
Schema="dbo">
<CommandText>
delete QueryView.Clubs where ClubId =
@ClubId
</CommandText>
<Parameter Name="ClubId" Type="int" Mode="In" />
</Function>
createref(QueryViewMM.Clubs,row(m.ClubId)),
createref(QueryViewMM.Members,row(m.MemberId))
)
from
QueryViewWithManyToManyStoreContainer.Membership as m
where m.MemberShipType = 'G'
</QueryView>
<ModificationFunctionMapping>
<InsertFunction
FunctionName="QueryViewWithManyToMany.Store.InsertGoldMembership
">
<EndProperty Name="Clubs">
<ScalarProperty Name="ClubId"
ParameterName="ClubId"/>
</EndProperty>
<EndProperty Name="Members">
<ScalarProperty
Name="MemberId" ParameterName="MemberId" />
</EndProperty>
</InsertFunction>
<DeleteFunction
FunctionName="QueryViewWithManyToMany.Store.DeleteMembership">
<EndProperty Name="Clubs">
<ScalarProperty Name="ClubId"
ParameterName="ClubId"/>
</EndProperty>
<EndProperty Name="Members">
<ScalarProperty
Name="MemberId" ParameterName="MemberId" />
</EndProperty>
</DeleteFunction>
</ModificationFunctionMapping>
</AssociationSetMapping>
4.2 DefiningQuery
Introduction
DefingQuery is a query view that is defined on the store model. When a view
is imported from the database using Entity model wizard, EF creates a
definingQuery that does a select on the view. If you import a table that does
not have a primary key, EF will create a DefiningQuery that does a select on
the table. In additional it will infer that all columns in the table participate in
primary key and will mark all columns as keys on the store model. Similarly
on the conceptual side, it will also make all properties as being entity keys.
This behavior is very confusing because if you do not know that EF
framework has mapped your table as a DefiningQuery, you will be clueless as
to why your inserts, updates and deletes are failing on the conceptual entity.
This brings us to a discussion of how EF handles crud operations on entities
that are mapped using DefiningQuery. Since DefiningQuery is only a read-
only view of data written in store specific syntax, EF does not have any
understanding of how the entity is stored in the database. To save an entity
that is mapped using DefiningQuery, we have to declare stored procedures on
the store model and then map the stored procedures to insert, update and
delete operations on the conceptual entity.
DefiningQueries allows you to use native sql syntax to create any complex
projection and expose the projection as a view that an entity on the conceptual
model can be mapped to. If there are modeling scenarios that you cannot
accomplish using Ef because of the way the data is stored in the database, you
can transform the data using DefiningQueries and project it in a way that is
friendly with EF modeling scenarios. DefiningQueries does not support
parameters so a sql written inside of DefingQuery section cannot contain
parameters that you specify a value for at runtime.
Entity framework also allows you to define a complex view using QueryView
that uses esql. Unlike DefiningQueries, QuerViews are declared inside the
mapping section and queries the store model to fetch its data. It is a preferred
approach of defining complex queries. However if you cannot represent a
projection using QueryView because it does not support all the esql operators,
you should use DefiningQuery as the last resort. Some common use of
DefiningQueries would be to use constructs that cannot be mapped directly
either using esql or Linq. For instance to create recursive queries sql server
provides Common table Expression which does not have any translation to
either esql or Linq. To use recursive queries, we can use Common Table
Expression inside of DefiningQuery and rest of conceptual model can simply
use the view without knowing the underlying details of the store model. In
version 1 release of EF, there is no support for spatial data type. To overcome
these limitations, we can create a DefiningQuery which brings spatial data
type as image and then inside of the partial class we can transform the data
into a Geographical data type which will allow us to perform domain specific
activities like plotting a point on map. Basically DefiningQuery provides
unlimited capabilities to exploit the data agnostic features which EF cannot
leverage.
Entity framework does not support mapping tables that reside on different
database. With DefiningQuery, you can create a view that joins tables across
multiple databases and returns a view that entity framework can consume
without knowing if the data is coming from multiple databases.
Problem: Figure below shows the database diagram for a Gun and its
promoters.
Solution: If we look at the above database model, we will realize that we are
mapping PromoterId as a foreign key to multiple tables. But for a single
GunShow it is either mapped to GunClub or ShootingRange not both. Entity
Framework does not allow a single column to be mapped to multiple
associations because that could corrupt the model. Although in our case it is
genuine mapping because only 1 mapping is valid at a given point. To get
around this limitation we need to create a view that exposes PromoterId
column on GunShow as two columns ShootingRangeId and ClubId. If the
GunShow’s promoter is GunClub , then ClubId will have a value but
ShootingRangeId would be null. Similarly if GunShow’s promoter is
ShootingRange, then ShootingRangeId would have a value and ClubId would
be null. After creating the view, import the GunShow view, GunClub and
ShootingRange table. Create an association between GunShow and GunClub
where a GunShow can have a single GunClub as a promoter. Similarly create
an association between GunShow and ShootingRange where a GunShow can
have a single ShootingRange as a promoter. Ensure that mapping between
GunShow and GunClub uses ClubId column on GunShow view and mapping
for ShootingRange and GunShow uses ShootingRangeid column on
GunShow view. Since views are not updatable, we have to create stored
procedures for GunShows, ShootingRange and GunClubs to insert record into
the database. So stored procedures needs to be declared inside store model
and then mapped to entities on the msl section.
The view above consists of two unions. The first union is a join between
GunShow and ShootingRange where the PromoterType is SR. Since the join
is against shootingRange table, I am setting null for ClubId column. Similarly
the second join is against GunShow and GunClub where PromoterType is
GC. Now that we have exposed two columns as foreign key to both GunClub
and ShootingRange, we do not need to expose promotertype from our view.
When we import the view into our model, EF creates a DifiningQuery which
basically selects from the view created on the database.
Rest of the steps below will outline the process of importing the view,
GunClub and ShootingRange into our entity data model.
<EntityType Name="vwGunShow">
<Key>
<PropertyRef Name="ShowId" />
</Key>
<Property Name="ShowId" Type="int" Nullable="false" />
<Property Name="ShowName" Type="varchar" Nullable="false"
MaxLength="100" />
<Property Name="VendorsRegistered" Type="int" Nullable="false" />
<Property Name="ShootingRangeId" Type="int" />
<Property Name="ClubId" Type="int" />
</EntityType>
Also make sure that GunShow entity on the conceptual model also has
ShowId as the entity key. Remove any extra columns from entity key.
3. Since ClubId and ShootingRangeId column will be used in association,
it cannot be mapped to properties. So remove ShootingRangeId and
ClubId property from GunShow entity.
4. Create association between GunShow and ShootingRange where
GunShow has a multiplicity of Many and ShootingRange has a
multiplicity of 1. Figure below shows the association dialog with
correct mappings.
5. Create association between GunClubs and GunShow where GunShow
has a multiplicity of Many and GunClub has a multiplicity of 1. Figure
below shows the association dialog to configure GunClub and
GunShow.
After completing the association, the completed entity model should
look like below.
Next step is to configure the mapping for the association we created
between GunShow, ShootingRange and GunClub.
Solution: Create CustomerSales entity on the designer that matches the shape
and data type result returned from the stored procedure. Import the stored
procedure on the store model and using the function import map the stored
procedure result to CustomerSales entity. At this point when we try to
validate the model, we will get build errors that CustomerSales entity is not
mapped to a table or view. In reality this is a correct case because our stored
procedure returns arbitrary results that cannot be mapped to any table or view.
To get around this validation error, we need to create a dummy
DefiningQuery that defines a select that has the same shape as CustomerSales
entity but does not return any data. Map the definingQuery to CustomerSales
entity using the mapping window.
Discussion: Figure below shows the results when we execute our stored
procedure GetCustSales.
1. Import the above stored procedure on the store model using Entity Data
Wizard.
2. Create CustomerSales entity that matches the datatype and columns
returned from the stored procedure. Make sure CustomerId is set as the
entitykey. Figure below shows CustomerSale entity on the model.
3. Right click the stored procedure imported in the store model from
Model Browser window and select create function import. On Add
Function Import dialog, set the return type to be CustomerSale entity.
Figure below shows Add Function Import.
<EntitySet Name="GetCusSales"
EntityType="DDQModel.Store.GetCusSale">
<DefiningQuery>
select null CustomerID, cast(0 as
decimal) TotalSales
where 1 = 2
</DefiningQuery>
</EntitySet>
Notice the above DefiningQuery simply returns CustomerId and
TotalSales column on a condition that would never be true. This is
because, it is a dummy view to satisfy EF conditions that an entity
defined on the model must be mapped to some table structure. In future
releases of EF, a stored procedure result could be mapped to a complex
type which would alleviate all these hacks we are applying to get
around EF limitations. Our GetCusSales entityset is mapped to
EntityType GetCusSale which we need to create. Code below created
GetCusSale entity with two columns CustomerId and TotalSales as
shown below.
<EntityType Name="GetCusSale">
<Key>
<PropertyRef Name="CustomerID" />
</Key>
<Property Name="CustomerID" Type="varchar"
Nullable="false" />
<Property Name="TotalSales" Type="decimal"
Nullable="false" />
</EntityType>
5. Code below calls GetCustomerSales that calls GetCustSales stored
procedure and returns a collection of CustomerSale entity. I then loop
through each entity and print its output to the console window.
var db = new DDQEntities();
foreach (var cus in db.GetCusSales())
{
Console.WriteLine("Customer {0} Sale
{1}",cus.CustomerID,cus.TotalSales);
}
Figure below shows the output printed on the console window.
4.2.4 Creating Read-only Calculated Properties using Defining
Query
Problem: Figure below shows the database diagram for Customer and Orders
table defined on the database.
The diagram above contains Customer and Order table. Customer has
FirstName and Last Name and Orders table contains the quantity customer
has ordered and the Total for the order. You want to expose the customer
table as an entity on entity data model. In addition you want to expose 3 read-
only properties. First read-only property Name would concatenate FirstName
and LastName. Customer entity should also expose calculated property called
TotalOrders which returns totalOrder the customer has placed so far,
TotalPurchases property that returns the total dollar amount purchase the
customer has made so far.
Solution: Import Customer table into EDM using Import wizard. Add three
new fields Name,TotalOrders and Total Purchases to Customer entity defined
on the conceptual model. Since the new fields added are not defined on the
Customer table structure, we need to create Defining Query that joins
Customer table to Orders table and returns the calculated fields for
TotalOrders and TotalPurchases. To create Defining Query, modify the ssdl
section of edmx in xml and change the Customers entity set to use
DefiningQuery instead of mapping to Customer table. Modify the Customer
Entity on the ssdl to include the three new columns defined on the conceptual
model. To map the new fields on the store model to the conceptual, open the
edmx in the designer and update the mapping for Customer entity using the
mapping window.
<EntityType Name="Customer">
<Key>
<PropertyRef Name="CustomerId" />
</Key>
<Property Name="CustomerId" Type="int" Nullable="false"
StoreGeneratedPattern="Identity" />
<Property Name="FirstName" Type="varchar" Nullable="false"
MaxLength="50" />
<Property Name="LastName" Type="varchar" Nullable="false"
MaxLength="50" />
<Property Name="Name" Type="varchar" />
<Property Name="TotalOrders" Type="int" />
<Property Name="TotalPurchases" Type="int" />
</EntityType>
4. To map the three new fields defined on the store and conceptual model,
open the mapping window for Customer entity and map Name property
to Name column, TotalOrders property to TotalOrders column and
TotalPurchases property to TotalPurchases column. Figure below
shows the updated mapping window.
5. Code below loops through the customers’ collection returned from the
ObjectContext and prints all three readonly properties to the console
window.
Problem: Figure below shows the database diagram for the relationship
between Contact and their addresses.
Although in the table relationship, a contact can have many addresses and
each address is either a billing or shipping identified by AddressType column.
On the entity data model, you want to make sure that a Contact cannot have
more than two addresses and there could be only 1 billing and 1 shipping
address. To ensure the above scenario, you want to expose two entity
references from Contact, a BillingAddress and ShippingAddress. The end
entity data model should look as follows
Solution: To accomplish the above solution, we have to create a
DefiningQuery and instead of exposing ContactId we need to expose to two
columns BillingContactId and ShippingContactId where BillingContactId
would not be null when AddressType is B and ShippingContactId won’t be
null when AddressType is S. The reason we have to expose two columns is
because entity framework does not allow mapping multiple associations to a
foreign key column as it would invalidate the model and cause data loss. Map
two associations between Contact and Address and map billingAddress
association to BilingContactId column and map the ShippingAddress
association to ShippingContactId column.
9. To test the above model, we can retrieve a Contact, its Billing and
shipping Address. On the code below I am using Include to retrieve
Billing and Shipping Address for contact and printing the result to the
console window.
.Include("BillingAddress").Include("ShippingAddress")
.First(c => c.ContactName ==
"Zeeshan");
Console.WriteLine("Name {0}",contact.ContactName);
Console.WriteLine("Billing
{0}",contact.BillingAddress.FullAddress);
Console.WriteLine("Shipping
{0}",contact.ShippingAddress.FullAddress);
5. Inheritance
Basics of Inheritance
Of all the supported inheritance models, the most simplest and easiest to
implement is Table Per Hierarchy (Single Table Inheritance). To implement
this inheritance, you store all concrete types in one table. In Entity framework
to identity a row as a specific concrete type, you define a discriminator
column which identities which concrete type a specific row gets mapped to.
From a usability point, I have found Single table model to be very easy to get
started. However from the database perspective, the model doesn't seem to
favor a clean approach. The reason is you are storing all different concrete
types in a single table. Some concrete types would need certain columns
where as others won't. To accomplish flexibility at the table level, you have to
mark all columns that are specific to their concrete implementation as allow
nulls. Some database developers may find this approach not a good solution
because it does not make efficient use of disk space. On the other hand table
per hierarchy offers good performance because to find a concrete type, you
don’t have to apply joins to another table which can be costly if the table is
too big. Since all the types are stored in one table, you can apply index on the
discriminator column to allow faster searches based on concrete type you are
looking for. To map this structure as table per hierarchy in entity data model,
you have to define the column which entity framework can use to identity
each type, basically a discriminator column. Next you need to move specific
field for each type from the base class to its own entity.
In Table per Type model, you define a base table that contains fields common
across all types. Then you define a table for each type which contains fields
that are specific to that type. In addition the primary key column defined on
the derived table is also the foreign key for the base table. To map this form
of table structure into table per type entity model, each individual type needs
to inherit from the base type where the base type is mapped to the base table
defined on the database. Each derived type needs to be mapped to its specific
table in the database. Additionally, you have to delete the primary key
property on the derived entity generated by the designer and map the primary
key column on the derived entity to the entity key defined on the base class.
In table Per Concrete Type, each table represents the entire entity. It is not
required that two tables participating in Table Per Type have same number of
columns. The columns that are specific to a table that is not in another table
participating in table per type, would end up as a property on the derived entity.
Rest of the columns would be placed as properties on the base entity. Table Per
Concrete Type is not fully supported on the designer so you start with importing
the model and create your conceptual model but for modeling table per concrete
type, you have to manually edit the xml file. One of the reasons you create table
per concrete type is to portray data coming from multiple tables as being a single
entity retrieving data from a single table. This means that primary key or entity key
on the conceptual model cannot be duplicated. You cannot have primary key of 1
on table1 and primary key of 1 on table2 as well because this would cause entity
framework to throw primary key violation constraint.
5.1.1 Table per Type Walkthrough
Problem: You have created 3 table Media, Video and Articles in the
database. Media table contains common fields for both Articles and Videos.
Fields specific to Video and Articles are stored in their respective table. You
want to map this structure to entity data model using Table per Type
inheritance.
Solution:
Discussion:
Media table in the database consists of MediaId, Title and Description where
MediaId represents the primary key of the table. Video table has VideoId as
the primary key which is also the foreign key for Media table. Video table
contains an additional column ResourcePath which is the location on the
network where the media resides. Articles table has ArticleId as the primary
key which is also the foreign key to Media table. ArticleContent contains the
content of the article. Figure below shows the database diagram for Media.
The next step is to import the database model using import database wizard.
Screen shot below shows the model created by edm when we import the raw
tables.
When we import the tables, entity framework maps one to one relationship
between Media, Article and Video as 1 to 0-1 associations. What we want is
an inheritance hierarchy. So the first step is to delete the association and add
inheritance with Media as the base entity.
To delete the association, select both associations and click delete. To add
inheritance right click Media entity and choose inheritance. On the
inheritance window popup, select Media as the base entity and Video as the
derived entity. Figure below shows the correct entity selected for inheritance
between Media and Video.
To create inheritance for article, same process can be followed. After setting
inheritance for both entities, Articles and Videos, final figure should look like
the one below.
If you try to validate the model, you will get validation errors. To fix the
validation errors remove the VideoId and ArticleId from Video and Article
entity. For video entity change the table mapping where VideoId is mapped to
Mediaid as shown below.
Video Mapping
Since we are only using Media as a base and will not instantiate on its own it
is important that we mark Media entity as Abstract. Therefore we can only
instantiate concrete implementations of Media such as Article and Videos.
Article Mapping
In the above sample, Media base entity is mapped to Medias table in the
database. Notice TypeName uses IsTypeof of Media which means this
mapping applies to any entity that derives from Media entity. Next two
entities Articles and Videos map to their respective table with type video and
Article. The IsTypeOf is also used in derived types for the case if there are
other entities that derive from Articles and Videos. However in our case we
can simply type in the class name for derived types without using IsTypeOf.
<EntitySetMapping Name="Medias">
<EntityTypeMapping
TypeName="IsTypeOf(SimpleTPTInheritance.Media)">
<MappingFragment StoreEntitySet="Medias">
<ScalarProperty Name="MediaId" ColumnName="MediaId" />
<ScalarProperty Name="Title" ColumnName="Title" />
<ScalarProperty Name="Description" ColumnName="Description"
/>
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(SimpleTPTInheritance.Video)">
<MappingFragment StoreEntitySet="Videos">
<ScalarProperty Name="MediaId" ColumnName="VideoId" />
<ScalarProperty Name="ResourcePath" ColumnName="ResourcePath"
/>
</MappingFragment>
</EntityTypeMapping><EntityTypeMapping
TypeName="IsTypeOf(SimpleTPTInheritance.Article)">
<MappingFragment StoreEntitySet="Articles">
<ScalarProperty Name="MediaId" ColumnName="ArticleId" />
<ScalarProperty Name="ArticleContent"
ColumnName="ArticleContent" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
To test the model, we can create an instance of the object context, add article
and video to the media collection and call SaveChanges on the objectContext.
In the example below, I am adding article and video entity to AddToMedias
method generated by the designer. AddToMedias can take any entity derived
from media class. You don’t get separate methods to add Media and Articles.
Similarly to read Article and Video there is so no separate property exposed
on the object to directly access Articles and Videos. Instead ObjectContext
exposes the base class reference Articles and you have to use OfType
operator to only return Articles of a certain derived type. The entities returned
are returned as derived class references which means you do not have to
perform explicit cast to get a derived type instance. In the example below, to
get videos, I am using a where linq operator and filtering to only return Media
of Type Video. Since where operator returns a base class reference, I am
additionally using Cast operator to cast the Media base class reference to
Video derived type to access properties on the Video entity.
var db = new STPTInh();
//inserting article and videos
Article article = new Article
{
Title = "Linq Getting Started",
ArticleContent = "article content"
};
Screen shot below shows the output of the above code on Console window.
Problem: You have an employee table in the database which contains two
kinds of Employee; Hourly Employee and Salaried Employee. To identity
each type of Employee, the table has an additional column Employee Type
which can have two values; HE for Hourly Employee and SE for Salaried
Employee. You want to map this table structure to entity data model using
Table Per Type.
Solution: Using the import wizard will allow you to import the tables into the
entity data model. After the import is completed you will see Employee entity
created on the designer. Since employee will have two derived types, create
two entities Hourly and Salaried Employee that inherits from Employee
entity. Move the properties specific to the derived type to its own entity. Then
map each derived type to the employee table and map properties on the
derived type to columns on the employee table. Since employee base class
does not have any definition to our concrete implementation except for the
fact that it serves as our base class, we need to ensure that employee class
cannot be instantiated. To satisfy the requirements make the base class
abstract. If we do not make the base class abstract, Entity framework
validation will complain that Employee class needs to also contain some
discriminator value that it can map to.
The Employee table contains all the columns required for each type of
Employee. Since both types of Employee are in same table, we need to make
columns specific to each Employee as allow nulls. Employee table also
contains a Type column which is used to differentiate between each type of
Employee. To import the table into entity data model, we will use Update
Model from Database option. Screen shot below show the entity created on
the design surface when we imported Employee table.
The next step is to create two entities, Hourly and Salaried Employee and
move the fields’ specific to each type to its own entity. One of the ways you
can move fields is by cutting the fields from the base class Employee and
pasting it to derived entity. To create an entity on the design surface, right
click and choose Add entity. This will open up Add entity dialog which
would allow us to give the entity a name and choose the base class the entity
derives. For Hourly Employee, I have chosen the following settings.
If you try to validate the model at this time, you will get the following error.
Error 1 Error 3023: Problem in Mapping Fragments starting at lines 139, 144, 149:
Column Employees.Type has no default value and is not nullable. A column value is required
to store entity data.
This error is caused because we have not specified any Type value for
Employee base class. If Employee entity cannot be mapped to a certain value
and is only a base class, then we must set the Employee entity as an abstract
class to clear the error. After making Employee entity abstract, the model
should validate cleanly. The final model is shown below
To test the model, we create an object context, add different types of
Employees and confirm that records got written to appropriate table. In the
code below I am creating two types of Employees and adding them to
AddToEmployees followed by SaveChanges. To confirm the insert happened
successfully, using the second data context, I am retrieving each type of
Employee and printing the properties of entity on the console window. Notice
that our context does not expose any collection of derived types. The object
context only exposes Employees collection which contains an entity of type
Employee. To access a derived type, we need to use OfType operator passing
in the derived type you needed returned.
var db = new STPTInh();
var hourly = new HourlyEmployee { Name = "Alex",
Rate = 40, Hours = 40 };
var salaried = new SalariedEmployee { Name =
"Chris", Salary = 90000 };
db.AddToEmployees(hourly);
db.AddToEmployees(salaried);
db.SaveChanges();
var db2 = new STPTInh();
var huorly1 =
db2.Employees.OfType<HourlyEmployee>().First();
Console.WriteLine("Name {0} Rate {1} Hours
{2}",huorly1.Name,huorly1.Rate,huorly1.Hours);
var salary1 =
db2.Employees.OfType<SalariedEmployee>().First();
Console.WriteLine("Name {0} Salary
{1}",salary1.Name,salary1.Salary);
Screen shot below shows the property values for employee printed on the
console window.
Problem: Figure below shows the current table structure for Media defined
on the database.
On the database model above, Media is top level table that contains all
different types of Media. Every Media has a title declared on Media table.
Media is divided into two tables; Articles and Videos. Every Article has
Article Content and an author. Article Type defines the type of the articles
Article table contains. Articles can be of two types; Stories or Blog Posting.
For Articles of type Story, we are also capturing IsFiction column. For Blog
Posting an additional field TotalComments is captured. Similarly on Videos
table, a video can be of two types; Education and Recreational Video. The
type of Video is differentiated by VideoType column. Every video has
Resource Path that defines the location of the video. EducationalVideo also
captures Instructor and RecreationalVideo captures the rating of the video.
We want to model this structure using Table per Type for Media and use
Table Per Hierarchy to segregate different types of Articles and Videos.
Solution: In this walk through, we will go through the steps of moving the
Media tables to use Table per Type and then extending the model further to
utilize Table per Hierarchy for Articles and Videos.
1. On the import wizard, import Media, Articles and Video table. Screen
shot below shows the entity data model after completing the import
wizard.
4. Make Article and Video entity inherit from Media entity. Remove
ArticleId and VideoId because inheriting from Media entity we
automatically gives MediaId as the entity which we can later used for
mapping. To inherit Article from Media entity, right click Media and
select Inheritance. On Add inheritance window select Media as the base
entity and Article as the derived entity as shown below.
Perform similar steps for Video entity to map ArticleId column to MediaId
property inherited from Media entity.
6. Article entity would have two derived types Story and BlogPosting. To
create Story entities right click on the design surface and select entity.
Give the entity name, Story and choose Article as the derived type.
Screen shot below shows Add entity wizard values for Story entity
deriving from Article.
//videos
var educvideo = new EducationalVideo { Instructor = "Zee",
ResourcePath = "Asp.netintro.wmv", Title = "Asp.net Video" };
var recreatioanlvid = new RecreationalVideo { Title = "WorldCup",
Rating = 5, ResourcePath = "cricket.wmv" };
db.AddToMedias(blogposting);
db.AddToMedias(story);
db.AddToMedias(educvideo);
db.AddToMedias(recreatioanlvid);
db.SaveChanges();
Screen shot below shows the result of the above cod in output window.
In this tutorial, we covered how we can use Table Per Type and Table
Per Hiearachy to model our table structure. We also wrote queries
against the model by using OfType and Where operator to filter and
return correct entities regardless of the depth of inheritance hierarchy.
Problem: Diagram below shows the current structure and relationship for
Person, Employee and Customer table in the database.
In the above table structure, we have a Person table that contains Employees
and Customers identified by PersonType. If the PersonType is E, it is an
Employee and if the PersonType is C, it is a Customer. Employees are
subdivided into two additional types HourlyEmployee and SalariedEmployee
and have separate tables as shown in the above figure. The primary key
EmployeeId in Salaried and Hourly Employee is also the foreign key for
Person table. ClubMembers table includes additional information about
Customers who have ClubMembership with the Company. You want to
implement the given table structure using Table Per Hierarchy for Customer
and Employees and use table per Type for derived types of Employees and
Customers.
Solution: Import the table structure using import wizard. After wizard
completes, delete all the associations created by the wizard. Create two
entities Customers and Employee and inherit them from Person entity. Make
Person, Employee, Customers as an abstract class since this entity have no
direct equivalent in our database and mainly serves as a base class. Make
SalariedEmployee and HourlyEmployee inherit from Employee entity created
earlier. Move EmployeeNumber from Person entity to Employee entity
because EmployeeNumber is a property specific to Employee entity. Make
ClubMembers inherit from Customer entity and move LoginAccount from
person entity to Customer entity. The last step is to map all the entities to the
store definition imported by the wizard.
Discussion: Steps below outlines using Table Per Hierarchy with Table Per
Type to model the database structure given above.
<EntitySetMapping Name="Locations">
<QueryView>
select value
case
when (el.LocationId is not null) then
EcommerceModel2.EventLocation(l.LocationId,l.Address,el.LocationName)
when (gc.ClubId is not null) then
EcommerceModel2.GunClub(l.LocationId,l.Address,gc.ClubName)
when (range.RangeId is not null) then
EcommerceModel2.ShootingRange(l.LocationId,l.Address,range.RangeName)
END
from EcommerceModel2StoreContainer.Locations as l
left join EcommerceModel2StoreContainer.EventLocation as el on
l.LocationId = el.LocationId
left join EcommerceModel2StoreContainer.GunClubs as gc on
l.LocationId = gc.ClubId
left join EcommerceModel2StoreContainer.ShootingRange as range
on l.LocationId = range.RangeId
</QueryView>
</EntitySetMapping>
Figure below shows the screenshot of the Count for different types of
location returned from the above query.
5.1.6 Optimizing QueryView for Inheritance
InhQVLocationModel.EventLocation(l.LocationId,l.Address,el.Locat
ionName)
when (gc.ClubId is not null) then
InhQVLocationModel.GunClub(l.LocationId,l.Address,gc.ClubName)
when (range.RangeId is not null) then
InhQVLocationModel.ShootingRange(l.LocationId,l.Address,range.Ra
ngeName)
END
from InhQVLocationModelStoreContainer.Locations as
l
left join
InhQVLocationModelStoreContainer.EventLocation as el on
l.LocationId = el.LocationId
left join
InhQVLocationModelStoreContainer.GunClubs as gc on l.LocationId
= gc.ClubId
left join
InhQVLocationModelStoreContainer.ShootingRange as range on
l.LocationId = range.RangeId
</QueryView>
</EntitySetMapping>
InhQVLocationModel.EventLocation(l.LocationId,l.Address,el.Locat
ionName)
from
InhQVLocationModelStoreContainer.Locations as l
left join
InhQVLocationModelStoreContainer.EventLocation as el on
l.LocationId = el.LocationId
</QueryView>
<QueryView
TypeName="IsTypeOf(InhQVLocationModel.GunClub)">
select value
InhQVLocationModel.GunClub(l.LocationId,l.Address,gc.ClubName)
from
InhQVLocationModelStoreContainer.Locations as l
left join
InhQVLocationModelStoreContainer.GunClubs as gc on l.LocationId
= gc.ClubId
</QueryView>
<QueryView
TypeName="IsTypeOf(InhQVLocationModel.ShootingRange)">
select value
InhQVLocationModel.ShootingRange(l.LocationId,l.Address,range.Ra
ngeName)
from
InhQVLocationModelStoreContainer.Locations as l
left join
InhQVLocationModelStoreContainer.ShootingRange as range on
l.LocationId = range.RangeId
</QueryView>
<QueryView
TypeName="IsTypeOf(InhQVLocationModel.Organization)">
select value
case
when (gc.ClubId is not null) then
InhQVLocationModel.GunClub(l.LocationId,l.Address,gc.ClubName)
when (range.RangeId is not null) then
InhQVLocationModel.ShootingRange(l.LocationId,l.Address,range.Ra
ngeName)
END
from
InhQVLocationModelStoreContainer.Locations as l
left join
InhQVLocationModelStoreContainer.GunClubs as gc on l.LocationId
= gc.ClubId
left join
InhQVLocationModelStoreContainer.ShootingRange as range on
l.LocationId = range.RangeId
</QueryView>
Notice that for EventLocation entity, I am doing an explicit join against
EventLocation table and Location only. Similarly GunClub entity joins against
GunClub table and ShootingRange table joins aginst ShootingRange table. When I
execute my same linq query above to get Locations of type eventlocation, the
query send to the database is very precise as shown below.
SELECT
..
FROM [dbo].[Locations] AS [Extent1]
LEFT OUTER JOIN [dbo].[EventLocation]
Another important point to remember is when you are specifying the TypeName,
you must use IsTypeOf operator for EF framework to select the correct queryview
for the entity you are searching on. Failing to do so would cause the query to use
the default QueryView with no TypeName.
Problem: The figure below shows Employee table structure in our database.
The table contains 3 types of Employees. An employee is considered hourly if
the Type column has a value of H. If the value of Type is S and GetsComm is
False, it is a SalariedEmployee. However if the Type has a value of S and
GetComm is set to true, the Employee is considered SalariedWithComm
Employee. You want to model this relationship in entity data model using 3
different employees extending from the base Employee class.
Solution: Import the model using the import wizard. Create 3 new entities,
Hourly Employee, Salaried Employee and SalPlusCommEmployee. Set the
mapping condition for HourlyEmployee where Type equal to H,
SalariedEmployee where Type equal to S and GetsComm to 0,
SalPlusCommEmployee where Type equal to S and GetComm to a value of 1
which means true. Make sure Employee entity is marked as abstract and move
the fields that belong to a derived entity to its own class. Ensure Hourly and
salaried Employee inherit from Employee entity and SalCommEmployee
inherits from SalariedEmployee. The completed entity data model is shown
below.
Discussion: In this example, we will cover how to override conditions to
create nested inheritance hierarchy. Basically derived entity inherits all the
conditions specified by the base entity. However derived entity has the option
to negate or change the value for one or more of the conditions dictated by
base entity. If we look at the Employee table structure given above, we will
notice that GetComm is set to allow null. This is because Hourly Employee
type is determined solely on the Type column where as for Salaried and
SalariedPlusCommission Employee both have a Type set to S and GetsComm
additional column determines if the Salaried Employee makes a commission
or not. Steps below outline the procedure to move the existing Employee table
to 3 derived entity types.
1. Import Employee table using the import wizard. Figure below shows
the table after the wizard completes.
To test our model, we create instances of each type of entity and save
them to the database. Then using second database we can query for
each type of entity confirm that output returned for the entity matches
what we expected. Code below shows an example of using the above
model we have created.
var hourly1 =
db.Employees.OfType<HourlyEmployee>().First();
Console.WriteLine("Hourly Employee " +
hourly1.Name);
var salpluscom1 =
db.Employees.OfType<SalComEmployee>().First();
Console.WriteLine("Sal plus Comm Employee "
+ salpluscom1.Name);
Solution: The above problem requires placing a condition on the base entity
with IsDeleted set to false. Create another entity and call it DeletedOrders
deriving from Orders entity. Set the condition for deleted Orders where
IsDeleted is true.
Discussion: Figure below shows the Orders table structure in the database.
The above table contains IsDeleted column that determines if the Order
is deleted or not. To map the table to entity model as desired, import
the table using the import wizard. Remove IsDeleted column as we will
use it as discriminator column. Apply condition on the Order entity to
IsDeleted = 0 means false. Figure below shows the mapping for Orders
entity.
Create a second entity Deleted Orders and derive it from Orders entity.
Set the condition for Deleted Orders entity to IsDeleted = 1 means true.
Figure below shows the mapping for DeletedOrders entity.
Screen shot below shows the output from the above code.
Problem: You have Person table in the database that contains Customer,
Student and Instructors. Each type of person is identified by a Type column.
You want to import the table structure as Table Per hierarchy with Customer
entity inheriting from Person entity. Instead of Student and Instructor directly
inheriting from Person, you want to add another layer of inheritance
hierarchy, Staff from which Student and Instructor derive from. Having
another layer of inheritance would allow you to program against both Student
and Instructor by using Staff entity. Figure below shows the table structure for
Persons table.
Solution: Import Persons table using import wizard, create four entities
Customer, Student, Instructor and Staff entity. Make Customer entity inherit
from Person entity. Make Student and Instructor entity derive from Staff
entity. Map Customer, student and Instructor entity using table mappings.
Since Staff entity is not defined on the database, make staff entity abstract.
This would ensure that we do not get any validation errors for not mapping
Staff entity.
Discussion: When you add additional layer of inheritance that is not defined
on the database and therefore cannot be mapped, the entity must be marked as
abstract. In the example below, we will go through the steps of adding Staff
entity not defined on the database.
1. Import Persons table using Edm import wizard. Figure below shows
Persons entity on entity designer.
2. Remove Type property as well will use the Type column to map
inheritance structure. Add Customer entity deriving it from Person
entity and move IsClubMember property from Person table to
Customer entity. Map Customer entity to Person table where Type is
Customer as the condition. Figure below shows the mapping for
Customer entity.
3. Create Staff entity and derived it from Person entity. Since Staff and
Person entity do not map to any table in the database, we will make
Staff and Person entity as abstract. Next create Student entity and
derive it from Staff entity. Move EnrollmentDate from Person entity to
Student entity and map Student entity to Person table in the database
where Type is Student for the condition. Figure below shows the
mapping for Student entity.
4. Create Instructor entity and derive it Staff entity. Move HireDate
property from Person entity to Instructor entity and map the entity to
Person table on the mapping window. Set the condition for Instructor
entity to be Type equal to Instructor.
If you try to validate the model after completing the steps mentioned
above, you will get following validation errors.
Error 1 Error 3024: Problem in Mapping Fragment starting at line 272: Must
specify mapping for all key properties (Persons.PersonId) of the EntitySet Persons.
There is nothing wrong with the model we have created. There appears
to be a designer bug where it fails to map PersonId Property for Student
and Instructor entity to Person table. Msl below shows the mapping for
Student and Instructor entity.
<EntityTypeMapping TypeName="IsTypeOf(MediaQWModel.Student)">
<MappingFragment StoreEntitySet="Persons">
<ScalarProperty Name="EnrollmentDate"
ColumnName="EnrollmentDate" />
<Condition ColumnName="Type" Value="Student"
/></MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="IsTypeOf(MediaQWModel.Instructor)">
<MappingFragment StoreEntitySet="Persons">
<ScalarProperty Name="HireDate"
ColumnName="HireDate" />
<Condition ColumnName="Type" Value="Instructor"
/></MappingFragment>
</EntityTypeMapping>
The above mapping is missing PersonId mapping available on Person
entity. When we added another inheritance structure that did not map to
any tables, the designer missed mapping PersonId property to PersonId
column on Person table. Msl below shows the updated mapping for
Student and Instructor entity that validates cleanly.
<EntityTypeMapping TypeName="IsTypeOf(MediaQWModel.Student)">
<MappingFragment StoreEntitySet="Persons">
<ScalarProperty Name="PersonId"
ColumnName="PersonId" />
<ScalarProperty Name="EnrollmentDate"
ColumnName="EnrollmentDate" />
<Condition ColumnName="Type" Value="Student"
/></MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="IsTypeOf(MediaQWModel.Instructor)">
<MappingFragment StoreEntitySet="Persons">
<ScalarProperty Name="PersonId"
ColumnName="PersonId" />
<ScalarProperty Name="HireDate"
ColumnName="HireDate" />
<Condition ColumnName="Type" Value="Instructor"
/></MappingFragment>
</EntityTypeMapping>
To test the above model created, we can create instance of each entity
type, save them to the database and using second data context retrieve
entities from the database and confirm the results match. On the code
below, I am creating instance of Customer, Instructor and Student
entity and saving it to the database. Using the second datacontext, I
retrieve the count of Persons in the database and count of Persons of
type Staff.
var db = new EcommerceEntities();
var cust = new Customer { Name = "Zee", IsClubMember
= true };
var instructor = new Instructor { HireDate =
DateTime.Now, Name = "Alex" };
var student = new Student { Name = "John",
EnrollmentDate = DateTime.Now };
db.AddToPersons(cust);
db.AddToPersons(instructor);
db.AddToPersons(student);
db.SaveChanges();
Problem: You have Contact table in the database that contains both Customer
and Employee. Each type of Contact is identified by a Type column. The
Type column has allow null set to true. If there is no value for Type column,
it means it is a regular Contact. Otherwise it could be either Customer or an
Employee depending on if the Type has a value of Customer or Employee.
You want to model this relationship as base class Contact that contains the
regular contacts with no type value and Customer and Employee Contact
extending Contact with their mapped to contacts with appropriate Type value.
Your current database structure is shown below.
Solution: Import Contact table into entity data model. Create two entities
Customer and Employee deriving from Contact entity. Move fields’ specific
to derived entities to its own class. Apply condition on Contact entity to
where Type is null. Apply condition of Type equal to Customer on Customer
entity and Type equal to Employee for Employee entity.
Discussion: In this example, we will learn how to use Null condition for
entity mapping. The Type column on the database is marked as null so that
regular contacts will have no value for Type where as Customer and
Employee entity will have specific value for Type column on the Contact
Table. Steps below illustrate how to map the above database table to model
shown below.
1. Import Contacts table using the wizard. Create two entities Customer
and Employee. Make Customer and Employee entity inherit from
Contact entity. Move IsClubMember property to Customer entity and
CompanyName to Employee entity. Set condition on Contact where
Type is null. We are setting the type is null because if Type column
does not have any value in the database, it is considered a regular
Contact. Also remove Type property from Contact entity since we will
use Type column to map inheritance. Figure below shows the mapping
for Contact entity.
2. For Customer entity set Type condition to Customer and maps
IsClubMember property to IsClubMember column on Contact table.
Figure below shows the mapping for Customer entity.
3. For Employee entity set Type condition equal to Employee and maps
CompanyName property to CompanyName column on Contact table.
Figure below shows the mapping for Employee entity.
Code below shows how to use the current model created.
Looking at the model generated by the designer, you will notice that EF
created 1 to many association between Manufacturer and Locations
entity. We did not get any entity called ManufacturerLocation because
entity framework turned that table as 1 to Many association. To fix the
model, first remove all the associations created by the designer. This
will also remove the navigation properties exposed on the Location
entity to Manufacturers. Create a new entity on the designer called
ManufacturerLocation that derives from Location entity. Create Many
to 1 association between ManufacturerLocation and Manufacturer
entity with ManufacturerLocation being the Many side and
Manufacturer being the 1 side of the relationship. Figure below shows
the association between ManufacturerLocation and Manufacturers.
Notice on the mapping window that we did not map any property to
ManufacturerId column. This is because we will use association
between ManufacturerLocation and Manufacturer to populate
Manufacturerid. To do this right click on the association line between
Manufacturer and ManufacturerLocation and select table mapping
window and choose ManufacturerLocation table. The designer will
auto fill the columns to map to entity keys defined on both ends of the
association. Figure below shows the mapping for the association.
3. Next select company entity and ensure that it derives from Location
entity. Remove the Locationid property on Company entity because we
inherited LocationId from Location entity. On the mapping window for
Company entity, ensure that LocationId column maps to LocationId
property acquired from Location base entity. Figure below shows the
mapping for Company entity.
After completing the above steps, entity model for manufacturer and
Company should like the one below.
Code below shows how to use create ManufacturerLocation, associate
it with a Manufacturer and save it to the database. In addition we are
also creating Company entity a derived type of Location and saving
that to the database also.
Problem: Figure below shows Tables Company and School defined on the
database.
You want to use the above table in your entity data model using Table per
Concrete Type implementation. The model should have base entity Location
which has an address field that is common to both tables. The Location entity
should have two derived entities Company and School containing properties
specific to their entity. To ensure that primary key values to do collide
between Company and School table, the identity column CompanyId for
Company table is set to start at 200 and identity column SchoolId starts for
School table starts at 1.
Solution: Import Company and School table using the import wizard. Create
abstract Location entity and move address property from School and
Company entity to Location entity. Make Company and School entity derive
from Location entity. Since the designer does not support mapping properties
to different table on derived entity, modify the msl manually by adding
LocationId and address mapping to both Company and Office entity.
Discussion: Table Per Concrete Type allows individual tables to map to same
base entity. If there are column that differ in each table, those columns can
appear as properties on derived entity. For Table per Concrete Type to work
properly, the entity key must be unique across both tables. If entity
framework finds that CompanyId and SchoolId have the same value for a
primary key, it would throw primary key violation during runtime. In addition
Table Per Concrete Type allow two or more tables to be under the same
EntitySet umbrella making querying, inserting and updating a unified process.
Steps below outline the process of importing Company and School table to
use Table per Concrete Type.
1. Import Company and School table using table import wizard. After
completing the wizard, entity model show look as below.
Problem: Figure below shows the current table structure for Employee and
its Salary table in the database.
You want to model the above table structure using table per Hierarchy. The
model should have an abstract salary entity with two derived entities;
CurrentSalary and HistoricalSalary. Current and HistoricalSalary are
identified by EndDate column on Salary table. If EndDate column has a null
value, it is considered CurrentSalary entity otherwise it is a HistoricalSalary.
The base entity Salary should have 1 to many associations with Employees
because an Employee can have many salaries with only 1 salary being the
current salary and rest would be HistoricalSalary.
Solution: Import Employee and Salary table using the import wizard. Make
Salary entity abstract. Create two entities HistoricalSalary and CurrentSalary
that derive from Salary entity. For CurrentSalary apply a condition on the
mapping where EndDate is null. For HistoricalSalary entity, apply the
condition for the mapping where EndDate is not Null. At this point if you try
to validate the model, you will receive error stating that EndDate has a
condition for not null and there is no mapping specified. This makes
legitimate sense because once we specify a condition for previous salary
entity stating that endDate cannot be null, then there has to be a way to map a
value to that property, otherwise what should it value be? So after applying
EndDate condition on HistoricalSalary, make sure to move EndDate property
from base Salary entity to HistoricalSalary and map the property to EndDate
column on Salary table. When we imported the model, the designer created
the EndDate property as nullable since EndDate column has allow null in the
database. However for HistoricalAlary, our condition states that EndDate can
never be null. So the last step to validate the model requires making EndDate
property as not nullable. The final entity diagram should look like below.
Solution: For entity framework to model table per type, the primary key from
derived table also needs to be the foreign key for the base table. If this
relationship is not defined on the tables, then entity framework cannot map
the tables to Table per Type inheritance. What this means is you can’t
leverage the default mapping capabilities of the designer. However you can
write QueryView in which you can write arbitrary esql to join different tables
from the store model and define your own mapping. First, import Person,
Employee and Customers table using Entity Model Wizard. Delete all the
associations created by the designer and make Employee and Customer entity
derive from Person entity. Ensure that EmployeeId and CustomerId are no
longer the entity key on Customer and Employee entity because conceptual
model needs to still follow EF rules which states that derived table’s primary
key must also be the foreign key to the base entity. Since our store model
does not follow this practice, we can open the edmx file in xml format and
use QueryView for Persons entityset.
1. Import the table using entity data model wizard. Figure below shows
the model after the wizard has completed.
2. Remove all the associations created by the designer. Make Employee
and Customer entity derive from Person entity. Since inheriting will
give us PersonId as the entity key which also has to be the entity key
for derived entity, change the CustomerId and EmployeeId to not be an
entity key anymore. In addition remove all the table mappings created
by the designer because we will modify the msl in xml and define a
queryview to populate each of entity defined with Person entityset.
Figure below shows the updated model
3. Open the edmx file in xml and modify the msl with the following
QueryView for Persons entityset.
<EntitySetMapping Name="Persons">
<QueryView>
select value
case
when (c.PersonId is not null) then
QvForeignModel.Customer(p.PersonId,p.Name,c.AccountNo,c.Cus
tomerId)
when (e.PersonId is not null) then
QvForeignModel.Employee(p.PersonId,p.Name,e.CompanyName,e.E
mployeeId)
END
from
QvForeignModelStoreContainer.Person as p
left join
QvForeignModelStoreContainer.Customers as c on p.PersonId =
c.PersonId
left join
QvForeignModelStoreContainer.Employee as e on p.PersonId =
e.PersonId
</QueryView>
</EntitySetMapping>
On the above QueryView, I am using esql to join Person table defined
on the store model to Customers and Employee table. If there is a
matching row found in Customer table, I am creating an instance of
Customer entity and if there is a matching row found on employee
entity, I am creating an instance of Employee entity. This means that
when we query for Persons collection, there would be some person of
type Customer and some Employee. It is important to understand that
when we write a queryview, EF has no clue how save the entities to the
database. You are required to provide stored procedures that can map
each entity to the database. So first we need to define mapping for
insert, update and delete stored procedure for each entity that is part of
Person entityset. Msl below shows the stored procedure mapping for
Customer and Employee derived entities.
<EntityTypeMapping TypeName="QvForeignModel.Customer">
<ModificationFunctionMapping>
<InsertFunction
FunctionName="QvForeignModel.Store.InsertCustomer">
<ScalarProperty
Name="Name" ParameterName="Name"/>
<ScalarProperty
Name="AccountNo" ParameterName="AccountNo" />
<ResultBinding
Name="PersonId" ColumnName="PersonId"/>
<ResultBinding
Name="CustomerId" ColumnName="CustomerId"/>
</InsertFunction>
<UpdateFunction
FunctionName="QvForeignModel.Store.UpdateCustomer">
<ScalarProperty
Name="Name" ParameterName="Name" Version="Current"/>
<ScalarProperty
Name="AccountNo" ParameterName="AccountNo" Version="Current" />
<ScalarProperty
Name="PersonId" ParameterName="PersonId" Version="Current"/>
<ScalarProperty
Name="CustomerId" ParameterName="CustomerId" Version="Current"/>
</UpdateFunction>
<DeleteFunction
FunctionName="QvForeignModel.Store.DeleteCustomer">
<ScalarProperty
Name="PersonId" ParameterName="PersonId"/>
</DeleteFunction>
</ModificationFunctionMapping>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="QvForeignModel.Employee">
<ModificationFunctionMapping>
<InsertFunction
FunctionName="QvForeignModel.Store.InsertEmployee">
<ScalarProperty
Name="Name" ParameterName="Name"/>
<ScalarProperty
Name="CompanyName" ParameterName="CompanyName" />
<ResultBinding
Name="PersonId" ColumnName="PersonId"/>
<ResultBinding
Name="EmployeeId" ColumnName="EmployeeId"/>
</InsertFunction>
<UpdateFunction
FunctionName="QvForeignModel.Store.UpdateEmployee">
<ScalarProperty
Name="Name" ParameterName="Name" Version="Current"/>
<ScalarProperty
Name="CompanyName" ParameterName="CompanyName" Version="Current"
/>
<ScalarProperty
Name="PersonId" ParameterName="PersonId" Version="Current"/>
<ScalarProperty
Name="EmployeeId" ParameterName="EmployeeId" Version="Current"/>
</UpdateFunction>
<DeleteFunction
FunctionName="QvForeignModel.Store.DeleteEmployee">
<ScalarProperty
Name="PersonId" ParameterName="PersonId"/>
</DeleteFunction>
</ModificationFunctionMapping>
</EntityTypeMapping>
The above mapping is very similar to the examples we have covered on
stored procedure section. The only noticeable difference is that both
InsertCustomer and InsertEmployee have two resultBindings. This is
because the PersonId is the primary key for Person table generated by
the database and CustomerId and EmployeeId are the primary keys for
Customer and Employee table also generated by the database. After
mapping the stored procedure, we need to define these stored
procedures inside our storage model. We can either created procedures
in the database and reference it on our storage model, or we can declare
the code inline inside the commandText property of the function. Since
insert, update and delete stored procedure for Employee and Customer
are pretty similar, code below shows the crud proc for Customer entity.
<Function IsComposable="false" BuiltIn="false"
Name="InsertCustomer">
<CommandText>
declare @personid int
insert into qv.person(Name) values
(@Name)
set @personid = SCOPE_IDENTITY()
insert into
qv.Customers(AccountNo,PersonId) values (@AccountNo,@personid)
select @personid as
PersonId,SCOPE_IDENTITY() as CustomerId
</CommandText>
<Parameter Name="Name" Type="varchar"
Mode="In" />
<Parameter Name="AccountNo"
Type="varchar" Mode="In" />
</Function>
<Function IsComposable="false"
BuiltIn="false" Name="UpdateCustomer">
<CommandText>
update qv.person set Name = @Name
where personid = @PersonId
update qv.Customers set AccountNo
=@AccountNo where CustomerId =@CustomerId
</CommandText>
<Parameter Name="Name" Type="varchar"
Mode="In" />
<Parameter Name="AccountNo"
Type="varchar" Mode="In" />
<Parameter Name="PersonId" Type="int"
Mode="In" />
<Parameter Name="CustomerId" Type="int"
Mode="In" />
</Function>
<Function IsComposable="false"
BuiltIn="false" Name="DeleteCustomer">
<CommandText>
delete qv.person where personid
=@PersonId
delete qv.Customers where personid
= @PersonId
</CommandText>
<Parameter Name="PersonId" Type="int"
Mode="In" />
</Function>
For InsertCustomer stored procedure, I am inserting the Name into
Person table and then assigning the Personid inserted to local variable
personid. Then using PersonId and AccountNo parameter, I am
inserting a record inside of Customer entity. To return both PersonId
and CustomerId back to the conceptual layer using ResultBinding, I am
executing a select statement with both personid and CustomerId. The
alias that I am using for selecting PersonId and CustomerId has to
match with the ResultBinding parameter on the stored procedure
mapping defined on the msl. Update stored procedure updates both
Person and Customer table with the appropriate values from the
parameter. Similarly delete procedure only uses PersonId to delete the
person first from Person table and then delete the customer that
matches the personId in the Customer table.
Code below is used to test the model we created earlier and confirm
that our crud procedures performs the insert, update and deletes correctly. In
the code, I am creating different type of Person such as Customer and
Employee entity, adding it Persons entityset and saving it to the database.
Using second datacontext, I am retrieving both Customer and Employee and
printing the Name, PersonId , CustomerId and EmployeeId on the console
window.
var db = new QvForeignEntities();
var customer = new Customer { AccountNo = "123",
Name = "Zeeshan" };
var employee = new Employee { CompanyName = "Chem
Ltd", Name = "John" };
db.AddToPersons(customer);
db.AddToPersons(employee);
db.SaveChanges();
Problem: Figure below shows the ContactInfo table structure that contains
different types of contacts based on ContactType column.
Contacts stored in the table can be of 3 different types; HomePhone,
CellPhone, and EmailAddress. You want to model the above table structure
using Table per Hierarchy but want to add an additional layer of inheritance
on your conceptual model. Both CellPhone and HomePhone should extend
from Phone entity and Phone entity should derive from ContactInfo entity.
Figure below shows how the conceptual model should look like after
modeling.
Solution: Import ContactInfo table using Entity data model wizard. Create
Phone and EmailAddress entity that derive from ContactInfo entity. Move
Email property from ContactInfo entity to EmailAddress entity and move
PhoneNumber from ContactInfo to Phone entity. Make ContactInfo and
Phone entity as abstract. Create two entities HomePhone and CellPhone
deriving from Phone entity and remove all the table mappings created by the
designer. Modify the entityset mapping for ContactInfo by writing a
QueryView that queries the store model and create appropriate derived
entities based on the value for ContactType.
1. Import ContactInfo table using Entity data model wizard. Figure below
shows the model created by the wizard.
<EntitySetMapping Name="ContactInfos">
<QueryView>
select value
case
when c.ContactType = 'HP' then
TPHQVContactModel.HomePhone(c.ContactInfoId,c.PhoneNumber)
when c.ContactType = 'CP' then
TPHQVContactModel.CellPhone(c.ContactInfoId,c.PhoneNumber)
when c.ContactType = 'EA' then
TPHQVContactModel.EmailAddress(c.ContactInfoId,c.Email)
END
from
TPHQVContactModelStoreContainer.ContactInfo as c
</QueryView>
</EntitySetMapping>
db.AddToContactInfos(homephone);
db.AddToContactInfos(cellphone);
db.AddToContactInfos(emailaddr);
db.SaveChanges();
Problem: Figure below shows the database diagram for relationship between
Category and Product table.
On the above database diagram, both Category and Product table has audit
fields such as CreateDate, CreatedBy, ModifiedDate and ModifiedBy. Instead
of having these same fields on each entity on your entity data model, you
want to create a base entity called Audit which contains all the audit fields
including the primary key for both tables. You then want to make sure that
both entities Category and Product derive from base Audit entity so they can
leverage the base properties as well as the business logic that revolves around
them.
Solution: When we import Category and Products table into EDM and try to
map both entities as derived entities of Audit entity base class, EF would
complain because both tables do not have anything in common in terms of
primary key or entity key. They are in completely different entityset.
Category entity belongs to Category entityset and Product entity belongs to
Products entityset. In the current version of EF, the designer does not allow
mapping tables to derived entities that belong to completely different entity
set. We have to create the conceptual using the designer. Once that’s
accomplished, we need to modify the mapping manually in the xml and map
the properties defined on the Audit entity to each table defined for derived
entity. In addition we also need to ensure that base entity Audit is marked as
abstract and does not belong to any entityset.
Steps below outline the process of mapping audit fields common in all tables
defined on the base entity and mapped to each table in the database.
1. Import Category and Products table using entity Data Model Wizard.
Figure below shows the model after finishing the wizard.
2. To share audit fields between Category and Products entity, create a
new entity Audit and move CreateDate, CreatedBy and ModifiedDate
properties to the new entity. Since Audit entity must have an entity key,
create an Id property and make it the entity key. This key will be used
by Product and Category entity and therefore remove ProductId and
CategoryId entity key defined on Category and Products.
3. Change Product and Category entity so that they inherit from Audit
entity leveraging audit fields as well as EntityKey Id. Also make Audit
entity abstract because there will be no mapping for the base entity.
Figure below shows the updated model after completing the above
steps.
Now that we have configured our conceptual model, we need to map
the model to our storage model. EF designer does not support mapping
TPC inheritance that will reside in different entitysets; therefore we
need to modify the MSL in xml.
5. The mapping created by the designer for Category and Products are
incomplete because it does not include audit properties that we moved
to base entity Audit. Add audit field mappings to each derived entity
defined on the mapping. Also make sure that both categories and
Products are mapped to a different entityset which is not the configured
mapping done by the designer. Code below shows the correct mapping
for Category and Product entity.
<EntitySetMapping Name="Categories">
<EntityTypeMapping
TypeName="IsTypeOf(AuditTPCModel.Category)">
<MappingFragment StoreEntitySet="Category">
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="Id" ColumnName="CategoryId"
/>
<ScalarProperty Name="CreateDate"
ColumnName="CreateDate"/>
<ScalarProperty Name="CreatedBy"
ColumnName="CreatedBy"/>
<ScalarProperty Name="ModifiedDate"
ColumnName="ModifiedDate"/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="Products">
<EntityTypeMapping
TypeName="IsTypeOf(AuditTPCModel.Product)">
<MappingFragment StoreEntitySet="Products">
<ScalarProperty Name="Name"
ColumnName="Name" />
<ScalarProperty Name="Id"
ColumnName="ProductId" />
<ScalarProperty Name="CreateDate"
ColumnName="CreateDate"/>
<ScalarProperty Name="CreatedBy"
ColumnName="CreatedBy"/>
<ScalarProperty Name="ModifiedDate"
ColumnName="ModifiedDate"/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
Since the designer does not fully support this feature, it also missed the
mapping for the association between Categories and Products defined
on the conceptual model. Code below shows the mapping for
FK_Products_Category Association defined on the conceptual model.
<AssociationSetMapping Name="FK_Products_Category"
TypeName="AuditTPCModel.FK_Products_Category" StoreEntitySet="Products">
<EndProperty Name="Category">
<ScalarProperty Name="Id" ColumnName="CategoryId"/>
</EndProperty>
<EndProperty Name="Products">
<ScalarProperty Name="Id" ColumnName="ProductId" />
</EndProperty>
</AssociationSetMapping>
6. By defining the audit fields on the base entity, both entities can
leverage the auditing behavior and do not require duplicating properties
on every entity defined on the model. Hopefully future versions of EF
will provide support to map this architecture using designer. Code
below creates an instance of Category and Product entity and also
defines values for audit fields declared on the base Audit entity. Then
product entity is added to the category’s product collection and both
entities are saved to the database. To confirm the save went
successfully using second data context, I am retrieving the product and
its category and displaying the result to output window.
Problem: Figure below shows the database diagram for Gunsmith and its
contact information.
Solution: The above table relationship can be represented using two Table
per Type inheritance. Import the above tables using the import wizard.
Remove all the associations created by the designer. Make GunSmith entity
derive from Contact entity and make Company entity derive from Location
entity. Remove GunSmithId from GunSmith entity and CompanyId from
Company entity. Create 1 to many association between company and
GunSmith entity where GunSmith is the many side of the association.
Validate the model and ensure that there are no build errors.
Discussion: Steps below outline the process of importing the above table
structure using Table Per Type inheritance.
1. Import the above table structure using Entity Data Wizard. Figure
below shows the model created by the wizard.
2. Remove association between Location and Company entity and remove
association between GunSmith and Contact entity.
3. Make GunSmith entity derive from Contact and delete GunSmithId
property from GunSmith entity as ContactId will serve as the entity
key. On GunSmith mapping, set GunSmithId column to map to
ContactId property available from Contact entity. Figure below shows
the mapping for GunSmith entity.
4. Make Company entity derive from Location and delete CompanyId
from Company entity as LocationId will serve as the entity key. On
Company mapping, set Companyid column to map to LocationId
property inherited from Location entity. Figure below shows the
mapping for Company entity.
Problem: Figure below shows the database diagram between Customer and a
Lookup table Type.
Solution: Import the above table using EDM wizard. Remove all associations
created by the designer. Create three entities; CustomerType, LeadType and
IndustryType and make sure they derive from Type entity. Make Type entity
as abstract because we will not be instantiating it from code. Create three
associations from Customer going to CustomerType, IndustryType and
LeadType where Customer has a Multiplicity of 1 on each association.
1. Import Type and Customer table using EDM wizard. Figure below
shows the model wizard has generated.
2. Remove all the associations between Customer and Type entity. Create
three entities CustomerType, LeadType and IndustryType. Make sure
all entities derive for Type entity and make Type entity as abstract.
Since we will use the Name column as a condition for each derived
entity, it cannot be mapped to a property. Remove the Name column
from base entity Type. Figure below shows the updated model.
.Include("CustomerType").Include("LeadType").Include("IndustryTy
pe")
.First(c => c.Name == "Zeeshan");
You want to import the above table as inheritance hierarchy into EDM. The
base class Product should contain two derived classes; DiscontinuedProduct
and ChrismasSpeical. DiscontinuedProduct is identified by discontinued
column in the Product table and ChristmasSpecial product are products that
have rows present in the ChrismasSpecial table as which indicates the
discount customer will receive on those products. The final EDM model
should look as below.
Solution: A hybrid approach where the same table Product is used as Table
per Hierarchy and Table per Type is not directly supported by EF designer.
We can model the conceptual model using the designer, however mapping
between the conceptual and storage model cannot be fully accomplished
using the designer. To configure the mapping, DiscontinuedProduct needs to
be mapped to product table where discontinued column has a value of true.
For product entity, the condition for discontinued column has to be set to
false. ChristmasSpeical entity also must have discontinued value of false in
addition to product entity. This is not supported in the designer and has to
done manually in the msl by adding multiple types inside the product
mapping. Code below shows the manual change required in the designer.
<EntityTypeMapping
TypeName="ProductsHiearchyModel.Product;IsTypeOf(ProductsHiearchyModel.Christ
masSpecials)">
Discussion: Steps below outline the process of importing the above table
structure using TPH and TPT to create Product base entity and two derived
entities; ChrismasSpecial and DiscontinuedProduct.
To confirm the results were inserted correctly with correct value for
discontinued column and also inserted into ChristmasSpecial table, I
have run the select statement on Product and ChristmasSpecial table.
Figure below shows the output.
Solution:
Discussion: Steps below outline the process of importing the customer table
into edm as customer and club member hierarchy.
1. Import customer table using EDM wizard. Add Club member entity to
the model deriving from Customer entity.
2. Move ClubDues and ClubDiscount properties from customer entity to
ClubMember entity. When the designer creates these two properties,
they are marked as allow null but for ClubMember, the properties
cannot be null. Change both properties nullable attribute to false.
3. Set condition for customer entity mapping where ClubDues and
ClubDiscount column are null. Figure below shows the mapping for
customer entity.
Problem: You have implemented inheritance in the database using Table per
Type. You want to how to many tables in your database using inheritance
feature available in Linq To Sql.
Solution: Linq to Sql only support Table Per Hierarchy model where all the
classes are mapped to one table in the database. Each row in the table is
differentiated by Type column also called as discriminator column. Another
form of inheritance that is used often in the database is Table Per Type model.
In Table Per Type, each class is mapped to a separate table in the database.
Each table’s primary key column is also the foreign key column for a base
table that contains common attributes belonging to all tables. All primary
keys for the child tables are basically generated in the the base table and used
as foreign key and primary key in the child tables. The advantage of using
Table Per Type to Table Per Hierarchy is, you do not have to create one big
gigantic table which contains fields for all concrete types and allow null on
the columns because other concrete types do not have any meaning for those
columns. Table Per Type approach helps reduced disk usage by not allowing
null values and keeps the data integrity intact. Since Table Per Type is not
supported directly by linq to sql, you will have to resort to a view to give a
perception to linq to sql that you are actually using Table Per Hiearchy and
for each type we will expose a Type attribute that will help linq to sql in
identity which table to map to what concrete types. For read scenarios, the
view options works great but what happens when you need to write the
inheritance model to the database. You can’t because we did not target tables,
we mapped view to entities and when linq to sql applies updates and inserts to
the view, sql server would complain that view is not updatable. One option
you can use to get around this is use instead of Trigger that will intercept the
row attempting to be inserted and map it to the appropriate table. Another
option is to override the Crud process in Linq To Sql and perform your crud
operations manually. To get started you need to create a view that exposes the
data from all different tables and map that to linq to sql entity classes. Next
you need override insert, update and delete process by registering with the
partial methods exposed by DataContext for each class. Code below shows
how we setup our hierarchy to map our view to entities defined. We are also
customizing the insert process by overriding the insert process.
Discussion
As we discussed earlier, Linq to Sql does not support Table Per Type
Inheritance, so we have to create a view that combine data from all concrete
tables into a single table. Code below shows the view that combines data from
all 3 different tables; HourlyEmployee, SalariedEmployee and
CommissionedEmployee.
create view dbo.vwEmployees as
Select
e1.*,
he.Hours,
he.Rate,
null Salary,
null VacationDays,
null CommPerc,
null ItemsSold,
'HE' EType
from Employee e1 join HourlyEmployee he on e1.EmployeeId = he.EmployeeId
union
Select
e1.*,
null Hours,
null Rate,
se.Salary,
se.VacationDays,
null CommPerc,
null ItemsSold,
'SE' EType
from Employee e1 join SalariedEmployee se on e1.EmployeeId =
se.EmployeeId
union
Select
e1.*,
null Hours,
null Rate,
null Salary,
null VacationDays,
ce.CommPerc,
ce.ItemsSold,
'CE' EType
from Employee e1 join CommissionedEmployee ce on e1.EmployeeId =
ce.EmployeeId
Above view, combines records from all 3 tables by using a union operator.
Since a view requires the number of columns to match for each union
operation, we have to add null columns for any extra columns to
accommodate data not provided by other table. For instance the first union
operation with HourlyEmployee only has Hours and Rate specific to
HourlyEmployee. Since we want to match extra columns provided by other
table, we added null values for Salary, VacationDays, CommPerc, ItemSold.
Now that we have all 3 tables merged into one view, we need to add a Type
column to differentiate each type of Employee. For this we have added EType
column and for each table we have specified a different value for EType. For
HourlyEmployee, I am using HE for EType, for SalariedEmployee SE and for
CommissionedEmployee, I am using CE.
Once the view is setup, we can map the view to our specific entities.
Following walk through takes you through the process of mapping our view
to concrete classes defined on the linq to sql designer.
We start with dragging our view defined on the database onto the designer.
We will than change our entity name from vwEmployee to just Employee.
Since employee class maps to 3 distinct concrete types HourlyEmployee,
SalariedEmployee and ComissionedEmployee, we will add these classes to
the designer and inherit from our base Employee class as shown below.
Once we have our concrete classes’ setup, we can move additional fields
defined in our base class that is not common to each class into their respective
classes as follows.
Next we need to define how linq to sql is going to map our view data to each
concrete class. For that we use the decimator column and specify
discriminator value for each concrete type. We also need to specify the
default class the inheritance will map to in the case a valid discriminator value
is not found that matches any of the concrete type classes.
In the above screen shot, I am saying that linq to sql should use EType
column defined in my view to map to each concrete class. IF EType has a
value of CE, it should map to CommissionedEmployee; SE should map to
SalariedEmployee and HE should map to HourlyEmployee class.
Our concrete classes are mapped to view which does not support inserts and
updates and deletes. If you were to trying inserting a concrete type, sql server
will raise an exception saying that view is not updatable. To support the
insert, updates and deletes, we can create stored procedures that can manage
writing data to appropriate tables. Following code shows 3 insert stored
procedures that can insert into employee base class and their respective
concrete class.
create proc dbo.InsertHourlyEmployee
(
@EmployeeId int output,
@Name varchar(50),
@Email varchar(50),
@Hours int,
@Rate int
)
as
begin
insert into Employee(Name,Email) values (@Name,@Email)
set @EmployeeId = SCOPE_IDENTITY()
insert into HourlyEmployee(EmployeeId,Hours,Rate) values
(@EmployeeId,@Hours,@Rate)
end
In the Delete stored procedure above, I am deleting the row from first
HourlyEmployee and then deleting it from Employee table. Once we have the
stored procedures created for each of the classes, we can drag those stored
procedures on the linq to sql designer to leverage it for customizing the insert
process.
When your drag the stored procedures linq to sql, automatically creates
methods exposed on the datacontext, that makes it easier to call the stored
procedure. If the stored procedure returns an output parameter, you have to
use ref parameter to get the value from the output parameter.
If we do not define primary column, linq to sql will not create partial methods
for insert, update and delete of Employee entity and thus you cannot override
the crud process.
Code below shows how we intercept the insert request for Employee entity.
partial void InsertEmployee(Employee instance)
{
int? empid = instance.EmployeeId;
if (instance is HourlyEmployee)
{
HourlyEmployee emp = instance as
HourlyEmployee;
this.InsertHourlyEmployee(ref empid,
emp.Name, emp.Email, emp.Hours, emp.Rate);
instance.EmployeeId = empid.Value;
}
if (instance is SalariedEmployee)
{
SalariedEmployee emp = instance as
SalariedEmployee;
this.InsertSalariedEmployee(ref empid,
emp.Name, emp.Email, emp.Salary, emp.VacationDays);
instance.EmployeeId = empid.Value;
}
if (instance is CommissionedEmployee)
{
CommissionedEmployee emp = instance as
CommissionedEmployee;
this.InsertCommissionedEmployee(ref empid,
emp.Name, emp.Email, emp.CommPerc, emp.ItemsSold);
instance.EmployeeId = empid.Value;
}
}
this.DeleteHourlyEmployee(instance.EmployeeId);
}
}
Now that we have all classes ready to be inserted and queried, we can query
and insert against our classes. Code below shows how we can can insert
various employee and query for a specific employee in the database.
public static void InsertEmployees()
{
var hourlyemp = new HourlyEmployee
{
Email = "[email protected]",
Hours = 40,
Rate = 50,
Name = "Alis"
};
var salaryemp = new SalariedEmployee
{
Email = "[email protected]",
Name="Sam",
Salary=90000,
VacationDays = 15
};
var commemp = new CommissionedEmployee
{
Email = "[email protected]",
Name="John",
CommPerc=20,ItemsSold=50,
};
var db = new TablePerTypeDataContext();
//inserting hourly, salaried and commissione
emploee
db.Employees.InsertAllOnSubmit(new Employee[] {
hourlyemp, salaryemp, commemp });
db.SubmitChanges();
Problem: You are using table per hierarchy inheritance in the database. You
want to how map your single table to numerous derived entities defined in
linq to sql designer. You also want to map your discriminator column to enum
defined in your
Solution: When you are programming in .net, you can apply inheritance and
polymorphism concepts on your entity classes to solve your business
problems. However when you are saving those objects to the database, there
is no clean way to store different child entities having some common
properties that they inherit from the base class and some properties that are
specific to that child entity. One of the ways to store these objects in the
database is by adding all the possible fields available for the entire child and
base classes and marking them as allow null. Then you simply set appropriate
values for only the fields that are applicable to that specific child classes. In
order to differentiate each row and identity what child class it is, you can
make use of discriminator column. Below code shows how to map single
table to multiple classes using Linq To Sql
public enum BookType
{
Technical=1,
Certification = 2,
Cooking = 3,
Novel = 4
}
[Table]
[InheritanceMapping(Code=BookType.Novel,Type=typeof(Novel),I
sDefault=true)]
[InheritanceMapping(Code = BookType.Cooking, Type =
typeof(CookingBook))]
[InheritanceMapping(Code = BookType.Technical, Type =
typeof(TecnnicalBook))]
[InheritanceMapping(Code = BookType.Certification, Type
= typeof(CertificationBook))]
public abstract class Book
{
[Column(IsPrimaryKey=true,IsDbGenerated=true)]
public int BookId { get; set; }
[Column]
public string Title { get; set; }
[Column(IsDiscriminator=true)]
public int Type { get; set; }
}
Discussion: To begin our discussion, we will start with how a single table that
maps to numerous classes looks like in the database.
In above screen shot, I have a table book, which contains different types of
books. All books have Title in common which means Title column in the
book table is marked as required because it applies to all books. Then every
derived book has properties specific to itself. When the Type is 1, it is a
technical book and we are interested in knowing about what kind of
technology the book talks about. When the Type is 2, the book is a
certification book and we are capturing the exam this book targets. When the
Type is 3, the book is a Cooking book and we are capturing if the book
contains recipes. If the Type is 4, the book is a novel and we are recording if
the book contains a true story. Any column that is not specific to a specific
book is set to null. To map these rows to different derived classes in C#, we
will make use of Inheritance Mapping Attribute and specify which enum
value maps to a specific derived class.
public enum BookType
{
Technical=1,
Certification = 2,
Cooking = 3,
Novel = 4
}
[Table]
[InheritanceMapping(Code=BookType.Novel,Type=typeof(Novel),I
sDefault=true)]
[InheritanceMapping(Code = BookType.Cooking, Type =
typeof(CookingBook))]
[InheritanceMapping(Code = BookType.Technical, Type =
typeof(TecnnicalBook))]
[InheritanceMapping(Code = BookType.Certification, Type
= typeof(CertificationBook))]
public abstract class Book
{
[Column(IsPrimaryKey=true,IsDbGenerated=true)]
public int BookId { get; set; }
[Column]
public string Title { get; set; }
[Column(IsDiscriminator=true)]
public int Type { get; set; }
}
In the above code, I have marked my book class as an abstract class because
the class itself does not map to any specific row in the book’s table. To
identity that book class should participate in linq to sql, I am using Table
attribute. Next I am applying column attributes to BookId , Title, and Type to
indicate that these properties map to column in the database. For BookId, I am
also specifying that it is the primary key column and it is generated by the
database. Failing to do so would raise exception if when I try to save any
derived book class to the database because linq to sql requires primary key
column to perform inserts. For Type column, I also specifying that it should
be used as a discriminator column. Discriminator column determines what
type of object to instantiate for a particular record in the database. Next I
specify the inheritance mappings to all the derived classes based on a specific
valued found in the discriminator column by making use of Code attribute.
Instead of hard coding an integer value for Code, I am making use of Enum
which Linq to Sql automatically maps to the appropriate integer valued
defined on the Type column. For instance when Enum is of type Novel, the
record should be mapped to Novel class and so on. Linq To Sql also requires
me to define a class that should be considered the default mapping in the case
where a value does not match to any concrete derived class. You enable the
default derived class by setting isDefault to true.
public class TecnnicalBook:Book
{
[Column]
public string Technology { get; set; }
}
Console.WriteLine("All Books");
foreach (var book in db.Books)
{
Console.WriteLine(book.Title );
}
The above example made use of attributes to translate columns defined in the
table to properties defined in your entities. If attributes removes the clarity of
the class, you can define the mapping in separate file and load the mapping in
the constructor of the datacontext. Mapping file below shows how we can
map Books table defined in the database to various derived entities defined in
our project.
<Database Name="Ecommerce"
xmlns="http://schemas.microsoft.com/linqtosql/dbml/2007">
<Table Name="dbo.Book">
<Type Name="Book">
<Column Name="BookId" Type="System.Int32"
Member="BookId" IsPrimaryKey="true" IsDbGenerated="true"/>
<Column Name="BookAuthor" Type="System.String"
Member="BookAuthor" />
<Column Name="Type" Type="System.Int32"
Member="Type" IsDiscriminator="true" />
<Type Name="TecnnicalBook" InheritanceCode="1"
IsInheritanceDefault="true">
<Column Name="Technology"
Type="System.String" Member="Technology" />
<Type Name="CertificationBook"
InheritanceCode="2">
<Column Name="Exam"
Type="System.String" Member="Exam"/>
</Type>
</Type>
<Type Name="CookingBook" InheritanceCode="3">
<Column Name="ReceipesAvailable"
Type="System.Boolean" Member="ReceipesAvailable"/>
</Type>
<Type Name="Novel" InheritanceCode="4">
<Column Name="TrueStory"
Type="System.Boolean" Member="TrueStory"/>
</Type>
</Type>
</Table>
</Database>
To load the mapping file, you can use XmlMappingSource class and call
FromUrl specifying the name of the file which contains the mapping.
Given this fact that EF cannot retrieve the CustomerId value after the insert,
when you try to use the model as it is you get the following error.
Server generated keys are only supported for identity columns. Key column has type
'SqlServer.uniqueidentifier' which is not a valid type for an identity column
1. Import the above customer table into edm using EDM import wizard.
2. Since CustomerId value is generated on the database we need to modify
the customer entity on the ssdl to indicate that. Code below shows the
change required on the CustomerId column on the ssdl layer.
<Property Name="CustomerId" Type="uniqueidentifier"
StoreGeneratedPattern="Identity" Nullable="false" />
This tells EF that database is responsible for assigning the value to the
CustomerId column.
3. As discussed earlier, EF has no way of knowing how to retrieve the
generated customerId column from the database. Hence we need to
create insert, update and delete stored procedure that can insert
customer entity into the databse. Code below shows the insert stored
procedure required.
end
On the above stored procedure to grab the customerid inserted, I am
using output clause that is available on sql server 2005 or above to get
the last record inserted into customer table. From the last record I
simply grab the CustomerId and insert it into insertedrows customer
table. Since EF requires that identity columns be return using a select
statement, I am select CustomerId column from insertedrows table.
The update and delete stored procedures does not have any
complications because we need to generate CustomerId value once and
that happens during the insert process. Code below shows the update
and delete stored procedures.
create proc sr.UpdateCustomer
(@Name varchar(50),@Email varchar(50),@CustomerId uniqueidentifier)
as
begin
update sr.Customer
set Name = @Name,Email = @Email where CustomerId = @CustomerId
end
The update stored procedure updates Name and Email property of the
Customer record based on the CustomerId guid passed in the
parameter. Similarly delete procedure takes in a customerid and deletes
the customer record from the customer table.
4. Import insert, update and delete stored procedures created above into
the edm using Update Model from database wizard. Since the update
wizard overwrites everything on the ssdl, the changes we made to
customerid column also got overwritten. Make sure CustomerId
column is once again marked as identity.
5. Map the insert function to InsertCustomer stored procedure, update
function to UpdateCustomer and delete function to DeleteCustomer
stored procedure. Notice that for insert stored procedure there is
Resultbinding where I am grabbing the CustomerId column returned
from select statement in the stored procedure to CustomerId property
on the customer entity.
Email = "[email protected]",
Name = "Zeeshan"
};
db.AddToCustomers(customer);
db.SaveChanges();
Console.WriteLine(customer.CustomerId.ToString("D"));
//update teh contact
customer.Name = "Zeeshan Hirani";
db.SaveChanges();
db.DeleteObject(customer);
db.SaveChanges();
On the above code after inserting the customer entity I am printing the
CustomerId on the console window to confirm that we have retrieve the
correct CustomerId guid generated by the database. Figure below
shows the results from the console window.
Solution: EF does not have any out of box support for xml data type in the
version 1 release. When we import the above table structure into edm,
Resume column would be imported as string data type. To use the Resume
column as an xml data type, make sure that Resume property is marked
private and then create partial class for Candidate entity and create a property
CandidateResume that reads the private Resume property and returns the data
as xml. Similarly the setter portion of the CandidateResume would write the
property back to resume property created on entity model.
Discussion: Entity framework does not import xml data type as xml. When
we import a table containing xml data type, entity framework will map the
column as string data type on the conceptual model. It is then left to the
developer to convert string back to xml for use in the application. Similarly
on saving the xml data type needs to be converted back to string because
entity framework does not allow saving an xml type to string. Steps below
outline the process of consuming xml data from the database, updating the
xml and making sure that the updated xml is saved to the database.
1. Import the candidate table using Entity Data Model Wizard. Change the
getter and setter for Resume property to be private because we want to
consume Resume as xml instead of simple string as created by the
designer.
Figure below shows the Candidate entity on edm designer.
2. Create a partial class Candiate that has a property CandidateResume
that returns Resume as xml. The setter for the property should be able
to get the xml and write it to the information back to Resume property
as string. Code below shows the Candidate partial class.
On the above code, I have local xml variable that reads the Resume by
using XElement.Parse. In addition I am also registering with the change
event on the xml and every time a change happens on the xml, I am
assigning the new xml back to resume property as resume property is
the one that will be persisted by entity framework. For the setter I am
using ToXml on the xml passed in and assigning it to Resume string
property.
To test the model we can try reading the resume property and updating
portion of the xml content and then saving the entity to the database.
Then using a second datacontext, we retrieving the candidate and read
the candidate’s resume to confirm that our updates on the Resume
column got successfully updated in the database. Code shows an
example of that.
db.SaveChanges();
The above supplier table has several fields whose value is generated by the
database when a new record is inserted or updated. CreateDate column is
assigned the current date by a trigger that gets fired on an insert of a new
supplier in the supplier table. Once CreateDate is assigned a value, it’s never
updated. ModifiedDate is updated to current date using a trigger that is fired
when a row in updated in supplier’s table. Timestamp is assigned a new value
by database engine when a new supplier is inserted or updated.
SupplierId, the primary key of the table is also generated using identity
column and is only assigned when a new supplier is inserted into the supplier
table.
We want to ensure that when a new supplier entity is inserted using Entity
Framework, the CreateDate, modifiedDate, TimeStamp and SupplierId
property should reflect the updated values from the database.
1. None
2. Identity
3. Computed
None is the default option which indicates that it is not a server generated value.
Setting property to identity means that value is generated once on an insert and any
updates on the entity should not affect this value. So in our case SupplierId and
CreateDate are assigned value once on an insert and therefore needs to be marked
with StoreGeneratedPatten equal to identity. When we import the Supplier table
EF will see that SupplierId is an identity column and make sure
StoreGeneratedPattern is set to identity. Since CreateDate is assigned a value by
insert trigger, EF has no way of knowing that it is a server generated value unless
we manually go in the ssdl section of the edmx and change it to identity. The last
option is Computed which means that server will always generate a new value for
the column and EF should refresh the column with new value on both insert and
updates. In supplier’s table ModifiedDate is assigned a new value on update and
therefore StoreGeneratedPattern needs to set to Computed to ensure that we
receive the new value after supplier entity is successfully updated. The Timstamp
property on supplier entity in SSDL should also be set to computed because EF is
aware of timestamp column as being server generated.
Note:
If primary key of the table is generated using other means such as default value, or
insert trigger, EF cannot retrieve the inserted value even if the property is set to
identity. The reason is sql server 2000 has no way of retrieving primary key value
if column is not marked as identity column. Although with Sql server 2005 you can
use output clause to access the inserted row and grab the primary key value from
the inserted row. This feature is not supported in version 1 of EF. To see how to
solve this problem in version 1, see section 8.1
Code below shows the insert and update trigger for Supplier table.
Insert trigger
ALTER trigger [sr].[td_SupplierInsert] on [sr].[Supplier]
for insert
as
update supplier
set createdate = getdate()
from supplier join inserted i on supplier.SupplierId = i.SupplierId
Update Trigger
ALTER trigger [sr].[td_SupplierUpdate] on [sr].[Supplier]
for update
as
update supplier
set modifiedDate = getdate()
from supplier join inserted i on supplier.SupplierId = i.SupplierId
On Insert trigger, createDate is assigned the current date and time using Getdate
function. Similarly Update trigger, assigns the current time and date to
ModifiedDate column.
Import Supplier table using EDM wizard and modify the ssdl so that CreateDate
has StoreGeneratedPattern set to Identity and modifiedDate is set to Computed.
Code below shows the correct settings for Supplier entity on the ssdl.
<EntityType Name="Supplier">
<Key>
<PropertyRef Name="SupplierId" />
</Key>
<Property Name="SupplierId" Type="int" Nullable="false"
StoreGeneratedPattern="Identity" />
<Property Name="SupplierName" Type="varchar" Nullable="false"
MaxLength="50" />
<Property Name="CreateDate" Type="date"
StoreGeneratedPattern="Identity" />
<Property Name="modifiedDate" Type="datetime"
StoreGeneratedPattern="Computed" />
<Property Name="TimeStamp" Type="timestamp" Nullable="false"
StoreGeneratedPattern="Computed" />
</EntityType>
Code below creates a supplier entity, saves the entity to the database and then
updates the entity. To test that we have the correct create date and modified date, at
each stage we are printing the values for CreateDate and ModifiedDate on the
console window.
var db = new StComputedEntities();
var supplier = new Supplier
{
SupplierName = "Exotic Liquids"
};
db.AddToSupplier(supplier);
db.SaveChanges();
Console.WriteLine();
Console.WriteLine("supplier insert details");
Console.WriteLine("Create Date:{0}",
supplier.CreateDate.Value.ToString());
Console.WriteLine("Modified Date:{0}",
supplier.modifiedDate.Value.ToString("hh:mm:ss"));
Thread.Sleep(TimeSpan.FromSeconds(3));
supplier.SupplierName = "Exotic Liquids Ltd.";
db.SaveChanges();
Console.WriteLine("Update Details");
Console.WriteLine("Modified Date:{0}",
supplier.modifiedDate.Value.ToString("hh:mm:ss"));
Screenshot below shows Create and modifiedDate printed on the console window.
Listing 1-1
[EdmEntityTypeAttribute
(NamespaceName="LinqCookBook.EFUsingPOCO",Name="Customer")]
public class Customer : IEntityWithChangeTracker,
IEntityWithKey,IEntityWithRelationships
{
#region changetracker and entity key
//code removed for keeping the exmaple short.
#endregion
RelationshipManager manager;
public RelationshipManager RelationshipManager
{
get
{
if (manager == null)
{
manager =
RelationshipManager.Create(this);
}
return manager;
}
}
[EdmRelationshipNavigationPropertyAttribute
("LinqCookBook.EFUsingPOCO","CustomerOrders","Orders")]
public EntityCollection<Order> Orders
{
get
{
return RelationshipManager
.GetRelatedCollection<Order>("LinqCookBook.EFUsingPOCO.Custo
merOrders", "Orders");
}
}
}
Now that we are using CustomerOrders relationship name and roles defined
within it, we need to define CustomerOrders in our conceptual modal. Listing
1-2 shows example of CustomerOrders relationship defined. To declare
CustomerOrders relationship, we create an AssociationSet element giving the
Name of CustomerOrders and Association as the fully qualified name. Inside
the associationset we define two roles that map to Customer and Orders
entityset declared at top. It is important to understand that associationset only
defines which entitysets participates in the relationship. It is the Association
element that defines how each role or entitysets relate to each other. Like in
CustomerOrders association, Customer has a multiplicity of 0 to 1 and Orders
has a multiplicity of many. What this means is a customer can have many
Orders. So far we have only described what entities participates in a
relationship by using Roles and how those roles relate to each other using
Multiplicity. The next step we do is expose a navigation property on
CustomerEntity by using NavigationProperty Element as shown in Listing 1-
2. Inside the navigation, we are specifying from which relationship we want
to go from which in Customer case, we want to go from Customers to Orders.
Listing 1-2
<Schema Namespace="LinqCookBook.EFUsingPOCO" Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="NorthwindEntities">
<EntitySet Name="Customers"
EntityType="LinqCookBook.EFUsingPOCO.Customer" />
<EntitySet Name="Orders"
EntityType="LinqCookBook.EFUsingPOCO.Order" />
<AssociationSet Name="CustomerOrders"
Association="LinqCookBook.EFUsingPOCO.CustomerOrders">
<End Role="Customer" EntitySet="Customers" />
<End Role="Orders" EntitySet="Orders" />
</AssociationSet>
</EntityContainer>
<Association Name="CustomerOrders">
<End Role="Customer"
Type="LinqCookBook.EFUsingPOCO.Customer" Multiplicity="0..1"
/>
<End Role="Orders"
Type="LinqCookBook.EFUsingPOCO.Order" Multiplicity="*" />
</Association>
<EntityType Name="Customer">
<!-- code removed for clearity-->
<NavigationProperty Name="Customer"
<NavigationProperty Name="Orders"
Relationship="LinqCookBook.EFUsingPOCO.CustomerOrders"
FromRole="Customer" ToRole="Orders" />
</EntityType>
</Schema>
The next step after defining the relationship is to map the relationship in the
mapping file to tables. Listing 1-3 shows the mapping of CustomerOrders
Association Set. In the assocationSetMapping we define the association we
want to map followed by mapping the roles within each association. For
Customer role, we are mapping CustomerID property to CustomerId column.
For Orders role, we are mapping OrderId property to OrderID column. Since
every order will always have a customer, we also need to add a conditional
attribute stating that Column CustomerID cannot be null for this relationship
mapping.
Listing 1-3
<Mapping Space="C-S" xmlns="urn:schemas-microsoft-
com:windows:storage:mapping:CS">
<EntityContainerMapping
StorageEntityContainer="dbo"
CdmEntityContainer="NorthwindEntities">
<EntitySetMapping Name="Customers">
<!-- code removed for clearity-->
</EntitySetMapping>
<EntitySetMapping Name="Orders">
<!-- code removed for clearity-->
</EntitySetMapping>
<AssociationSetMapping Name="CustomerOrders"
TypeName="LinqCookBook.EFUsingPOCO.CustomerOrders"
StoreEntitySet="Orders">
<EndProperty Name="Customer">
<ScalarProperty Name="CustomerID"
ColumnName="CustomerID"/>
</EndProperty>
<EndProperty Name="Orders">
<ScalarProperty Name="OrderID"
ColumnName="OrderID" />
</EndProperty>
<Condition ColumnName="CustomerID" IsNull="false"
/>
</AssociationSetMapping>
</EntityContainerMapping>
</Mapping>
For relationship navigation to work properly, you have to define the same
relationship that we defined in conceptual model, be defined at assembly level
as follows.
[assembly: EdmRelationshipAttribute
("LinqCookBook.EFUsingPOCO","CustomerOrders",
"Customer",
RelationshipMultiplicity.ZeroOrOne,
typeof(LinqCookBook.EFUsingPOCO.Customer),
"Orders",RelationshipMultiplicity.Many,
typeof(LinqCookBook.EFUsingPOCO.Order))]
The edmrelationship attribute defines how each role related to the other role
using multiplicity as we defined in our conceptual model. If you do not have
this attribute at the assembly level, you will get runtime error with exceptions
that association cannot be found.
Now to test our code, we can write a query against our model. In the code
below, we are getting customers with a contact title of Sales Representative
and than for each Cutomer loading its Orders and printing the total Orders for
each Customer.
public static void CustomerWithOrders()
{
var db = new NorthwindEntities();
var custs = db.Customers.Where(c =>
c.ContactTitle == "Sales Representative");
foreach (var cus in custs)
{
cus.Orders.Load();
Console.WriteLine("cust {0}
OrderCount:{1}",cus.CustomerID,cus.Orders.Count());
}
}
("LinqCookBook.EFUsingPOCO.CustomerOrders", "Customer");
if (!efreference.IsLoaded)
{
efreference.Load();
}
return efreference.Value;
}
}
Next we need to add Navigation element on the Order entity defined in the
conceptual modal with a role going from Order to Customer as shown below.
<NavigationProperty Name="Customer"
Relationship="LinqCookBook.EFUsingPOCO.CustomerOrders"
FromRole="Orders" ToRole="Customer" />
Solution: To update the start and end date for a show when a new timing gets
added or removed from EventTimings collection, you have to register with
AssociationChanged for EventTimings Collection. This collection exposes
two objects necessary to perform your business activity. First object, Action
represents the type of action performed on the collection. When you add event
timing to EventTimings collection, associationChanged event gets triggered
and the action is set to Add. If you remove event timing from EventTimings
collection, the action is set to Remove. The second object passed is the entity
that got removed or added from the collection. Using the entity passed in, you
have the ability to check for business rules and determine if this insert or
remove is valid operation otherwise throw exception. Below code shows to
use association changed event.
public partial class TradeShow
{
/// <summary>
/// creates a trade show with name and different
timings the show is happenning at.
/// </summary>
public static void CreateTestShow()
{
var db = new EcommerceEntities();
TradeShow show = new TradeShow
{
Name = "ATLANTIC DESIGN MANUFACTURING SHOW",
ShowInfo = "ATLANTIC DESIGN MANUFACTURING
SHOW description",
Location = "New York",
};
db.AddToTradeShows(show);
db.SaveChanges();
//add timings would fire association changed
event.
show.EventTimings.Add( new EventTiming{EventDate
= DateTime.Parse("8/4/2008"),Timing="8:00AM-4:00PM"});
show.EventTimings.Add(new EventTiming {
EventDate = DateTime.Parse("8/5/2008"), Timing = "9:00AM-
4:00PM" });
show.EventTimings.Add(new EventTiming {
EventDate = DateTime.Parse("8/6/2008"), Timing = "10:00AM-
3:00PM" });
db.SaveChanges();
}
/// <summary>
/// update the trade show by removing one of the
timings.
/// this would trigger Association changed event
causing start and end date
/// for the show to update itself from its
EventTiming collection.
/// </summary>
public static void UpdateTradeShow()
{
var db = new EcommerceEntities();
//get gunshow by name and also eventimings
collection as well.
var show = db.TradeShows
.Include("EventTimings")
.First(s => s.Name == "ATLANTIC
DESIGN MANUFACTURING SHOW");
//just remove the first available timing in the
collection
var firstavailabletiming =
show.EventTimings.First();
db.DeleteObject(firstavailabletiming);
//show.EventTimings.Remove(firstavailabletiming);
//removing it from the collection is not enought
you have to also
//delete eventiming as well.
db.SaveChanges();
}
public TradeShow()
{
this.EventTimings.AssociationChanged += (sender,
e) =>
{
if (!this.EventTimings.IsLoaded)
{
this.EventTimings.Load();
}
if (e.Action ==
CollectionChangeAction.Add)
{
this.UpdateTimings();
}
else if (e.Action ==
CollectionChangeAction.Remove)
{
if (this.EventTimings.Count() > 0)
{
this.UpdateTimings();
}
else
{
this.StartDate =
DateTime.MaxValue;
this.EndDate =
DateTime.MaxValue;
}
}
};
}
void UpdateTimings()
{
this.StartDate = this.EventTimings.Min(ev =>
ev.EventDate);
this.EndDate = this.EventTimings.Max(ev =>
ev.EventDate);
}
}
Discussion: To start the discussion let’s start with looking at how our tables
look like in the database and how they are related to each other.
In the above diagram, we have a tradeshow table where we capture the
name,StartDate, EndDate and Location of the tradeshow. We have another
table EventTimings that captures the various days’ timings for a particular
show. Although we could use EventTiming table to find out the span of days
for a certain show at runtime, by having StartDate and EndDate on
TradeShow table it allows us to not have to join against another table and also
makes search queries less complicated. Now that we have our tables in place
we can use the entity framework wizard to generate entity classes and register
for Association Changed event in our partial class as show below.
public TradeShow()
{
this.EventTimings.AssociationChanged += (sender,
e) =>
{
if (!this.EventTimings.IsLoaded)
{
this.EventTimings.Load();
}
if (e.Action ==
CollectionChangeAction.Add)
{
this.UpdateTimings();
}
else if (e.Action ==
CollectionChangeAction.Remove)
{
if (this.EventTimings.Count() > 0)
{
this.UpdateTimings();
}
else
{
this.StartDate =
DateTime.MaxValue;
this.EndDate =
DateTime.MaxValue;
}
}
};
}
In the above example, I am registering with association changed event for the
eventTimings collection in the constructor of the TradeShow class. I am using
an inline lambda expression so that I could have my code next to the changed
event. If your collection changed event is not going be using any instance
level variables than you may be better off using a static method to improve
performance since you will not have a method per instance of TradeShow
class. AssociationChanged event is fired after the collection has been
modified which means that if you remove or add event timing, inside the
AssocitionChanged event, accessing the EventTimings collection will reflect
the updated collection with additions and deletions of the timings
If the action passed to the collection changed event is remove, meaning event
timing was removed from the collection, I am checking to see if there are any
event timings available in the EventTimings collection by using Count. If
Eventtimings collection is empty, I set the startdate and endDate of tradeshow
to be the default value which is DateTime.Max. If EventTiming Count is
greater than 0, I am calling UpdateTiming method to update the show timing
with the update data available in the EventTimings collection.
We saw how our association changed event got fired with when adding new
timings to the event Timings collection. Let’s create a simple example where
we remove timing from eventtimings collection. Example below shows the
code where we remove event timing.
/// <summary>
/// update the trade show by removing one of the
timings.
/// this would trigger Association changed event
causing start and end date
/// for the show to update itself from its
EventTiming collection.
/// </summary>
public static void UpdateTradeShow()
{
var db = new EcommerceEntities();
//get gunshow by name and also eventimings
collection as well.
var show = db.TradeShows
.Include("EventTimings")
.First(s => s.Name == "ATLANTIC
DESIGN MANUFACTURING SHOW");
//just remove the first available timing in the
collection
var firstavailabletiming =
show.EventTimings.First();
db.DeleteObject(firstavailabletiming);
//show.EventTimings.Remove(firstavailabletiming);
//removing it from the collection is not enought
you have to also
//delete eventiming as well.
db.SaveChanges();
}
Solution: There are various operations that you can perform which can trigger
the collection changes. For instance adding item to collection, removing item
from a collection, refreshing the collection, retrieving the collection from the
database, attaching item to the datacontext, attaching an item to the collection
directly, detaching item from the datacontext and marking an item for
deletion. Code below shows various examples when an association changed
event fires and what kind of action is reported by CollectionChangedEvent
arguments.
public static void TestForAssociationChangedEvents()
{
db.ObjectStateManager.GetObjectStateEntry(LAZYKorder).Delete
();
}
1. Add
2. Remove
3. Refresh
Listing 1-1
Console.WriteLine("Include in the query raises an action of
add");
var THECR =
db.Customers.Include("Orders").First(c => c.CustomerID ==
"THECR");
In Listing 1-1, I am loading customer and its orders at the same time. Once
the query is performed the orders retrieved would be attached to the orders
collection of the customers. This result in an action of Add being raised equal
to the number of Orders the customer collection has. Using Include in a query
is process called eager loading where you want to fetch additional entities
with parent entity.
Listing 1-2
var LAZYK = db.Customers.First(c => c.CustomerID ==
"LAZYK");
In listing 1-2, we are calling load method to lazy load Orders for a customer.
This raises collection changed event with action property set to Refresh. You
would normally perform Load when you want to refresh the collection from
the database. Refresh event is a batch event which gets raised only once and
after the entire collection is refreshed from the store.
Listing 1-3
var LAZYK = db.Customers.First(c => c.CustomerID ==
"LAZYK");
Listing 1-4
Console.WriteLine("Calling Removes fires an action of
remove");
var THECRorder = THECR.Orders.First();
THECR.Orders.Remove(THECRorder);
In listing 1-4, I am getting the first order found for the customer THECR.
After getting the reference to the order, I am removing the order from the
orders collection of the customer by passing the reference to the order object.
This action causes the orders collection to be altered causing
AssociationChanged event to fire with an action of Remove. If I want to
remove all the orders for a customer, you can call clear on the Orders
collection. This causes an AssociationChanged event to get triggered. Since
the operation performed is at the collection level, the event gets raised only
once and the action property is set to refresh.
Listing 1-6
Console.WriteLine("Calling Add on Orders Collection causes
an action of Add.");
THECR.Orders.Add(new Order { ShipCity =
"London", ShipCountry = "UK", OrderDate = DateTime.Today });
Listing 1-7
var LAZYKorder = db.Orders.First(o => o.OrderID == 10482);
Console.WriteLine("Detaching an order causes the
order to be removed from customer's order collection");
db.Detach(LAZYKorder);
Console.WriteLine("Calling Attach causes an
action of Add if the customer is already being tracked");
db.Attach(LAZYKorder);
Listing 1-8
Listing 1-9
//Assigning HANARorder from customer THECR to LAZYK
Console.WriteLine("Assigning customer instance
on existing order causes action of remove and add");
HANARorder.Customer = LAZYK;
In listing 1-9, I am setting the customer object on an existing order to point to
a new customer that is already being tracked in the datacontext. This is an
action where we are indicating the order belongs to a new customer instead of
old customer. This causes assocaitionchanged event to fire twice first with an
action of remove to remove the order from the old customer, and preceded by
Add to add the order to the new customer.
Listing 1-10
Console.WriteLine("Calling DeleteObject causes
an action of remove if customer is being tracked.");
db.DeleteObject(HANARorder);
db.ObjectStateManager.GetObjectStateEntry(LAZYKorder).Delete
();
In listing 1-10, I am marking an order for deletion. Since the order belongs to
a customer that is also tracked by the datacontext, this causes the order to be
removed from the customer’s orders collection. On removing the order from
customer’s order collection, association changed event is fired with an action
of Remove. You also have the ability to access the objectstateentry from
objectstatemanager that is managing the Order tracking by calling
GetObjectStateEntry passing in the reference to the order object. After
getting the reference to the ObjectStateEntry, you can delete the entry which
also causes AssociationChanged event to fire with an action of remove.
Problem: You have customer and employee table that contains address
information. In entity data model you would like to structure the address
information into its own class that can be reused with both customer and
employee’s address.
Solution: Entity framework supports the concept of Complex Type that can
group related properties in a single composite class. For instance city, country
and zip can appear as address information for multiple tables. Instead of
declaring those fields as properties for every entity, you can create a single
complex type Address that contains all these fields and reuse the Address
object with various other entities that contains address related info. To
consume a complex type in entity framework, you have to define the complex
type in the conceptual model and then use the complex type as the property
for the instances that contains address information. After defining the
conceptual modal, you have to map each complex defined on an entity to the
columns defined in the store. Example below shows how schema required to
use conceptual model.
Conceptual Modal
<?xml version="1.0" encoding="utf-8" ?>
<Schema Namespace="NWComplexTypeModel" Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="NWComplexTypeEntities">
<EntitySet Name="Employees"
EntityType="NWComplexTypeModel.Employee" />
<EntitySet Name="Customers"
EntityType="NWComplexTypeModel.Customer" />
</EntityContainer>
<EntityType Name="Employee">
<Key>
<PropertyRef Name="EmployeeID" />
</Key>
<Property Name="EmployeeID" Type="Int32"
Nullable="false" />
<Property Name="LastName" Type="String"
Nullable="false" />
<Property Name="FirstName" Type="String"
Nullable="false" />
<Property Name="Address"
Type="NWComplexTypeModel.CommonAddress" Nullable="false" />
</EntityType>
<EntityType Name="Customer">
<Key>
<PropertyRef Name="CustomerID"/>
</Key>
<Property Name="CustomerID" Type="String"
Nullable="false" />
<Property Name="CompanyName" Type="String"
Nullable="false" />
<Property Name="Address"
Type="NWComplexTypeModel.CommonAddress" Nullable="false" />
</EntityType>
<ComplexType Name="CommonAddress">
<Property Name="Address" Type="String" />
<Property Name="City" Type="String" />
<Property Name="Region" Type="String" />
<Property Name="PostalCode" Type="String" />
<Property Name="Country" Type="String" />
</ComplexType>
</Schema>
CdmEntityContainer="NWComplexTypeEntities">
<EntitySetMapping Name="Customers">
<EntityTypeMapping
TypeName="NWComplexTypeModel.Customer">
<MappingFragment StoreEntitySet="Customers">
<ScalarProperty Name="CustomerID"
ColumnName="CustomerID" />
<ScalarProperty Name="CompanyName"
ColumnName="CompanyName" />
<ComplexProperty Name="Address"
TypeName="NWComplexTypeModel.CommonAddress">
<ScalarProperty Name="Address"
ColumnName="Address" />
<ScalarProperty Name="City"
ColumnName="City" />
<ScalarProperty Name="Region"
ColumnName="Region" />
<ScalarProperty Name="PostalCode"
ColumnName="PostalCode" />
<ScalarProperty Name="Country"
ColumnName="Country"/>
</ComplexProperty>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="Employees">
<EntityTypeMapping
TypeName="NWComplexTypeModel.Employee">
<MappingFragment StoreEntitySet="Employees">
<ScalarProperty Name="EmployeeID"
ColumnName="EmployeeID" />
<ScalarProperty Name="LastName"
ColumnName="LastName" />
<ScalarProperty Name="FirstName"
ColumnName="FirstName" />
<ComplexProperty Name="Address"
TypeName="NWComplexTypeModel.CommonAddress">
<ScalarProperty Name="Address"
ColumnName="Address" />
<ScalarProperty Name="City"
ColumnName="City" />
<ScalarProperty Name="Region"
ColumnName="Region" />
<ScalarProperty Name="PostalCode"
ColumnName="PostalCode"/>
<ScalarProperty Name="Country"
ColumnName="Country" />
</ComplexProperty>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
In the above definition of the complex type, we are declaring scalar value
properties such as Address, City, Region, Postal code and Country. A
complex property can have other complex properties within it. Currently in
the v1 release of the entity framework, Complex Type cannot contain
navigation properties such as entity refs and entity collection. Since complex
type has no notion of entity key or has an identity of its own, it cannot be
tracked in the object state manager. Complex types cannot stand alone and it
has to be associated to an entity to be useful. Even though the identity of
complex type is tied to identity of the entity it is part of, you can still put
general validation rules on the complex type that will affect all entities using
the at complex type.
After creating the complex type in the conceptual model, we need to map the
property on the entity that exposes complex type to columns defined on our
table. Because the mapping is pretty much same for both Customer and
Employee, I will just cover the mapping for Customer.
<EntitySetMapping Name="Customers">
<EntityTypeMapping
TypeName="NWComplexTypeModel.Customer">
<MappingFragment StoreEntitySet="Customers">
<ScalarProperty Name="CustomerID"
ColumnName="CustomerID" />
<ScalarProperty Name="CompanyName"
ColumnName="CompanyName" />
<ComplexProperty Name="Address"
TypeName="NWComplexTypeModel.CommonAddress">
<ScalarProperty Name="Address"
ColumnName="Address" />
<ScalarProperty Name="City"
ColumnName="City" />
<ScalarProperty Name="Region"
ColumnName="Region" />
<ScalarProperty Name="PostalCode"
ColumnName="PostalCode" />
<ScalarProperty Name="Country"
ColumnName="Country"/>
</ComplexProperty>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
Current release of entity designer does not support complex types. If you
manually change the conceptual and mapping model you will lose the ability
to re open the edmx file in the designer.
To re emphasize, Complex types are not same as entities. Complex types do
not have independent identifiers and is associated as a property on an entity
that is tracked by entity framework. Given this fact, you cannot expose any
ends of an association or relation as a complex type because complex type has
no key and therefore cannot be tracked separately. In future versions of entity
framework, a complex type may be allowed to be part of an association.
After defining the conceptual, mapping and store model, you can run edmgen
utility to generate your class files which will generate 3 classes, Customer,
Employee and Object Context that exposes your entity set. Looking at
generated code for Common Address Complex, you will see that it inherits
from Complex Object. Complex Object is an object with no key as compared
to Entity Object from which entity derives that uses entity key to track an
entity. After generating the model, we can use the complex type in our query,
read values from complex type and update columns in our table by modifying
values on our complex type property. Example below shows various usages
of complex type with insert and read scenarios.
To insert a customer and its complex type, you simply create an instance of
Customer object and nested within it you can create an instance of
CommonAddress object assigning it to address property of the customer. You
can use object initialize syntax to assign values to Address object. Example
below shows how to create a customer and Address object and then adding
the object to the object context followed by submitchanges.
//insert customer
var customer = new NWComplexTypeModel.Customer
{
CustomerID = "COML1",
CompanyName = "Test Company",
Address = new CommonAddress
{
Address = "address 1",
City = "Euless",
Country="USA"
}
};
db.AddToCustomers(customer);
db.SaveChanges();
Although in the above example, you explicitly created an instance of
Common Address, you do not have to necessarily do so because accessing the
address object first time will ensure the instantion of CommonAddress object.
Then all you have left to do is assign values to the scalar properties of the
CommonAddress class. Code below shows assigning values to address
property without creating an instance of Common Address class.
//insert employee
var employee = new Employee
{
FirstName = "Zeeshan",
LastName = "Hirani"
};
employee.Address.Address = "address 1";
employee.Address.City = "Euless";
employee.Address.Country = "USA";
db.AddToEmployees(employee);
db.SaveChanges();
You can also use Complex Types in queries to find a match with a specific
value defined on the complex type. Code below retrieves the first customer
with a city of Euless and country of USA. To specify the expression filter, I
am navigating the Address object and accessing its properties.
As I talked earlier, you can also nest complex types with in a complex type.
Example below shows CommonAddress a complex type has property
AdditionalInfo that maps to Geography class that is also a complex type.
Mapping the nested complex is also same where further column mapping are
performed inside AdditionalInfo property which is nested inside of Address
property.
CSDL
<ComplexType Name="CommonAddress">
<Property Name="Address" Type="String" />
<Property Name="Region" Type="String" />
<Property Name="AdditonalInfo" Nullable="false"
Type="NWNestedComplexTypeModel.Geography" />
</ComplexType>
<ComplexType Name="Geography">
<Property Name="City" Type="String" />
<Property Name="PostalCode" Type="String" />
<Property Name="Country" Type="String" />
</ComplexType>
Mapping
<ComplexProperty Name="Address"
TypeName="NWNestedComplexTypeModel.CommonAddress">
<ScalarProperty Name="Address"
ColumnName="Address" />
<ScalarProperty Name="Region"
ColumnName="Region" />
<ComplexProperty Name="AdditonalInfo"
TypeName="NWNestedComplexTypeModel.Geography">
<ScalarProperty Name="City"
ColumnName="City" />
<ScalarProperty Name="PostalCode"
ColumnName="PostalCode" />
<ScalarProperty Name="Country"
ColumnName="Country"/>
</ComplexProperty>
</ComplexProperty>
Similarly you can use nested complex type inside of a query to filter the
results. Code below searches for customer with address that contains Obere
and country of Germany. To do this we are navigating both levels of the
complex query to apply the filter.
var db = new NWNestedComplexTypeModel.NWComplexTypeEntities();
var cus = db.Customers.First(c =>
c.Address.Address.Contains("Obere Str") &&
c.Address.AdditonalInfo.Country == "Germany");
Console.WriteLine(cus.Address.AdditonalInfo.City);
Problem: Figure below shows EDM model for Employee and customer using
Table Per Hierarchy inheritance.
When you access the generated objectcontext, there is only Persons
ObjectQuery exposed. You want the ability to directly access any derived
type of person such customer, employee, club member etc.
Discussion: To use the default option where the base entity and the derived
types are part of the same entityset, we need to create a partial class for the
ObjectContext and add properties that return each derived type from the
Person entityset. Code below shows partial class for the ObjectContext that
has properties to return each derived entities.
public partial class EmployeeTPHTPT
{
public ObjectQuery<Customer> Customers
{
get{return this.Persons.OfType<Customer>();}
}
public ObjectQuery<Employee> Employees
{
get { return this.Persons.OfType<Employee>(); }
}
public ObjectQuery<ClubMember> ClubMembers
{
get { return this.Persons.OfType<ClubMember>(); }
}
public ObjectQuery<HourlyEmployee> HourlyEmployees
{
get { return this.Persons.OfType<HourlyEmployee>(); }
}
public ObjectQuery<SalariedEmployee> SalariedEmployees
{
get { return this.Persons.OfType<SalariedEmployee>(); }
}
}
On the above article table, AuthorInfo, Summary and Content are fairly large
field which are not always require when querying for article record. You want
to be able to delay the load of these fields unless you specifically ask to have
these fields loaded.
Solution: Unlike linq to sql, EF does not support the concept of delay loading
certain properties on an entity. But Ef does support the concept of delay
loading an association on an entity. To solve the above problem we can create
ArticleDetail entity and move the expensive fields to ArticleDetail entity.
Then create 1 to 1 association between article and Article Detail entity. Now
when we retrieve Article entity, we would be only retrieving properties on the
Article entity and unless we use Include operator or explicitly call load on
ArticleDetail, we won’t be fetching additional fields defined on ArticleDetail
entity. The completed EDM model should like below
Discussion: Mapping a single table to multiple entities is not fully supported
in the designer. We can start with the designer but have to add Referential
constraint on the conceptual model manually by editing the xml. The next
version of Ef will fully support this scenario not requiring you to edit the
edmx file manually. Steps below outline the process of moving over
expensive fields to ArticleDetail entity.
6. Open edmx file in xml and modify the association definition on the
conceptual model to include referential constraint as shown below.
<ReferentialConstraint>
<Principal Role="Article">
<PropertyRef Name="ArticleId" /></Principal>
<Dependent Role="ArticleDetail">
<PropertyRef Name="ArticleId"
/></Dependent></ReferentialConstraint>
To test the above model we can query for all articles and confirm that query send
to the database only retrieved properties defined on Article entity. Code below
shows an example retrieving articles from the database.
var db = new DelayLoadingEntities();
foreach (var article in db.Articles)
{
Console.WriteLine("Title:{0} Publish Date:{1}",
article.Title, article.PublishedDate.ToString("d"));
}
The sql capture below confirms that querying for Article entity only retrieves the
Title, PublishDate and ArticleId from Article table.
SELECT
1 AS [C1],
[Extent1].[Title] AS [Title],
[Extent1].[PublishedDate] AS [PublishedDate],
[Extent1].[ArticleId] AS [ArticleId]
FROM [dbo].[Article] AS [Extent1]
To bring additional fields on cases when we need it we can use Include operator to
eaglery fetch ArticleDetail entity. Code below shows an example of that.
var db = new DelayLoadingEntities();
foreach (var article in db.Articles.Include("ArticleDetail"))
{
Console.WriteLine("Title:{0} Publish Date:{1} Content:{2}",
article.Title,
article.PublishedDate.ToString("d"),article.ArticleDetail.Content);
}
Sql capture below confirms that having an Include in linq query causes Summary,
Content and AuthorInfo fields to be also brought along with other article fields.
SELECT
1 AS [C1],
[Extent1].[ArticleId] AS [ArticleId],
[Extent1].[Title] AS [Title],
[Extent1].[PublishedDate] AS [PublishedDate],
1 AS [C2],
[Extent1].[AuthorInfo] AS [AuthorInfo],
[Extent1].[Summary] AS [Summary],
[Extent1].[Content] AS [Content]
FROM [rs].[Article] AS [Extent1]
Problem: You want to know the different ways to retrieve an entity using
primary key and what benefits one way offers over the other.
Discussion: If you had been using Linq to Sql, you may be wondering why I
did not mention the use of Single operator. Unfortunately using Single
operator is not supported. If you run a query using Single operator, you would
get an exception stating that Single operator is not supported and consider
using First operator instead. This leaves you with two concrete options First
and GetObjectByKey. In the above example, I am calling First operator
passing in my primary key column value to get the customer. If the query
returns no data, you will get an exception. If you are not sure that your query
will match any record in the database, than you should use FirstOrDefault
operator. Code below shows an example of using FirstORDefault operator.
private void UsingFirstORDefault()
{
var db = new NorthwindEFEntities();
The above code does not raise any exception even though customerid
ABCDE does not exist because we are calling FirstOrDefault. If the query
results do not find a match, you get a null object instead of getting exception.
You can check for null object reference to do you logic.
In Linq to Sql, if you use Single or First operator, it is optimized for caching.
For instance if you retrieve an object using First or Single operator, the object
gets cached in object tracking service. Next time when you query using First
or Single for the same customer, Linq to Sql does not go to database if it finds
the object in the cache. With entity framework, every time you use First
operator, the request will go to the database without looking in the cache.
After the object is retrieved from the database, it will then look in the cache to
see if there is an object being tracked that has the same key that was fetched
from the database. If there is a matching key found in the cache, the object
from the cache is returned instead. If match is not found in the cache, the
object is than stored in the cache for tracking and also returned to the user. In
the code below, I am calling the First operator twice which happens to make
two database calls but when I compare the object references, they are returned
same. This confirms that once an object is stored in the cache, the same object
is always returned.
var db = new NorthwindEFEntities();
var ALFKI = db.Customers.First(c => c.CustomerID == "ALFKI");
//second database call. does not retrieve from cache.
var ALFKI2 = db.Customers.First(c => c.CustomerID ==
"ALFKI");
//object references are same.
Console.WriteLine("Is reference same {0}", ALFKI == ALFKI2);
If you do not want to incur a database hit on subsequent calls when querying
for an entity using primary key, than you can use GetObjectByKey method
available on the ObjectContext class. GetObjectByKey first checks to see if
the object is available in the cache, if the object is found matching the primary
key, object is returned to the user; otherwise a database call is made. Like
First operator, GetObjectByKey also tracks the object after the first call to
GetObjectKey so that later same object can be returned. Similar to first
operator, if query does not return any match, GetObjectByKey will throw
exception. If you know ahead of time that an entity may not exist in the
database for a given primary key value, than you can use
TryGetObjectByKey which returns a null reference if the query does not
return any results. In the code, I am showing how to use GetObjectByKey and
TryGetObjectByKey which returns a null reference since there is no customer
found with customerid ABCDE.
//create an entity key
EntityKey key = new
EntityKey("NorthwindEFEntities.Customers", "CustomerID", "ALFKI");
//if object is found in the cache use that otherwise get it
from database.
var ALFKI2 = db.GetObjectByKey(key) as Customer;
Console.WriteLine("Object retrieved from the cache " +
ALFKI2.CustomerID);
If you retrieve an object using GetObjectByKey and later detach the object
from the datacontext, subsequent call to GetObjectByKey will hit the
database because detaching removes the object from the cache. If you Attach
an object to the context, the object is marked for tracking and therefore
calling GetObjectByKey will not hit the database. Similarly objects marked
for deletion can also be fetched from the cache when using GetObjectByKey
since they are in deleted state but not yet deleted. If you create a new instance
of an object and add the object to ObjectContext, from this point onwards the
object is tracked in the cache. Calling GetObjectByKey will return the object
is Added state. However if you were to query for an object in Added state
using first operator, you will get an exception because the object does not
exist in the database. Code below shows various examples of Using Detach
and Attach, DeleteObject and new customer and how it effects
GetObjectByKey in determining if the query can be fetch from the cache or a
database call must be issued.
var db = new NorthwindEFEntities();
var ALFKI = db.Customers.First(c => c.CustomerID == "ALFKI");
////detaching the object would remove the object from
tracking
////so query will hit the database again.
db.Detach(ALFKI);
var ALFKI3 = db.GetObjectByKey(ALFKI.EntityKey) as Customer;
////if you attach it back again then query would not hit the
database.
db.Attach(ALFKI3);
////does not hit the database
db.GetObjectByKey(ALFKI3.EntityKey);
db.DeleteObject(ALFKI3);
////can retrieve objects in deleted state
db.GetObjectByKey(ALFKI3.EntityKey);
When an entity is retrieved using NoTracking option, EF does not retrieve its
related foreign key reference entity key. For instance to access the customerid
for an order, we can write Order.CustomerReference.EntityKey.Values[0] to
access the customerid. However in the case of NoTracking query,
CustomerReference.EntityKey would be set to null.
Since NoTracking query does not hold the reference to the entity returned
from the object context, identity resolution cannot be performed. This could
have serious side effects in the application. For instance if you retrieved a
customer with id of 1 using NoTracking and then later down made a request
to retrieve the same customer again, you will be returned a completely new
instance of the customer with the same customerId. If you would be doing
object comparisons in your application code to check if two customers are
same, the result would be false although both customers would have the same
entity key.
//using esql
string esql = "select value c from customers as c where
c.CustomerID = 'ALFKI'";
db.CreateQuery<Customer>(esql).Execute(MergeOption.NoTracking).First();
//entity reference
order.CustomerReference.Load(MergeOption.NoTracking);
//entity collection
category.Products.Load(MergeOption.NoTracking);
MergeOption can also be called in cases where you have to either lazy load
collection or an entity reference. For instance in the above example, to retrieve
customer for an order, I am calling Load method. If you do not pass any
MergeOption parameter, the default option is MergeOption.AppendOnly which
essentially means every entity retrieved will be tracked by the
ObjectStateManager. To indicate that we need NoTracking on the query, I am
using overloaded version of the Load method passing in MergeOption.NoTracking.
The same behavior is also available when retrieving collections in a lazy loaded
fashion like in the above case of Products for a category.
On the queries above, we are setting the MergeOption once on the Customers
ObjectQuery. Both queries deriving from ObjectQuery of Customers end up using
the same MergeOption. This is because Customers query reference is cached in
private variable. Code below shows the generated code for Customers query in the
object context
private global::System.Data.Objects.ObjectQuery<Category> _Categories;
/// <summary>
/// There are no comments for Customers in the schema.
/// </summary>
public global::System.Data.Objects.ObjectQuery<Customer> Customers
{
get
{
if ((this._Customers == null))
{
this._Customers =
base.CreateQuery<Customer>("[Customers]");
}
return this._Customers;
}
}
Since new queries are built using the Customers Object Query, all queries
inherit the same mergeOption. If this is not the desired effect, you can create a new
instance of ObjectQuery using CreateQuery method. Code below shows an
example.
var customers = db.CreateQuery<Customer>("Customers");
customers.MergeOption = MergeOption.NoTracking;
customers.Where(c => c.City == "London");
In the above query Customer entity is retrieve using the default MergeOption,
MergeOption.AppendOnly. For retrieving Orders collection, NoTracking option is
used. Since related entities are loaded using different mergeOption, EF throws a
runtime exception.
When a query retrieves an entity with related ends which are entity reference,
EF will rewrite the query to bring all foreign key values in the query. Based on the
foreign key values, EF will create relationship stub for related ends of the entity.
At some later point when the related ends are loaded inside the state manager, EF
will replace the stub entry with a full blown entity. An example of this scenario is
when we retrieve an order without retrieving the customer for the order; a stub
entry would be created in the state manager. It is because of the stub, we can
access customerid foreign key value for the order by
Order.CustomerRefrence.EntityKey. However when order entity is retrieved using
NoTracking MergeOption, Ef will make query as lean as possible and would not
load any additional data then requested by the client. Therefore
Order.CustomerReference.EntityKey would be null reference. If you want the
related entity, you can either use Load but the Load operator also needs to be used
with NoTracking Option as both MergeOptions must be same. Another option is to
eagerly load both Order and Customer with NoTracking option. Code below shows
an example.
var db = new NorthwindEFEntities();
db.Customers.MergeOption = MergeOption.NoTracking;
var cust = db.Customers.Include("Orders").First();
Console.WriteLine(cust.Orders.Count());
On the above code, both Customer and Orders are retrieved using NoTracking
MergeOption. Although NoTracking was only set on Customer entity, since
Orders entity was also part of Customer query retrieval, EF applied the same
query rules to Orders collection.
On the above, the first customer is retrieved using NoTracking. Before I can
perform an update on the customer, I need to notify the object state manager
about the entity so it tracks the original values of the entity. After attaching
the entity, I am modifying the phone number and save the changes to the
database.
NoTracking queries are great for asp.net scenario where majority of the data
displayed inside of listview and gridview is read-only. Additionally if an
entity is return using NoTracking, it is already in detached state and you do
not have to explicitly detach an entity before you can attach the entity to
another context. A place where we do this quite often is when retrieving an
entity using Object context for update scenario on a gridview and on post
back the entity needs to be attached to a new context because the context that
originally retrieved the entity is already disposed. If an entity is not detached
from the older context is disposed it cannot be attached to a new context. But
if the entity is retrieved using NoTracking, we do not have to worry about
detaching the entity.
//non tracking
db.Customers.MergeOption = MergeOption.NoTracking;
var cust3 = db.Customers.First();
var cust4 = db.Customers.First();
Console.WriteLine("Non Tracking query. Object same {0}",
cust3 == cust4);
On the code above, entities that are tracked have the same object references.
Whereas cust3 and cust4 does not have same references because it uses
MergeOption.NoTracking. Screen shot below shows the result on the console
window.
MergeOption provides several other options that determine how objects are
loaded into the state manager. For instance if a customer is tracked inside the
state manager and some of properties of the customer is modified, if you want
to overwrite those changes with the recent data from the data source, you can
use OverWriteChanges option to discard all the changes the made to the
customer. Although the entity returned will reflect the data from the store but
you will receive the same instance that you had previously been working with
in the application. Code below shows an example of using
MergeOption.OverWriteChanges
var db = new NorthwindEFEntities();
string esql = "select value c from customers as c where
c.CustomerID = 'ALFKI'";
var cust = db.CreateQuery<Customer>(esql).First();
//Berlin
Console.WriteLine(cust.City);
//change it to london
cust.City = "London";
db.CreateQuery<Customer>(esql).Execute(MergeOption.OverwriteChanges).First();
Console.WriteLine(cust.City);
AppendOnly is another option for Merge changes which can be used when
materializing objects inside of object State manager. When AppendOnly
option is used, EF will first check if the entity key retrieved from the store is
found inside the state manager. If object already exists, it will not be
overwritten and the old object is returned to the application. Code below
shows an example of Using AppendOnly
var db = new NorthwindEFEntities();
var customer = db.Customers.First(c => c.CustomerID == "WOLZA");
Console.WriteLine("City: " + customer.City);
//change Wolza customer's city to paris
ChangeCitytoParis();
Console.WriteLine("After change ");
db.Customers.MergeOption = MergeOption.AppendOnly;
db.Customers.First(c => c.CustomerID == "WOLZA");
The code above retrieves WOLZA customer and prints its city to console
window. Then using straight ado.net, I am changing the city of the customer
to Paris by calling changeCityToParis method. After refreshing the customer
by AppendOnly option, I am printing the Wolza customer’s city again on the
console window to check if the city has changed. The results confirm that
calling AppendOnly returns the old object from state manager without taking
changes from the database. Figure below shows the screen shot for the city
results.
Another option for MergeOption is PreserveChanges. When you call preserve
changes to refresh an entity, the original values of the entity are refreshed to
what’s retrieved from the data source. A good use case for this option is when
you get an OptimisticConcurrency exception when saving an entity to the
database. This exception could be raised because the original value of entity
does not match with what’s defined in the store because the values had
changed during the time the entity was inside the state manager. To fix this
exception, we can update the original values of entity inside the state manager
by refreshing the entity using MergeOption.PreserveChanges. Code below
shows an example of using PreserveChanges.
var db = new NorthwindEFEntities();
var customer = db.Customers.First(c => c.CustomerID == "WOLZA");
//change city to london.
customer.City = "London";
//original city is Warszawa
string orignalcity =
(string)db.ObjectStateManager.GetObjectStateEntry(customer).OriginalValues["C
ity"];
Console.WriteLine("Original City " + orignalcity);
Console.WriteLine("Changed City " + customer.City);
CompiledQuery.Compile<ObjectContext, arguments,returntype>(delegate)
There are three overloaded versions of Compile method. Compile method
must at least take in the object context on which queries execute. The return
type is optional if the compiled query returns an anonymous type. In other
cases you can have return type that could be IQueryable of T, an ObjectQuery
of T, an entity or a complex type. Compile method supports up to three
arguments. Those arguments can be used to apply dynamic filter to a query. If
you are executing a compile queries several times, you can pass in different
parameters and still reuse the command tree that was generated the first time.
If you need to pass more than 3 parameters to provider dynamic filtering and
sorting to a compile query, you can pass in class or struct as an argument to
the compile method. Within the compiled query, you can reuse the properties
declared inside struct or a class.
A compiled query only works with linq queries. If linq queries are mixed with
builder methods that allow passing esql statements to linq query operators,
you will get runtime exception. Builder methods are not support inside of a
compiled query. However you can perform eager loading operation by calling
include inside of a compiled query.
Console.WriteLine("Compiled query");
var db2 = new IncludeTPTEntities();
The code above retrieves contact of type gunsmith. In addition, we are also
eagerly loading Company, Phone and department entity associated to it. Since
a compiled query is created once, it is declared as a static variable and to
execute it, we are passing in an instance of data context. To create a test
results, I am first executing a non compile version of the gunsmith query ten
times inside a loop and capturing the time taken followed by executing a
compiled version of the query ten times and capturing its result. Figure below
shows the output on the console window.
The above screen shot shows that compiled query actually took less initially
and consecutive runs were extremely fast to a point some queries had a value
of zero for milliseconds because majority of the cost in executing the query
was compiling the linq expression to a command tree. The results above are
not an accurate measure of the performance improvement between a compiled
and non compiled version of the query. The results would vary depending on
the data you are retrieving in the application, complexity of the model and the
load on the machine. It gives you a base line to consider of how
improvements you would receive when compiling linq queries.
Console.WriteLine(gunsmiths(entities).First().ContactName);
To execute the above linq query, I am passing the data context and start and
end date parameters required by the compiled query.
Compiled queries do not restrict you to use the query as it is. For instance if
you feel that only a certain portion of the query needs to be compiled and the
rest of the query should remain dynamic you can further apply transformation
on top of a compiled query and leverage the benefits of some portion of the
command tree being pre compiled. In the code below I am retrieving
gunsmiths that belong to Widget Company using a compiled query. On top of
the compiled query, I am adding additional filter to only retrieve those
gunsmiths that are certified.
var entities = new TwoTPTEntities();
The return type for a compiled query does not need to be a full blow entity. If
you have complex type defined on your model that could also be used as the
return type for the compiled query. In the example below customer’s address
is represented as a complex type CommonAddress. Since the query is only
interested in grabbing the address information for a customer, I have set the
return type for the compiled query to be CommnonAddress class.
var entities = new NWComplexTypeEntities();
var addresses = CompiledQuery.Compile<NWComplexTypeEntities,
IQueryable<CommonAddress>>(
(db) => db.Customers.Where(c => c.Address.City ==
"London").Select(c => c.Address));
foreach (var addr in addresses(entities))
{
Console.WriteLine(addr.Address);
}
The above code not only returns a complex type but also filters on a complex
type to only retrieve addresses where the city is London.
Compiled query also allows returning anonymous type if the query does not
return a full entity. To use anonymous type in a compiled query, you do not
specify the return type for the compiled query and simply assign the compiled
query results to var which automatically infers the type from the query
declaration. In the example below, I am retrieving the CustomerID and the
total purchases the customer has made up to date.
var entities = new NorthwindEFEntities();
var summary = CompiledQuery.Compile((NorthwindEFEntities db) =>
from c in db.Customers
where c.City == "London"
select new
{
CustID = c.CustomerID,
TotalPurchases = c.Orders.SelectMany(o =>
o.OrderDetails).Sum(od => od.UnitPrice * od.Quantity)
});
foreach (var cust in summary(entities))
{
Console.WriteLine("CustID:{0}
Purchases:{1:C}",cust.CustID,cust.TotalPurchases);
}
A compiled query can also return scalar value as the return type. In the
example below, the compiled query returns the total units on order for all the
products in the database that have a unitinStock equal to zero.
//total units ordered for all products
var entities = new NorthwindEFEntities();
var totalordered = CompiledQuery.Compile<NorthwindEFEntities,
int>(
(db) => db.Products.Where(p => p.UnitsInStock == 0)
.Sum(p => p.UnitsOnOrder).Value);
Console.WriteLine("Total units ordered: " +
totalordered(entities));
The overloaded versions of Compile method only supports up to three
parameters. What if the queries need to apply a dynamic filter that would
require more than three parameters? To use more than three parameters, you
can either declare a class or struct that has all the properties that you need to
filter and pass the instance of the class or struct as a parameter to the
compiled query.
var criteria = new Criteria{ Quantity = 50, Discount = 0, City = "London" };
var ods =
CompiledQuery.Compile<NorthwindEFEntities, Criteria,
IQueryable<OrderDetails>>(
(db, search) => from od in db.OrderDetails
where od.Quantity > criteria.Quantity &&
od.Discount == criteria.Discount
&& od.Orders.Customer.City == criteria.City
select od
);
var entities = new NorthwindEFEntities();
foreach (var od in ods(entities,criteria))
{
Console.WriteLine(od.OrderID);
}
Solution: In version 1 release, relationship span does not work when entities
are returned using stored procedure. Even if the stored procedure results
includes foreign key columns for related entities which is what object state
manager use to build relationship stub, EF cannot break the result into entity
and relationship info entry. It is possible that this feature will make it to next
release where framework would have the smartness to create stub entries if
stored procedure result contains foreign key columns.
When we load phone entities using stored procedure, they are tracked in the
state manager but there is no relationship stub created for the graph to be
fixed when customers are loaded. To ensure the phones are attached to the
appropriate customers, we have to manually attach the phones that belong to
customer.
Solution: When a query is executed against an entity model, the runtime will
first convert the model into esql views. Esql views are compiled versions of
the mapping schema file in code. It basically contains esql queries that
represent the model being queried. The store views are created once and then
cached at the app-domain level for later use. For asp.net scenarios, the view
generation occurs as a startup cost when the model is queried for the first
time. On subsequent request the model is cached and reused for later
execution. Unless there is an IIS reset or recycling of app domain, the query
view generation would not occur. There are several different ways to reduce
the cold startup cost. Bullets points below describe some possible options.
The above command line execution would generate views.cs class that can
be added to the class library project and will be utilized by the runtime
instead of generating views. Another option of generating view would be to
use edmgen2.exe utility that can be downloaded from
http://code.msdn.microsoft.com/EdmGen2
You can also use FullGeneration mode option with edmgen.exe to generate
ssdl, msl and csdl along with the store views that contains all the mapping
definition in code.
The code below queries the gunsmith model causing the view generation to
happen. The query is executed under a stopwatch to capture the time taken.
The above query when executed ten times takes 452 milliseconds with store
views generated on demand by the runtime. When the store views code file
is generated ahead of time using edmgen.exe and added to the console
application, ten iterations of the query only takes 404 milliseconds. The 48
milliseconds that took extra was the cost of converting the model to esql
views. For a model that only contains 6 entities the cost was fairly high, so if
the model is very complex and contains large number of entities, the startup
cost which is executing the first query on the model would be very high.
One of the problems with using edmgen.exe or edmgen2.exe is, you have to
remember to regenerate the views when you make changes to the model
otherwise the views would be out of sync with the model causing runtime
errors. To avoid that you can programmatically generate the views and place
the code file inside the bin directory for runtime to find it. Code below
shows an example of programmatically generating the code file for a console
application.
static string csdlNamespace = "http://schemas.microsoft.com/ado/2006/04/edm";
static string ssdlNamespace =
"http://schemas.microsoft.com/ado/2006/04/edm/ssdl";
static string mslNamespace = "urn:schemas-microsoft-
com:windows:storage:mapping:CS";
StorageMappingItemCollection mapping =
MetadataItemCollectionFactory.CreateStorageMappingItemCollection
(edmitems,sitems,new[]{msl.CreateReader()}, out merrors);
The above code generates Views.cs code file inside the bin directory
containing all the store views.
One of the other ways to force the generation of views is executing a dummy
query ahead of time causing the runtime to generate the views. Code below
confirms that behavior.
var db = new IncludeTPTEntities();
db.Contacts.First();
Stopwatch watch = new Stopwatch();
watch.Start();
//orignal query
var gunsmiths = db.Contacts.OfType<GunSmith>()
.Include("Company.Phone")
.Include("Company.Departments").ToList();
watch.Stop();
Console.WriteLine("Gunsmith Time taken:{0}",
watch.ElapsedMilliseconds);
On the above code, I am executing a query that returns the first contact in
the database. The query causes view generation process to trigger and
therefore when the actual query executes, there is no cost associated with
view generation. Figure below shows the time takes for the original query to
execute.
If an Entity Key is already assigned to an entity reference and you want to change
it to a different value you will still have to create a new instance of entity key
because it is immutable and its properties cannot be changed once it is assigned. If
you do try to change it you will get an exception that key cannot be changed once
they are set. The code below shows an incorrect usage of assigning customerid.
Instead of creating an entity key, customerid value is assigned to an existing an
entity key value that throws an exception.
var db = new AdventureWorksLTEntities();
var prod = db.Product.First(p => p.ProductNumber == "FR-R92B-
58");
//Error:entity key value cannot be changed once they are
set.Incorrect usage.
prod.ProductModelReference.EntityKey.EntityKeyValues[0].Value =
2;
db.SaveChanges();
In a situation where you want to remove an entity reference, you can simply set the
entity key to a null value. The code below retrieves the product and prints its
productModelId on the console window. To remove the association of the product
to Productmodel, EntityKey on ProductModelReference is set to null. When
SaveChanges is called EF sees that ProductModelId entity key has a null value and
updates the product table where ProductModelId column value is set to null.
EF is smart in synchronizing references when the entity key value is set. For
instance if Product entity is pointing to ProductModel and if entity key is set to
null, productModel would no longer be pointing to valid reference. When entity
key is set to null, EF removes the relationship entry inside of Object State Manager
between Product and ProductModel.
var db = new AdventureWorksLTEntities();
var prod = db.Product.Include("ProductModel").First(p =>
p.ProductNumber == "FR-R92B-59");
Console.WriteLine("Model:{0}",prod.ProductModel.ProductModelID);
//set entitykey for model to null also removes the reference.
prod.ProductModelReference.EntityKey = null;
Console.WriteLine("ProductModel clr object is {0}",
prod.ProductModel == null ? "null" : "not null");
On the code above, I am retrieving Product and its ProductModel by using Include.
To confirm that Product entity has a valid product model, I am printing the
ProductModelId on the console window. When EntityKey is set to null on
ProductModelReference, ProductModel navigation property is also set to null as
indicated by the results shown on the console window.
All the above cases discussed are options that should be considered if the related
entity key is not loaded inside the state manager. If related entities are available as
objects and tracked by object state manager, it is cleaner to assign an object
reference to the navigation property. The code below shows an example where
ProductModel entity is retrieved from the store and assigned to the Product entity.
var db = new AdventureWorksLTEntities();
var prod = db.Product.First(p => p.ProductNumber == "FR-R92B-
58");
var productmodel = db.ProductModel.First(m => m.ProductModelID ==
2);
prod.ProductModel = productmodel;
db.SaveChanges();
In the above example, a dummy course entity is created with the courseid present
in the database. To notify the Object State Manager about the course, we are
attaching the course to the state manager and adding the course to the student’s
course collection. Since the relationship requires the courseid to be present for
course entity, Ef correctly inserts the relationship into the link table to associate the
student with the course.
Problem: You have select items in an array and would like to right a query
that filters the results based on the items in the array. The query requires an In
clause. You need to know what are the various options available in entity
framework that would be translated to In clause on the database.
Discussion: In the example above, I have an array of cities that I would like to
get customers for from NorthWind database. To pass this array to esql
statement, I have to convert the array to a string delimited by comma and if
the column you are searching on is string column, than you also need to make
sure that each item in your list is enclosed in single quotes. You would think
that you write an in clause in esql as follows.
var cities = new [] {"London","Berlin"};
string sql1 = @"
Select value c from Customers as c
where c.City in {" + cities + "}";
db.CreateQuery<Customer>(sql1).ToList();
In the above code, I am passing in the array directly to esql statement as you
would normally do when you are writing sql statement. However when I run
this code I get an exception stating that query syntax is not valid. Since I
cannot pass array directly, I first use select operator to surround each item in
my arrary with single quote and then flatten my array into a string by using
string.join operator. I am than building esql query as follows
var db = new NorthwindEFEntities();
string sql = @"
Select value c from Customers as c
where c.City in {" + cityparams + "}";
var custs =
db.CreateQuery<Customer>(sql);
In the above code, I use value operator in my select clause to return all the
customers which has city that matches the cities that I am passing in using
cityparams variable. To test the query returned correct results, I am doing a
count operator in memory to checks how many customers in my result belong
to city of London and Berlin. Following code shows how to do this.
Console.WriteLine("Customers for London " +
custs.Count(c => c.City == "London "));
You are not obligated to build an entire esql statement to get your results
back. You can additionally use linq to entities and pass in a dynamic where
clause in the form of esql statement. The benefit of this approach is you don’t
have to write the entire esql statements just the where clause of the query is
required. Secondly using linq to entities, you also get a chance to chain the
query by joins, where and sorts clause. This is an important concept in the
sense you can mix and match esql with linq expressions. For cases that are
supported in terms of linq query operator, you can use esql and for the rest of
query, you can enjoy the benefits of linq query operators. Following code
shows an example of using esql statement as part of the where extension
method.
//2nd way to do it.
var custs2 =
db.Customers.Where("it.City in {@cities}",new
ObjectParameter("cities",cityparams));
In the above code, I am using the it syntax to access the current item passed in
to the where clause. Since it operator in the current context represents a
particular customer, I can access its City property and filter the list to only
customers that belong to the cities passed in using cityparams.
Example below shows a linq query that performs the same operator of
filtering the customers to cities that match London and Berlin.
var all = from ct in cities
join c in db.Customers on ct equals
c.City
select c;
Console.WriteLine("Customers from London and
Berlin " + all.Count());
Looking at the above query you must be wondering that if linq syntax works
fine than why is there a need to write esql statement to get results back.
Although the above query compiles and runs fine, it is really not an optimal
query. Since join against an in memory collection is not possible and cannot
be understood by sql server, linq to entities brings the entire customer records
in memory applies the join in memory. Although the results will be same and
the query is also much readable, query is not performed on the database
which is not a good solution unless the records in the customer table are not
too many to affect the performance. The most appropriate way to write a linq
query that gets executed on the database, is readable and supports compile
time check is by using contains operator. Currently v1 version of entity
framework does not support contains operator in contrast to linq to sql which
fully supports contains operator that is translated to in clause on the database.
Following example shows how to write the same query using linq to sql
syntax and uses contains clause.
var db = new NorthwindEFEntities();
db.Customers.Where(c => cities.Contains(c.City))
Above code uses contains operator to tell that customer’s city must be in one
of the city in the array.
Problem: You have Customers table that you are filtering based on certain
criteria defined in your linq query. You want to display the results of the
query in a listview control. Since list view’s page size is set to 20 rows per
page, you want to ensure that the data returned from the query uses paging on
the server side to only bring rows enough that can be fit on one page.
Solution: Use the Skip the operator to skip the number of records that you
have paged through in listview control. When you use objectadatasource
control with listview, objectdatasource will pass in the number of records to
skip by inquiring the current page from the listview. After applying Skip
operator you will use the Take operator to take the number of records the
listview can display on a page. Number of records the page can display is
obtained by the pagesize property of the listview control. If you use
objectdatasource control, you will automatically be passed the page size of
the listview control. Code below applies the contact title filter to query for
customers which has a matching contact title. Instead of returning all the
records we use the Skip and Take operator to only bring records equal to the
page size of the listview control.
namespace NorthWind.Business.EF
{
public partial class Customer
{
public static IQueryable<Customer>
GetCustomersByContactTitle(string contacttitle, int start,
int max)
{
var db = new NorthwindEFEntities();
Discussion: In the above example, I have a partial class Customer which has a
method GetCustomersByContactTitle. Since we do not need an instance of
Customer class, I have made the static. GetCustomersByContactTitle method
takes 3 parameters. First parameter is the contact title which will limit our
customer results by returning customers that only match the contact title
passed in. The second parameter passed in represents the number of rows we
need to skip. Start parameter is populated by objectdatasource control on our
page as we will see shortly when we go through our Customers page.
Objectdatasource control inquires the listview control the current page being
requested and than using the page size it determines how many rows it needs
to skip and passes that as the value for start parameter of our method. The
third parameter max represents how many records to retrieve from the
database. Max parameter is also assigned by ObjectDatasource which reads
the pagesize from the data pager’s Page Size property. Inside the method, we
are assigning the generic Customers collection exposed by Objectcontext to
custs variable. Based on if the contact title has something other than null
value, we filter the results based on contact title. Than using the start and max
row parameter we skip and take the number of rows required for one page.
Another interesting lambda expression that we have added before the skip and
Take operation is ordering the rows by CompanyName. Linq to entities
requires that you specify what column you want to sorts the results by before
you applying paging on the results. Failing to order the results before paging
will result in the following error.
The method 'Skip' is only supported for sorted input in LINQ to Entities. The
method 'OrderBy' must be called before the method 'Skip'.
Although Linq to entities enforces constraints for sorting before paging, Linq
to sql does not require any sort operation for paging to work. Code below uses
linq to sql datacontext to run the same query with no OrderBy operator. The
results returned are exactly the same except no order and you do not get any
exception either.
var db = new NorthWindDataContext();
db.Log = new DebuggerWriter();
var custs = db.Customers.AsQueryable();
if (contacttitle != null)
{
custs = custs.Where(c => c.ContactTitle ==
contacttitle);
}
//linq to sql does not require ordering.
custs = custs.Skip(start).Take(max);
SelectCountMethod="GetCustomersByContactTitleCount"
SelectMethod="GetCustomersByContactTitle"
TypeName="NorthWind.Business.EF.Customer">
<SelectParameters>
<asp:ControlParameter Name="contacttitle"
ControlID="titles" />
</SelectParameters>
</asp:ObjectDataSource>
</div>
In the example above, I have a dropdown which contains few selections for
Contact Title that we will use to filter our customer query. By default when
the page loads up, the selected value in the dropdown is set to empty which
objectdatasource converts it to null before send the filter parameter to
customer entity. Since we are checking for null value for filter, we will
retrieve all records in customer table with no filter criteria set.
When your run the above customer query, the sql generated is slightly
different in both linq to sql and linq to entities. Below code shows the
different query generated by their respective providers. Both queries have
been slightly modified to clean up the noise from explicit column names
specified in the query.
Linq To entities
SELECT TOP (5) [Project1].*
FROM
( SELECT [Project1].*, row_number() OVER (ORDER BY
[Project1].[CompanyName] ASC) AS [row_number]
FROM
( SELECT [Extent1].*
FROM [dbo].[Customers] AS [Extent1]
WHERE [Extent1].[ContactTitle] = 'Owner'
) AS [Project1] ) AS [Project1]
WHERE [Project1].[row_number] > 5
ORDER BY [Project1].[CompanyName] ASC
Linq to Sql
SELECT [t1].*
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].*) AS [ROW_NUMBER], [t0].*
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[ContactTitle] = 'Owner'
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN 5 + 1 AND 5 + 5
ORDER BY [t1].[ROW_NUMBER]
Linq to entities uses where clause to skip the number of rows and uses top
clause to fetch the number of records to take. Whereas Linq to sql uses
between operator to apply the skip and take operation specified in our linq
query.
Problem: Figure below shows the Table Per Type inheritance defined on
entity data model.
Since the above model uses table per type structure, Entity framework
will write inserts to two different tables. For instance if we create an
instance SalariedEmployee, entity framework will insert record into
employee table followed by SalariedEmployee table. You want to
know how you can enforce optimistic concurrency at entity level
instead at table level.
Solution: To ensure optimistic concurrency, we need to add timestamp
column to Employee table because it is the base entity defined on the
model. Adding timestamp column to derive tables serve no purpose
because when an update happens and regardless if only derived tables
get affected, EF will still apply a dummy update to base Employee
table causing TimeStamp column on base entity Employee to also get
updated.
System.Threading.Thread.Sleep(5);
salaryemployee.Salary = 85000;
//run update statement and check for time.
db.SaveChanges();
After executing the above code, we have also captured sql statements
send by entity framework to the database engine.
exec sp_executesql N'insert [tpt].[Employee]([Name])
values (@0)
select [EmployeeId], [TimeStamp]
from [tpt].[Employee]
where @@ROWCOUNT > 0 and [EmployeeId] = scope_identity()',N'@0
varchar(4)',@0='Alex'
To confirm that our stored procedure got imported in our model, we can open
up model browser window and expand the stored procedures node to see our
stored procedure. Screen shot below confirms that GetOrdersForCust stored
procedure got successfully imported into our entity data model.
Importing the stored procedure is only the first step in being able to use the stored
procedure with our entity data model. The next step is to import the stored
procedure in our conceptual model. You can do that in 2 different ways. You can
either right click the designer, click add, and select Add Functional Import or open
up the entity model browser and right click the stored procedure and select create
function import. Screen shot below shows how to access Add Functional Import
window.
After completing the function import, object context will expose method
GetOrdersForCust which takes customerid as parameter and returns orders for the
customer. Code below shows how to call the stored procedure to return orders for
the customer.
var db = new NorthwindEntities();
var orders = db.GetOrdersForCust("ALFKI");
foreach (var order in orders)
{
Console.WriteLine(order.OrderID);
}
Unlike linq to entity queries that are late bound and are only executed when you
iterate over the results, calling stored procedure executes the query immediately. In
the above example, when I call GetOrdersForCust, the result returned is of type
ObjectResult of T where T in this case is an Order entity. If you are going to be
iterating over orders collection multiple time, you have to use a ToList or other
operators that can force the result into a collection. Failing to do so would raise an
exception as follows.
When you call GetOrdersForCust stored procedure, it uses data reader to fetch the
results and by calling it once you reach to end of the data reader. Therefore when
you call the stored procedure second time, you cannot reiterate the reader starting
from top since data readers are only move forward. As a result you get an
exception stating that query can only be enumerated once.
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<Parameter Name="custid" Type="varchar" Mode="In" />
</Function>
In the above xml, we are declaring a stored procedure by the name
GetOrdersForCust that exist in dbo schema. The reason we know it is a stored
procedure is because IsComposable is set to false which means that the function
cannot be called in from clause of sql statement which stored procedures cannot.
We are also specifying that it is not a build in stored procedure by setting BuiltIn to
false. Stored procedure takes an input parameter by name custid that has to be
varchar data type which we are defining by using Parameter element inside of the
function declaration.
When we imported the stored procedure into our conceptual model, function
import calls are written in our conceptual model that causes a method to be
declared with the correct parameters our stored procedure needs. Xml below shows
the functionimport calls declared in our conceptual model.
<FunctionImport Name="GetOrdersForCust" EntitySet="Orders"
ReturnType="Collection(NorthwindModelStoredProcedure.Orders)">
<Parameter Name="custid" Mode="In" Type="String"
/></FunctionImport></EntityContainer>
<EntityType Name="Customers">
The above xml creates a method GetOrdersForCust on our object context that
returns a collection of Orders belonging to Orders EntitySet. The method takes in a
parameter custid with string data type. The designer also writes an entry in the
mapping file that associate our method call in our conceptual model to a call to the
stored procedure declared in our store definition. Following entry is written on the
mapping file that associates that maps the method to the stored procedure. On the
FunctionImportMapping element we specify the functionimportname declared in
our conceptual model and specify the functionname defined in our store model to
create the mapping.
<FunctionImportMapping FunctionImportName="GetOrdersForCust"
FunctionName="NorthwindModelStoredProcedure.Store.GetOrdersForCu
st" />
Based on Function and import calls defined in our model, following code is
generated by the designer to execute call to our stored procedure. I have simplified
the actual code for clarity and explanation.
public ObjectResult<Orders> GetOrdersForCust(string custid)
{
ObjectParameter custidparam = new
ObjectParameter("custid", custid);
return
base.ExecuteFunction<Orders>("GetOrdersForCust", custidparam);
}
In the above example, we had a stored procedure that returned orders for a
customer. What if you wanted to return order details for those orders in a single
query? If you try to update the stored procedure to return multiple result set eg
Orders and Order Details, entity framework would not allow that. Currently in the
v1 release of entity framework stored procedures cannot return multiple result set.
The result set can only be a single entity type. To get around this you can create
another stored procedure that returns all the order details for a given customer and
then manually match attach the OrderDetails that belong to a given Order.
Example below shows how to do this.
var db = new NorthwindEntities();
var orders = db.GetOrdersForCust("ALFKI").ToList();
var orderdetails =
db.GetOrderDetailsForCust("ALFKI").ToList();
// one way to attach order.
orders.ForEach(o =>
o.OrderDetails.Attach(orderdetails.Where(od => od.OrderID ==
o.OrderID)));
foreach (var order in orders)
{
Console.WriteLine(order.OrderDetails.Count());
}
On the above code, I am getting orders for ALFKI customer by calling our stored
proc GetOrdersForCust and forcing the result of the query by calling ToList. Next
I am retrieving all the Order Details for all the orders placed by ALFKI customer.
Although I have two collections in memory orders and OrderDetails, they are
completely disjoined from each other. To create a relationship between the
OrderDetails and the orders, I am enumerating the orders in memory and for each
order; I query the OrderDetails collection to find only OrderDetails entities that
belong to the given order by filtering the results based on ordered. After finding the
OrderDetails that belong to the order, I attach the matched OrderDetails to the
OrderDetails collection of the order. Once I have synced up my orders with order
details, I am printing the count of the OrderDetails for each order and output is as
follows.
On the above example that used stored procs, both orders and order details were
disjoined from each other and you had to manually attach the order details to their
order. If you had queried the data using Object services than entity framework
would take care of merging order Details to their given orders as if they had been
retrieved together. Example below show that attach is implicit since Entity
framework knows more about the query and is therefore able to get enough
information to fix up the relationship entries in the object statemanager for
OrderDetails retrieved from the query.
var db = new NorthwindEntities();
var orders = db.GetOrdersForCust("ALFKI").ToList();
var orderdetails = db.OrderDetails.
Where(od =>
od.Order.Customer.CustomerID == "ALFKI")
.ToList();
foreach (var order in orders)
{
Console.WriteLine(order.OrderDetails.Count());
}
The above code, makes separate database calls to first retrieve Orders for ALFKI
customer and followed by another query to retrieve all the OrderDetails for ALFKI
customer. Although two queries are completely disjoined from each other, entity
framework has enough information from the query being executed to determine
that OrderDetails retrieved actually belong to orders that are available in memory
and therefore automatically performs the attach for us.
Discussion:
To use the stored procedure I have to perform my usual step of importing the
stored procedure into my store model and from the store model, use the
import function screen to add a functionImport call in our conceptual model.
Xml below shows definitions created in ssdl, mapping and store model.
SSDL
<Function Name="TotalSalesForCust" Aggregate="false" BuiltIn="false"
NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<Parameter Name="custid" Type="varchar" Mode="In" />
</Function>
CSDL
<FunctionImport Name="TotalSalesForCust"
ReturnType="Collection(Decimal)">
<Parameter Name="custid" Mode="In" Type="String" />
</FunctionImport>
MSL
<FunctionImportMapping FunctionImportName="TotalSalesForCust"
FunctionName="NorthwindModelStoredProcedure.Store.TotalSalesForCust" />
Although all the entries are created in the model, there is no method created in
the generated files to call the stored proc. Due to time constraints this feature
did not completely made it version 1 of entity framework and we should
expect a complete support to return a scalar value from the stored proc in the
next version. In the mean time, you can write some code to open a connection
and manually execute the datareader to get value from the stored procedure.
Code below shows how to do that.
public partial class NorthwindEntities
{
private T ExecuteFunction<T>(string functionname,DbParameter[]
parameters) where T:struct
{
DbCommand cmd =
((EntityConnection)this.Connection).CreateCommand();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddRange(parameters);
cmd.CommandText = this.DefaultContainerName + "." +
functionname;
try
{
if (cmd.Connection.State ==
System.Data.ConnectionState.Closed)
{
cmd.Connection.Open();
}
var obj = cmd.ExecuteScalar();
return (T)obj;
}
catch (Exception)
{
throw;
}
finally
{
cmd.Connection.Close();
}
}
To consume the generic function, I have created another method which passes
ExeucteFunction the name of the stored procedure to call and the parameters
required by the stored procedure. Having done this, consuming the stored
procedure requires creating an instance of our objectContext and calling
TotalSalesForCust method passing in the name of the customerid as shown
below
var db = new NorthwindEntities();
decimal totalsales = db.TotalSalesForCust("ALFKI");
Discussion: First step to using the stored procedure with entity framework is
to define the stored procedure in the database. GetCusSales stored procedure
below, returns top 5 customers with highest sales. The result returned includes
CustomerId and TotalSales column.
create proc dbo.GetCusSales
as
begin
select top 5 c.CustomerID,sum(od.UnitPrice * od.Quantity) TotalSales
from Customers c join orders o on c.CustomerID = o.CustomerID
join [Order Details] od on o.OrderID = od.OrderID
group by c.CustomerID
order by 2 desc
end
To use the stored procedure with EDM, we will update the model from the
database which will write following entry in our ssdl.
<Function Name="GetCusSales" Aggregate="false" BuiltIn="false"
NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" />
To import the function into our conceptual, the function needs to map to an
entity. Therefore we will create an entity that matches the column and data
type returned from the result of the stored procedure. Using the designer to
create CustomerSale entity generates the following entity definition on my
conceptual model.
<EntitySet Name="CustomerSales"
EntityType="NorthwindModelStoredProcedure.CustomerSale" />
<EntityType Name="CustomerSale">
<Key>
<PropertyRef Name="CustomerID" /></Key>
<Property Name="CustomerID" Type="String" Nullable="false" />
</EntityType>
We then need to import our stored procedure from SSDL into conceptual
model with return type being the new entity called CustomerSale. Using the
Add function import dialog on the designer I have imported the stored
procedure and it generated the following statement in our conceptual model
<FunctionImport Name="GetCusSales" EntitySet="CustomerSales"
ReturnType="Collection(NorthwindModelStoredProcedure.CustomerSale)" />
If you try to compile the edmx file at this stage, you will get validation errors.
This is because entity framework requires an entity defined on the conceptual
model to be mapped to something on the storage model. To achieve that
purpose we will fake out an entity set and entity type on the storage model
(SSDL) as follows.
<EntitySet Name="CustomerSales"
EntityType="NorthwindModelStoredProcedure.Store.CustomerSale">
<DefiningQuery>
faking out entityset
</DefiningQuery>
</EntitySet>
<EntityType Name="CustomerSale">
<Key>
<PropertyRef Name="CustomerID" />
</Key>
<Property Name="CustomerID" Type="nvarchar"
Nullable="false" />
<Property Name="TotalSales" Type="decimal"
Nullable="false" />
</EntityType>
The above xml creates an entity set using a defining query. Since we are
faking a query, defining query does not contain anything. We are also
defining an entity type Customer Sale which we will use in our mapping layer
to map Customer Sale on the conceptual model to storage model.
EntitySetMapping section defined below in our msl layer is what maps our
customerSales Entityset on the conceptual model to customerSales on
entityset.
<EntityTypeMapping
TypeName="IsTypeOf(NorthwindModelStoredProcedure.CustomerSale)">
<MappingFragment
StoreEntitySet="CustomerSales">
<ScalarProperty
Name="CustomerID" ColumnName="CustomerID" />
<ScalarProperty
Name="TotalSales" ColumnName="TotalSales" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
Once we have the mapping set correctly, we can consume the stored
procedure by calling GetCusSales method available on our object context.
Code below loops through CusSales object returned by GetCusSales method
and prints the customerid and TotalSales on the console window.
private static void StoredProcedureReturningAnonymousType()
{
var db = new NorthwindEntities();
foreach (var cussale in db.GetCusSales())
{
Console.WriteLine("CustID {0} Sales
{1}",cussale.CustomerID,cussale.TotalSales.ToString("c"));
}
}
Stored Procedure returning entities that are partially filled will appear to work
but when you execute them you get a runtime exception complaining the
columns missing from the entity. Unlike entity framework, linq to sql allows
you to return partially filled entities such as customer from both stored
procedures and dynamic sql queries. The only constraint it imposes on either
option is that the result set returned must have the primary key column
included. In addition, if the stored procedure returns an arbitrary number of
columns such as columns containing summary data, dragging the stored
procedure on linq to sql designer would generate a class that matches columns
and the return type defined on the stored procedure.
Problem: Instead of defining a stored procedure to reuse sql logic, you want
to use command Text option for functions definition to embed the sql
statement inside the SSDL layer.
Solution: In most scenarios, if you have some complex sql statements that
requires various joins and makes use of operators that are only available on
sql server, you would prefer taking the route of stored procedure. In the case
where you do not have permissions to create stored procedure or do not want
to manage separate deployments for application and database, you can use the
commandText property on the function definition on the ssdl layer to embed
stored procedure logic. This allows reuse of certain database logics without
actually defining a stored procedure on the database. Example below embeds
a sql statement to get customer with the highest total purchase.
<Function Name="CustomerWithHighestSales"
Aggregate="false"
BuiltIn="false" NiladicFunction="false"
IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<!-- cannot use command text with functions.
allows to execute multiple statement.-->
<CommandText>
select *
from Customers
where CustomerID = (
select top 1 o.CustomerID
from Orders o join [Order Details] od on
o.OrderID = od.OrderID
group by o.CustomerID
order by SUM(od.UnitPrice * od.Quantity)
desc
)
</CommandText>
</Function>
The above function import defines the name that would be created on the
objectcontext and will be used by the application layer to consume the stored
procedure. Using the return type and entity set, we are also defining that
stored procedure would return a collection of Customer entities that would be
mapped to Customers entityset. Entityset is required because an entity
customer could be part of many entity sets. To map the method definition on
the object context to the store definition of the function, we have to create a
mapping by using FunctionImportMapping element on msdl as shown in the
example below. FunctionImportMapping specifies the functionImportname
defined on our conceptual model that will map to the function name defined
on our storage model.
<FunctionImportMapping
FunctionImportName="CustomerWithHighestSales"
FunctionName="NorthwindModelStoredProcedure.Store.CustomerWi
thHighestSales" />
EXEC [dbo].[CustStats]
@custid,
@TotalOrders = @TotalOrders OUTPUT,
@TotalPurchases = @TotalPurchases
OUTPUT
MERGE CustomerSummary as cs
USING (
SELECT @custid CustomerID,
@TotalOrders TotalOrders,
@TotalPurchases TotalPurchases
) as csr
on cs.CustomerID = csr.CustomerID
WHEN MATCHED THEN
UPDATE SET
cs.TotalOrders = csr.TotalOrders,
cs.TotalPurchases = csr.TotalPurchases
WHEN NOT MATCHED THEN
INSERT(CustomerID,TotalOrders,TotalPurchases)
VALUES
(csr.CustomerID,csr.TotalOrders,csr.TotalPurchases);
</CommandText>
<Parameter Name="custid" Type="nvarchar"
Mode="In" />
</Function>
As we have done in the past, we have to also define the function on the
conceptual layer and specify mapping in msdl to map to the stored procedure.
In the v1 release of the EF there is no code generated on the object context to
call a stored procedure with no result set; therefore we have to write little bit
of boiler plate code to execute the stored procedure. Code below shows the
completed version with all the declarations for msl, csdl and executing the
method using ObjectContext.
CSDL
<FunctionImport Name="UpdateCustomerSummary">
<Parameter Name="custid" Mode="In"
Type="String" />
</FunctionImport>
MSL
<FunctionImportMapping
FunctionImportName="UpdateCustomerSummary"
FunctionName="NorthwindModelStoredProcedure.Store.UpdateCust
omerSummary" />
Object Context
private void ExecuteNonQuery(string functionname,
DbParameter[] parameters)
{
DbCommand cmd = this.Connection.CreateCommand();
cmd.CommandType =
System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddRange(parameters);
cmd.CommandText = this.DefaultContainerName + "." +
functionname;
if (cmd.Connection.State ==
System.Data.ConnectionState.Closed)
{
cmd.Connection.Open();
}
cmd.ExecuteNonQuery();
}
public void UpdateCustTotal(string custid)
{
var param = new EntityParameter("custid",
System.Data.DbType.String);
param.Value = custid;
this.ExecuteNonQuery("UpdateCusTotal",
new[]{param});
}
db.UpdateCustomerSummary("ALFKI");
Output below confirms that ALFKI record in Customer Summary table got
updated.
Problem: You have stored procedures defined on the database that has input
and output parameters. You want to learn how to call these procedures and
get output parameters back using entity framework.
Discussion:
CSDL
<FunctionImport Name="CustStats">
<Parameter Name="custid" Mode="In"
Type="String" />
<Parameter Name="TotalOrders"
Mode="InOut" Type="Int32" />
<Parameter Name="TotalPurchases"
Mode="InOut" Type="Decimal" />
</FunctionImport>
MSL
<FunctionImportMapping FunctionImportName="CustStats"
FunctionName="NorthwindModelStoredProcedure.Store.CustStats"
/>
As mentioned earlier when you define a function on the conceptual model
there is no method generated on the object context if the stored procedure
returns no result. To call the stored procedure we can create a partial class for
the objectcontext and create a method that calls the stored procedure.
Example below creates GetCustStats method that executes our stored
procedure in the database.
public void GetCustStats(string custid, ref int totalorders,
ref decimal totalpurchases)
{
var dbparams = new DbParameter[]
{
new
EntityParameter{ParameterName="custid",DbType=
DbType.String,Value=custid},
new
EntityParameter{ParameterName="TotalOrders",DbType=
System.Data.DbType.Int32,Direction =
ParameterDirection.Output},
new
EntityParameter{ParameterName="TotalPurchases",DbType=
System.Data.DbType.Decimal,Direction =
ParameterDirection.Output}
};
ExecuteNonQuery("CustStats", dbparams);
totalorders =
Convert.ToInt32(dbparams[1].Value);
totalpurchases =
Convert.ToDecimal(dbparams[2].Value);
}
Problem: You have modeled product table in your conceptual model using
table per hierarchy inheritance. Any products that are discontinued is defined
using DiscontinuedProduct entity which inherits from Product entity. You
have a stored procedure that returns products that also contains discontinued
products. You want to ensure that products returned from the stored
procedure gets correctly mapped to the inheritance structure defined on the
conceptual model.
Discussion:
To walk through the example, we will create a stored procedure that returns
two products with second product being discontinued. Stored procedure
below achieves our requirement.
ALTER proc [dbo].[GetSimpleProds]
as
begin
select top 1 * from SimpleProduct where Discontinued = 1
union
select top 1 * from SimpleProduct where Discontinued = 0
end
On the above stored procedure, I am returning the top 1 product for both
regular products and discontinued products. To define the stored procedure
into our storage model and import the procedure into our conceptual model,
we will update ssdl and csdl layer as follows.
SSDL
<Function Name="GetSimpleProds" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitPromotion" Schema="dbo"
/>
CSDL
<FunctionImport Name="GetSimpleProds"
EntitySet="SimpleProducts"
ReturnType="Collection(NorthwindModelStoredProcedure.SimpleP
roduct)" />
On the above code, in our conceptual model, we are setting the return type to
be collection of SimpleProducts which is the base class for all products. To
map the conceptual model to the storage model, mapping definition is
updated to reflect inheritance structure.
<FunctionImportMapping FunctionImportName="GetSimpleProds"
FunctionName="NorthwindModelStoredProcedure.Store.GetSimpleProds
">
<ResultMapping>
<EntityTypeMapping
TypeName="NorthwindModelStoredProcedure.SimpleProduct">
<Condition
ColumnName="Discontinued" Value="0"/>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="NorthwindModelStoredProcedure.DisontinuedProduct">
<Condition
ColumnName="Discontinued" Value="1"/>
</EntityTypeMapping>
</ResultMapping>
</FunctionImportMapping>
Problem: You have created stored procedures to insert, update and delete
categories in the database. You want to make sure that when a category is
inserted, updated or deleted using entity data model, EF, instead of generating
a dynamic sql statement to perform the operation should use your stored
procedures to carry out the operation.
Screen shot below shows how the mapping is configured for insert category stored
procedure.
To map the update procedure, select update function from the dropdown which
will automatically map the procedure column names to properties on the category
entity where the names match. Since there is no return value to capture for update
we do not have to worry about result binding. Similarly to map delete procedure,
select delete function from the dropdown and map the catid column on the
procedure to categoryid property on the category entity. Screen shot below shows
the completed mapping for category crud operations.
To test that our crud operations issues calls to our stored procedure instead of
generating dynamic sql statements , we can create a new category, update its field
and delete the category from the database. During this operation, we can open our
profiler to confirm that correct stored procedures are getting executed. Code below
performs various crud operations on the category entity.
On the above example, I am creating a new category instance and marking the
category to be added by calling AddToCategories. Calling SaveChanges triggers
our InsertCategory stored procedure to be called. To call our update stored
procedure, I am simply changing the description of the category and calling
savechanges again. Since ObjectStateManager was already tracking the category
we just inserted, it knew that changing the description was update operation. To
call our deletecategory stored procedure, I am calling DeleteObject method
available on the ObjectContext. Calling SaveChanges, triggers the delete process
and causes DeleteCategory stored procedure to be called. Profiler screen shot
below confirms that operations above caused stored procedure to be called which
mapped to Category entity.
Earlier, I mentioned that if you have existing stored procedures that return primary
key generated value using output parameters, you can work around this limitation
in v1 by tweaking the ssdl file directly. When you import a stored procedure in the
ssdl file, EF creates a function in ssdl that calls your stored procedure. One of the
options available on the function is commandtext which allows you to execute
pretty much anything you can do at the database level. We can leverage the
commandtext to declare an output parameter to the stored procedure, call the stored
procedure with the output parameter declared and after executing the stored
procedure we can return the output parameter value as select clause which Entity
framework can understand. Following function declaration on the ssdl file shows
how to do that.
After executing the stored procedure the output parameter is populated with the
categoryid of the inserted category. Since Ef does not understand output
parameters, I am performing select to output the categoryid from the output
parameter. Notice that my select uses as alias for catid to return the categoryid.
This is the same alias name that we will use to apply binding on ResultBinding
section of the InsertFunction defined on the mapping layer. Code below shows
how we are mapping the parameters on the stored procedure to properties on the
entity and also binding the result of CategoryId returned from the select operation
to CategoryId property on Category entity.
<InsertFunction
FunctionName="MappingStoredProcModel.Store.InsertCategory">
<ScalarProperty Name="CategoryName"
ParameterName="catname" />
<ScalarProperty Name="Description"
ParameterName="description" />
<ResultBinding Name="CategoryID"
ColumnName="catid" />
</InsertFunction>
Problem: You want to map insert, update and delete for product entity
defined on the entity data model to stored procedures defined on the database.
The product table on the database has column for supplierid that associates
the product to a supplier. The product also belongs to a category which is
defined by categoryid column on the products table. However product entity
on the entity data model does not expose productid and supplierid as its
properties. However product entity exposes two navigation relationshsips
Supplier and Category. You want to know how to map the navigation
relationships exposed on the product entity to parameters defined on the
stored procedure.
Discussion: To start the discussion we will first see how our database
diagram looks like.
On the above screen shot, I have a product table which has many to 1
association with Categories and suppliers. This means that a given category
can have many products and a product is provided by a supplier. Additionally
product has a 1 to 1 mapping with ProductAdditionalInfo which means if the
product has additional info it would be inserted into productadditional info
table with using the productid defined by the product table. To enable crud
operation on product table, I have created 3 stored procedures shown below.
/*insert proc */
ALTER proc [dbo].[InsertProd]
(
@prodname varchar(50),
@unitprice decimal,
@catid int,
@supppid int
)
as
begin
insert into Products(ProductName,UnitPrice,CategoryID,SupplierID)
values (@prodname,@unitprice,@catid,@supppid)
select SCOPE_IDENTITY() as prodid
end
/* update proc */
ALTER proc [dbo].[UpdateProduct]
(@prodname varchar(50),
@unitprice decimal,
@supplierid int,
@categoryid int,
@prodid int)
as
begin
update Products
set ProductName = @prodname,
UnitPrice = @unitprice,
SupplierID = @supplierid,
CategoryID = @categoryid
where ProductID = @prodid
end
/* delete proc */
ALTER proc [dbo].[DeleteProduct]
(@prodid int,
@supplierid int,
@categoryid int
)
as
begin
delete Products where ProductID = @prodid
end
For delete stored proc, we are faced with the same constraint. Although for
deleting a product all you need is the productid. Since EF requires parameters for
navigation relationship, such supplier and category exposed on our product entity,
we have to provide these parameters regardless if they will be used in the
procedure or not. Once again, if you already have stored procedures on the
database that does not confirm to EF rules, you have the ability to tweak the ssdl
file to map the delete stored procedure with only productid parameter.
To perform the mapping we need to import 4 tables into our model; Category,
Product, Supplier and ProductAdditional Info. We also have to import three stored
procedures created to insert, update and delete product entity. Following screen
show how the model looks like after importing 4 tables.
On the entity diagram, product entity has many to 1 and many to 0-1 association to
category and supplier entity. However there is 0-1 association between product and
productAdditionalInfo which is very close to one to one mapping. One of
constraints enforced by entity framework is, if you map product entity using stored
procedure, any related entity, product is tied to with 1 to 1 mapping or 1 to 0
mapping must also be mapped using stored procedure. So in our case, we have to
provide stored procedure mapping to ProductAdditionInfo since it is tied to
product with 0-1 mapping. We are not obligated to provide mapping for supplier or
category because product has many to 1 association with these entities. Failing to
provide mapping for ProductAdditionalInfo raises the following error.
If an EntitySet or AssociationSet includes a function mapping, all related entity and AssociationSets
in the EntityContainer must also define function mappings. The following sets require function
mappings: FK_ProductAdditionalInfo_Products.
The above stored procedures follow the same pattern as we used for product table
stored procedures. Noticeable difference is, for insertProdAddInfo we are not
returning the identity of the productid since productid was originally created by
insertproduct stored procedure. We are in fact using the productid created after
inserting the product, to use in inserting record inside ProdAdditionalInfo table.
DeleteProdInfo stored procedure takes productid to delete additional info for a
given product.
Mapping the stored procedures to Product entity is done by selecting the column
on the procedure and mapping it to the property on the entity. For the case of
supplierid and categoryid parameter, there is no property available on the product
entity. Therefore we have to use the navigation relationship exposed on the product
entity to map the correct parameter values.
On the above screen shot, to map categoryid parameter, I have selected
CategoryId property available on the Category navigation property. Similarly
for supplierid, I have selected supplierid property on supplier entity. Entity
framework requires all 3 operations on an entity to be mapped to stored
procedure. Failing to map an operation to a stored procedure would cause the
model to not compile. If you feel that delete is not allowed for an entity, you
can tweak the ssdl layer using CommandText property to perform no
operation when a delete is executed on an entity. Modifying the model is not
supported by entity framework designer and would get overwritten if you
were to update the model from the database. Example below shows no code
executed for a delete scenario.
<Function Name="DeleteProdAddInfo" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<CommandText>
raiserror(N'Operation not permitted', 16,
1);
</CommandText>
<Parameter Name="prodid" Type="int" Mode="In" />
</Function>
<Function Name="DeleteProduct" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
<CommandText>
raiserror(N'Operation not permitted', 16,
1);
</CommandText>
<Parameter Name="prodid" Type="int" Mode="In" />
<Parameter Name="supplierid" Type="int" Mode="In" />
<Parameter Name="categoryid" Type="int" Mode="In" />
</Function>
Once the mapping for stored procedure is configured using the designer, following
definition is written for product entity on the msdl layer.
<EntitySetMapping Name="Products">
<EntityTypeMapping
TypeName="IsTypeOf(MappingStoredProcModel.Product)">
<MappingFragment
StoreEntitySet="Products">
<ScalarProperty
Name="Discontinued" ColumnName="Discontinued" />
<ScalarProperty
Name="UnitPrice" ColumnName="UnitPrice" />
<ScalarProperty
Name="ProductName" ColumnName="ProductName" />
<ScalarProperty
Name="ProductID" ColumnName="ProductID" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping
TypeName="MappingStoredProcModel.Product">
<ModificationFunctionMapping>
<InsertFunction
FunctionName="MappingStoredProcModel.Store.InsertProd">
<AssociationEnd
AssociationSet="FK_Products_Suppliers" From="Products"
To="Suppliers">
<ScalarProperty Name="CategoryID"
ParameterName="categoryid" Version="Current" />
</AssociationEnd>
<AssociationEnd
AssociationSet="FK_Products_Suppliers" From="Products"
To="Suppliers">
<ScalarProperty Name="SupplierID"
ParameterName="supplierid" Version="Current" />
</AssociationEnd>
<ScalarProperty
Name="ProductName" ParameterName="prodname" Version="Current" />
<ScalarProperty
Name="UnitPrice" ParameterName="unitprice" Version="Current" />
</UpdateFunction>
<DeleteFunction
FunctionName="MappingStoredProcModel.Store.DeleteProduct" >
<AssociationEnd
AssociationSet="FK_Products_Categories" From="Products"
To="Category">
<ScalarProperty Name="CategoryID"
ParameterName="categoryid" />
</AssociationEnd>
<AssociationEnd
AssociationSet="FK_Products_Suppliers" From="Products"
To="Suppliers">
<ScalarProperty Name="SupplierID"
ParameterName="supplierid" />
</AssociationEnd>
<ScalarProperty
Name="ProductID" ParameterName="prodid" />
</DeleteFunction>
</ModificationFunctionMapping>
</EntityTypeMapping>
</EntitySetMapping>
After configuring the mapping we can write code that forces the stored procedures
to get called such as inserting, updating and deleting a product. Code below shows
how to insert the product entity using stored procedure.
var db = new StoredProcMappingEntities();
var cat = new Category { CategoryName = "Makeups" };
On the above example, I am creating a new category and supplier and assigning it
to the entityreference on the product entity for Category and Supplier. Product also
has a 1 to 1 mapping with ProductAddditionalInfo which I am creating an instance
for inside the objectintializer. To add the product, I have to call AddToProducts
passing in the newly created product followed by SaveChanges. SaveChanges is
the method that actually triggers the insert process in the correct sequence defined
by our model. So in this case, EF framework first issues a dynamic insert statement
to insert Category and Supplier entity and then followed by Product entity.
Inserting the product entity uses our insert stored procedure which gets passed the
categoryid and supplierid of the newly created supplier and Category entity. Since
productAdditionalInfo requires a valid product to exist, entity framework is smart
enough to execute insert for ProductAdditionalInfo to be the last. For executing
insert for ProductAdditionalInfo table, entity framework uses Insert stored
procedure to carry out the insert operation. Following profiler capture shows the
execution of the code in the order performed.
To update the product entity, I am using the following code.
//update product info.
var db2 = new StoredProcMappingEntities();
var updateproduct = db2.Products.First(p =>
p.ProductName == "Hair Formula");
//change the unit price price.
updateproduct.UnitPrice = 15.0M;
db2.SaveChanges();
//change supplier and category information and
product additional info.
updateproduct.Category =
db2.Categories.FirstOrDefault(c => c.CategoryID == 1);
updateproduct.ProductAdditionalInfoReference.Load();
updateproduct.ProductAdditionalInfo.ProductDescription = "Hair
formula";
db2.SaveChanges();
On the above example, I am updating product entity two times. For the first update
only unit price is changed. Also notice that for the first update we have not
explicitly loaded Category or supplier navigation relationship even though our
stored procedure’s categoryid and supplierid is mapped to properties on our
navigation relationship. Does that mean our update will crash? The answer is no.
Entity framework under the covers materializes the entity references using the
metadata and therefore before the update stored procedure call is issued, entity
references are assigned valid references. For the second update, I am replacing the
existing category and supplier entity reference to a new category and supplier. To
change description property on ProductAdditioanalInfo entity, I have to load the
entity first and then change the description property. When SaveChanges is called
several updates are triggered. First UpdateProcedure on product is called followed
by update procedure defined for ProductAdditionalInfo. Profilers capture show
below illustrates the order of updates issued.
To delete product and its additional info, all that we have to do is pass the product
entity to DeleteObject method exposed on the ObjectContext. Code below shows
the example.
//delete updated product and additionalinfo table
db2.DeleteObject(updateproduct);
db2.SaveChanges();
Notice on the above code we did not call delete on ProdAdditionalInfo. Since
ProductAdditionalInfo has a relationship with product and cannot exists without a
product entity, calling delete on the product entity triggers an automatic delete to
be called on ProdAdditional entity as well. If we look at the profiler trace, we will
see that entity framework first calls delete procedure for productadditionalinfo
followed by product delete procedure to delete product from product table. The
order in which entity framework performs this operation is important otherwise we
can get a foreign key violation error. Profile capture shows the order in which
delete procedure is called.
Problem: You have two tables MusicalShows and Sponsor that are joined
using a link table to portray many to many relations. A musical Show can
have many sponsors and a sponsor can participate in many musical shows.
You want to map both entities defined on your model to use stored procedures
for crud operations. You want to know how many stored procedures are
required for this entire process and how to map these stored procedures to
entity data model.
Solution: When you import tables that are defined as using a link table, entity
framework will display two tables having many to many relationships.
Although there will be 2 entities on the designer, but crud operation would
require 8 stored procedures meaning 3 stored procedures for MusicalShow,
Sponsor and two stored procedure for link table called
MusicalShow_Sponsor. The two procedures for link table would be Insert and
Delete because you cannot update a link table; you can either insert a many to
many relationship or delete a many to many relationship. Mapping stored
procedures for Musical Show and Sponsor can be achieved using the
designer. However the link table mapping which immerges as an association
set in entity framework cannot currently be mapped using the designer. This
would require editing the msdl to map the association sets to stored procedure
for the link table.
On the above stored procedures, we have 3 stored procedures that take cares of
inserting, updating and deleting a sponsor. Similarly for inserting, updating and
deleting sponsors we have defined three store procedures. To insert into our link
table, show_sponsor, stored procedure takes sponsorid and showid to create a
relationship between a sponsor and a show. Similarly to delete relationship
between a sponsor and a show we have delete stored procedure for the link table
that takes showid and sponsorid to delete the relationship. After importing the
stored procedure and selecting 3 tables from the entity model wizard, we get the
following diagram that represents relationship between sponsor and musicalshow.
Notice that the relationship between MusicalShow and Sponsor is denoted by
many to many on both sides.
The next step is to map the Musicalshow entity using the stored procedures we
have imported. StoredProcedure mapping window shows how we have configured
the crud mapping for Musical Show.
The mapping for Musical Show is very similar to the mappings we have done in
other examples so I will not go into the details of how to configure it. To map
Sponsor entity to the stored procedure we have imported we can once again use the
Stored Procedure mapping window. The final result for Sponsor stored procedure
mapping is shown below.
As discussed earlier, we have to map two stored procedures we had created earlier
for the link table to the associationset defined by the entityframework. Current
version of entity framework does not support mapping associationset to stored
procedure using the designer. Therefore we need to go in msdl section of edmx file
and modify the assocaitionset that maps to Show_Sponsor entityset that happens to
be our table in the database. Code below shows the mapping required for the linked
table.
<AssociationSetMapping Name="Show_Sponsor"
TypeName="MappingStoredProcModel.Show_Sponsor"
StoreEntitySet="Show_Sponsor">
<EndProperty Name="Sponsors">
<ScalarProperty Name="SponsorId"
ColumnName="SponsorId" /></EndProperty>
<EndProperty Name="MusicalShow">
<ScalarProperty Name="ShowId" ColumnName="ShowId"
/></EndProperty>
<ModificationFunctionMapping>
<InsertFunction
FunctionName="MappingStoredProcModel.Store.insertshowsponsor">
<EndProperty Name="Sponsors">
<ScalarProperty Name="SponsorId"
ParameterName="sponsorid"/>
</EndProperty>
<EndProperty Name="MusicalShow">
<ScalarProperty Name="ShowId"
ParameterName="showid"/>
</EndProperty>
</InsertFunction>
<DeleteFunction
FunctionName="MappingStoredProcModel.Store.deleteshowsponsor">
<EndProperty Name="Sponsors">
<ScalarProperty Name="SponsorId"
ParameterName="sponsorid"/>
</EndProperty>
<EndProperty Name="MusicalShow">
<ScalarProperty Name="ShowId"
ParameterName="showid"/>
</EndProperty>
</DeleteFunction>
</ModificationFunctionMapping>
</AssociationSetMapping>
To delete the relationship we can call clear to clear all sponsors for a given
show or call remove to remove a specific sponsor from a show.
//removes alex from the sponsor_show
rockandroll.Sponsors.Remove(alex);
On the above code, I am removing alex from rockandroll show and clearing
all the sponsors for musicalshow. Removing and clearing of Sponsors would
not work unless we load all the sponsors for a given show ahead of time by
either calling Load using Include to load sponsors with their shows. Profiler
capture shows the execution path taken by entity framework to delete many to
many relationships from our link table.
On the above profiler capture we can see that deleteshowsponsor was called
three times; first for deleting alex from rockandroll show and then removing
the two sponsors we assigned earlier to musicalshow.
Both validation errors mentioned above do not prevent you from building
your solution and can be ignored. However if seeing those validation errors
annoy you, you can either get rid of complex type or do use edmx file and
generate your model using command line utility such edmgen.exe or
edmgen2.exe.
To map the complex type and the entity to stored procedure, following steps
must be followed.
Stored procedures defined above are fairly simple from the fact that they take
in parameters and insert, update and delete customer record. When we define
a complex type on Customer entity, we cannot use the designer and have to
manually edit the edmx file. Once you have edited the edmx file manually
you lose the ability to open the edmx file in the designer. Because of this
constraint you cannot even leverage the designer abilities to map crud
operation to stored procedures. One of the ways I have gotten around to this is
by not declaring the complex type initially and use the designer to import
stored procedures and my entity. After importing the stored procedure, I went
ahead and mapped customer entity to stored procedure. What this allowed me
to do is get me 80 percent through the mapping and 20 percent I went ahead
and manually edit the mapping files. Following steps I took to map the
complex type to stored procedure.
1. Import the customer crud stored procedure and customer table into
entity data model using the designer. Model browser window confirms
our import from the database.
<UpdateFunction
FunctionName="SpMappComplexType.Store.UpdateCustomer">
<ComplexProperty Name="Address"
TypeName="SpMappComplexType.CAddress">
<ScalarProperty Name="StreetAddress"
ParameterName="Address" Version="Current" />
<ScalarProperty Name="City" ParameterName="City"
Version="Current" />
<ScalarProperty Name="Zip" ParameterName="Zip"
Version="Current" />
</ComplexProperty>
<ScalarProperty Name="ContactName"
ParameterName="ContactName" Version="Current" />
<ScalarProperty Name="CustomerID"
ParameterName="CustomerID" Version="Current" />
</UpdateFunction>
After completing the mapping when you build your project, you will still get
validation errors such complex type property not mapped. Ignore these errors as
they are bug in the designer and features not completely implemented in the
designer. To insert customer entity with our complex type using stored procedure,
we can create an instance of Customer entity, initialize Address complex type will
correct values and add the customer entity to the objectcontext and call
savechanges. Code below shows how to insert customer and complex type using
stored procedures defined on the store model.
var db = new SpMappComplex();
var customer = new Customers
{
CustomerID = "ALFK1",
ContactName = "Zeeshan Hirani",
Address = new CAddress { City = "Dallas",
StreetAddress = "123 Address", Zip = "76111" }
};
db.AddToCustomers(customer);
db.SaveChanges();
db.DeleteObject(customer);
db.SaveChanges();
Problem: You have created an entity data model using table per hierarchy.
Inheritance model consists of a base class Person with two derived classes
Student and Employee. Student contains another derived class called Special
Student. So far you had successfully mapped inheritance hierarchy structure
to People table in the database. You have created insert, update and delete
stored procedure for people table in the database. You want to know how to
map these procedures to inheritance hierarchy defined on Entity data Model.
/*delete procedure */
ALTER proc [SPIH].[DeletePeople]
(@personid int)
as
begin
delete SPIH.People where personid = @personid
end
On the above stored procedure, I have set hiredate and enrollmentdate and
specialneeds to have default values of null. We are setting default values for
these parameters because all entities derived from people will use the same
procedure for mapping their crud and will not have values defined for every
parameter on the stored procedure. By setting default values ensure that
stored procedure would not crash if a derived entity does not provide value
for a parameter that is not specific for that entity. Both insert and update
stored procedure takes Category parameter that is used to define what type of
entity this record represents in the table. To map the stored procedures to our
entity data model, we have to import the people table, the 3 stored procedures
we have created and then model our people table in entity data model as table
per hierarchy. Screen shot below shows how entity data model looks like after
importing the table mapping the table to table per hierarchy structure.
After mapping the People table to inheritance hierarchy, we need to map
stored procedure to each derived entity in the model. If we try to map the
store procedures imported from the database to derived entity, the designer
will complain that we are missing mapping for certain parameters of the
procedure and mapping of all parameters for the stored procedure is required.
From our perspective this is completely a valid situation because not all
parameters are valid for a given derived entity. To get around this problem,
we have to edit the ssdl model and fake out insert and update stored procedure
for every derived entity defined on the model. We do not need a separate
delete for every derived entity because our delete stored procedure takes in
personid that is common to all derived entities. SSDL below shows our fake
stored procedure that calls our original stored procedure with default values.
<Function Name="InsertStudent" IsComposable="false">
<CommandText>
exec [SPIH].InsertPeople @name =
@name,@enrollmentdate = @enrollmentdate,@category = 1
</CommandText>
<Parameter Name="name" Type="varchar" Mode="In"
/>
<Parameter Name="enrollmentdate" Type="date"
Mode="In" />
</Function>
<Function Name="UpdateStudent" IsComposable="false">
<CommandText>
exec SPIH.[UpdatePeople]@name =
@name,@enrollmentdate = @enrollmentdate,@personid = @personid
</CommandText>
<Parameter Name="name" Type="varchar" Mode="In"
/>
<Parameter Name="enrollmentdate" Type="date"
Mode="In" />
<Parameter Name="personid" Type="int" Mode="In"
/>
</Function>
<Function Name="InsertSpecialStudent"
IsComposable="false">
<CommandText>
exec SPIH.InsertPeople
@name = @name,@enrollmentdate =
@enrollmentdate,@specialneeds = @specialneeds,@category = 3
</CommandText>
<Parameter Name="name" Type="varchar" Mode="In"
/>
<Parameter Name="enrollmentdate" Type="date"
Mode="In" />
<Parameter Name="specialneeds" Type="varchar"
Mode="In" />
</Function>
<Function Name="UpdateSpecialStudent"
IsComposable="false">
<CommandText>
exec SPIH.UpdatePeople
@name = @name,@enrollmentdate =
@enrollmentdate,@specialneeds = @specialneeds,@category =
3,@personid = @personid
</CommandText>
<Parameter Name="name" Type="varchar" Mode="In"
/>
<Parameter Name="enrollmentdate" Type="date"
Mode="In" />
<Parameter Name="specialneeds" Type="varchar"
Mode="In" />
<Parameter Name="personid" Type="int" Mode="In"
/>
</Function>
<Function Name="InsertEmployee" IsComposable="false">
<CommandText>
exec SPIH.InsertPeople
@name = @name,@hiredate =
@hiredate,@category = 2
</CommandText>
<Parameter Name="name" Type="varchar" Mode="In"
/>
<Parameter Name="hiredate" Type="date" Mode="In"
/>
</Function>
<Function Name="UpdateEmployee" IsComposable="false">
<CommandText>
exec SPIH.UpdatePeople
@name = @name,@hiredate =
@hiredate,@category = 2,@personid = @personid
</CommandText>
<Parameter Name="name" Type="varchar" Mode="In"
/>
<Parameter Name="hiredate" Type="date" Mode="In"
/>
<Parameter Name="personid" Type="int" Mode="In"
/>
</Function>
After creating these procedures, we can go back to the model in the designer
and map these procedures to every derived entity. There will be no mapping
for the base Person entity because it is marked as abstract and entity
framework does not allow stored procedure mapping to abstract entity. Below
is the screen shot for mapping Student entity.
Now that we have completed our mapping we can create instance of derived
entities and call save changes to save entities to the database. Saving entity
would trigger Insert procedure specific to each entity to get called. Code
below shows how to insert derived entities to the database.
var db = new SPInhMapping();
var spstudent = new SpecialStudent
{
EnrollmentDate = DateTime.Now,
Name = "James",
SpecialNeeds = "Hearing"
};
var student = new Student
{
EnrollmentDate = DateTime.Now,
Name = "Zeeshan Hirani"
};
var employee = new Employee
{
HireDate = DateTime.Now,
Name = "Alex"
};
db.AddToPersons(spstudent);
db.AddToPersons(student);
db.AddToPersons(employee);
db.SaveChanges();
The above code creates 3 different derived entities and adds them to the
Persons method followed by SaveChanges. This triggers the insert procedure
to be called. If you open up the profiler, you will see that it is the same
procedure InsertPeople getting called just with different parameters.
Problem: You have created an author entity that maps to author table in the
database. Authors table contains a timestamp column that gets updated when
a row is changed. You have created stored procedures for inserts, updates and
delete to author’s table. You want to map these procedures to author entity
defined on the EDM but want to ensure that these stored procedures observe
concurrency and do not update the row if the timestamp value passed in to the
stored procedure does not match with the current time stamp value defined for
the row being updated.
Solution: To use stored procedures with concurrency option, you need to map
the time stamp parameter of the stored procedure to TimeStamp property on
author entity using original value option. In addition on the Update stored
procedure, the where clause needs to include the original timestamp value
passed in the parameter along with the primary key column. The number of
rows affected needs to be communicated back to entity framework using
output parameter. When entity framework sees that a row affected is zero, it
throws concurrency violation exception.
select a.AuthorId,a.[TimeStamp]
from Authors a where a.AuthorId = SCOPE_IDENTITY()
end
end
To map the 3 stored procedures we can import the author’s table along with 3
stored procedures. Screen shot below shows the insert, update and delete
stored procedure mapping for author entity.
To test that our stored procedure handles the optimistic concurrency we can
insert author record and then update the record using a separate datacontext
and on updating again using the original datacontext would raise
OptimisticConcurrencyVoilation. Code below shows exception being thrown
when rowsaffected is returned zero from the stored procedure.
var db = new SpConcurrency();
var author = new Author{Name =
"Zeeshan",BooksPublished = 1};
db.AddToAuthors(author);
db.SaveChanges();
Console.WriteLine("authorid {0} timestamp
{1}",author.AuthorId,
Convert.ToBase64String(author.TimeStamp));
//cause concurrency voilation
db.Connection.Open();
db.CreateStoreCommand("update authors set
BooksPublished = 2 where name = 'Zeeshan'")
.ExecuteNonQuery();
db.Connection.Close();
author.Name = "Zeeshan Hirani";
try
{
int result = db.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
Console.WriteLine("Concurreny voilation on
update " + ex.Message);
}
db.Refresh(System.Data.Objects.RefreshMode.ClientWins, author);
author.Name = "Zeeshan Hirani";
//update author name succeeds
db.SaveChanges();
//delete to author succeeds.
db.DeleteObject(author);
db.SaveChanges();
Console.WriteLine("Author delete successfully");
You need to how entity framework extensions fill the gap by providing the
above features and how you can use it to read data from database and
execute arbitrary commands.
Solution:
Discussion: Entity framework Extensions extends Object Context class with
extension methods and provides a new class EntitySet that inherits from
ObjectContext class providing several utility methods. These methods help
solve common tasks that are sometimes fairly trivial to do if you are directly
coding against entity framework. In some cases EF extensions fills in the
gap for features that did not make it to version 1 release of the product. In
this segment we will explore different feature set that helps working with
entity framework easier.
In the above code, the tedious work lies in creating an entity key. To create
an entity key for category entity, I am specifying NorthWindEntities as my
container and Categories as my entityset which contains my category
entities. For the second parameter, I am specifying CategoryID as my key
and third parameter being the value for the CategoryID. The above code is
redundant because Meta data in our conceptual model contains these
definitions. When we use EF extensions, it gets rid of additional details by
leveraging the conceptual model and allows us to simply specify a value for
entity key. Code below shows how to achieve the same thing using EF
extensions.
var product2 = new Product { ProductName = "Product2" };
db.ProductSet.InsertOnSaveChanges(product2);
//allow setting key without meta data.
product2.CategoriesReference.SetKey(1);
db.SaveChanges();
//reading key value
Console.WriteLine("Product CategoryId
{0}",product2.CategoriesReference.GetKey().ToString());
//can also retrieve meta data and value back.
Console.WriteLine("Category belongs to {0}
EntitySet",product.CategoriesReference.GetTargetEntitySet()
.Name);
With entity framework you are required to import the stored procedure into
SSDL, CSDL and then provider the mapping. In addition the stored
procedure must return an entity defined on the conceptual modal. This is too
much work if you want to simply execute a stored procedure or arbitrary sql
statement to get either an entity or any clr object back. Code below shows
how to perform these simple tasks using EF extensions with few lines of
code.
public class ProductPartial
{
public int ProductID { get; set; }
public string ProductName { get; set; }
}
//SettingEntityReference();
//get all categories using dynamic sql.
var db = new NorthwindEFEntities();
var categories = db.CreateStoreCommand("select *
from categories").Materialize<Category>();
Console.WriteLine("Total Categories {0}
",categories.Count());
Entity framework extensions allow you to use any arbitrary stored procedure
defined on the database without registering the stored procedure with entity
data model. Example below is a stored procedure that returns two results.
First result returns suppliers for products with UnitPrice > 90 dollars.
Second result returns productid and ProductName of products with UnitPrice
greater than 90 dollars.
create proc dbo.EFExMultipleResult
as
begin
select s.*
from Suppliers s join Products as p on s.SupplierID = p.SupplierID
where UnitPrice > 90
select ProductID,ProductName
from Products where UnitPrice > 90
end
{
suppliers = multiplecmd.Materialize<Suppliers>();
reader.NextResult();
prods3 = multiplecmd.Materialize<ProductPartial>();
reader.NextResult();
// quanityordered = multiplecmd.ExecuteScalar();
}
Console.WriteLine("Results from multiplerestult set");
Console.WriteLine("Total Suppliers {0}",suppliers.Count());
Console.WriteLine("Total Products {0}",prods3.Count());
Category cat;
Screen shot below shows the results returned from the stored procedure.
By default the entities returned using the extension methods are not tracked
by the ObjectStateManager, therefore update and inserts and delete cannot
be performed. To overcome this problem, EF extensions expose a Bind
method on Entityset to attach these entities to the context passed in.
EntitySet.Attach
If you have an entity that you received from WCF service, session or
viewstate, in order to perform update, delete or insert you have first register
the entity with EF. To register an entity you can either use Attach or
AttachTo but you have to fully qualify the EntityKey that includes the
EntityContainer, entity key property and entity key property value. With
Attach method on EntitySet you can attach an entity without specifying an
entity container or creating an entity key. Attach method creates an entity
key if it is not provided. Code below shows the use of Attach method.
var db = new NorthwindEFEntities();
var category = new Category { CategoryID = 1,CategoryName =
"Beverages" };
//no need for entitykey or entity container.
db.CategorySet.Attach(category);
Console.WriteLine(db.ObjectStateManager.GetObjectStateEntry(category).S
tate);
IQueryable Extensions
ToTraceString
When you write query using linq to entities or esql, ObjectQuery converts
the command trees to into sql that is send to database for execution. To get
the sql that is created ObjectQuery exposes ToTraceString method that
returns the sql statement. However if there your query returns an IQueryable
there is no way to access the sql statement unless you cast the results back to
ObjectQuery and than get reference to ToTraceString method. With EF
extensions, IQueryable also has a similar method which allows you call
ToTraceString to get the sql generated. Code below calls ToTraceQuery
extension method on IQueryable.
var db = new NorthwindEFEntities();
//using ToTraceString on IQueryable.
IQueryable<Product> products = db.Products.Where(p
=> p.UnitPrice > 20).Take(10);
Console.WriteLine(products.ToTraceString());
Include
ObjectQuery has an include method that allows you to eagerly load related
entities when fetching an entity or entities from the database. If the query
returns an IQueryable, you do not get an option to specify an Include clause.
One option is to cast IQueryable query to ObjectQuery and then specify and
Include statement. EF extensions extends an IQueryable with an Include
method so a query returning an IQueryable can specify addition related
entities it wants to eagerly load. Example below calls Include on IQueryable
of categoryies to load products for those categories. Since we are only
interested in the first category, I use first operator to only take the first
category. To confirm that are products are loaded as well I am printing the
total products for the category retrieved from the database.
//Products included with category
var category =
db.Categories.Take(2).Include("Products").First();
Console.WriteLine("Total Products for category:
" + category.Products.Count());