What Will I Learn?
- You will learn using the Entity Framework
Requirements
- Entity Framework
Difficulty
- Intermediate
Tutorial Contents
- Entity Framework Overview
- Getting the Data
- Displaying the Data
- Relationships
- Modifying Data/Structuring Code
Getting Started With Entity Framework
At some point in application development, we need data from a database. Almost 99% of all applications pull data from a database of some kind, yet the mechanism to get the data from the database to the UI is one of the often debated subjects in development. The object-relational mapper (ORM) tools dominate the market as the means of choice for querying data; however, finding the right one to use, especially with all the diverse conversation via articles and blogs, can be the challenge.
In this tutorial, we'll examine using the Entity Framework. The last version has received some major upgrades, with some major API enhancements and fixes for most of the pain points within the designer.
Entity Framework Overview
To start, let's look at a sample model. The following model below was generated from the database, one of several ways that a model is created. Take a look at the sample model below.
Figure 1: Our Sample Entity Framework
This model mimics the database structure, but one of the benefits of ADO.NET EF is that it's flexible enough to merge tables into one entity or split a single table into multiple entities as you see fit. Each class contains a mapping between the database table and the model class(es) that represent it. I'm not going to go into the specifics of the model as I assume you have a familiarity with Entity Framework; instead, I'm going to examine how ASP.NET MVC uses a model to render data.
Getting the Data
Entity framework provides several ways to get the data from the database into your code. Here are the options to get the data at a quick glance:
- Entity SQL – Provide inline SQL statements as a string, with some variants to the syntax as compared to pure SQL. You define a query string and it returns either the entity being queried; however, queries are not restricted just to an entity type.
- Stored Procedures – Stored procedures can be imported as a function, which is called directly.
- LINQ Queries, the option utilized in this tutorial. There are two types of queries, LINQ queries and the extension methods that perform equivalent querying capabilities.
Entity Framework queries translate into database SQL statements to get the data, returning the results of that query as class instances that contains all of our data. Plus, with the new lazy loading feature, we no longer need to worry about loading the related references, we just use the object model and everything works great. More on this feature later.
We begin by displaying information about our customer. Our customer is represented by the Customer class generated by the Entity Framework model, which looks like the following:
Listing 1: A Partial Listing of the Customer class
[EdmEntityTypeAttribute(NamespaceName="DotNetSamples.DataAccess.LinqToEntities", Name="Customer")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Customer : EntityObject
{
#region Factory Method
public static Customer CreateCustomer(global::System.Int32 customerID, global::System.String firstName, global::System.String lastName, global::System.String addressLine1, global::System.String city, global::System.String state, global::System.String zipCode, global::System.String mainPhone)
{
Customer customer = new Customer();
customer.CustomerID = customerID;
customer.FirstName = firstName;
customer.LastName = lastName;
customer.AddressLine1 = addressLine1;
customer.City = city;
customer.State = state;
customer.ZipCode = zipCode;
customer.MainPhone = mainPhone;
return customer;
}
#endregion
#region Primitive Properties
[EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int32 CustomerID
{
get
{
return _CustomerID;
}
set
{
if (_CustomerID != value)
{
OnCustomerIDChanging(value);
ReportPropertyChanging("CustomerID");
_CustomerID = StructuralObject.SetValidValue(value);
ReportPropertyChanged("CustomerID");
OnCustomerIDChanged();
}
}
}
private global::System.Int32 _CustomerID;
partial void OnCustomerIDChanging(global::System.Int32 value);
partial void OnCustomerIDChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String FirstName
{
get
{
return _FirstName;
}
set
{
OnFirstNameChanging(value);
ReportPropertyChanging("FirstName");
_FirstName = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("FirstName");
OnFirstNameChanged();
}
}
private global::System.String _FirstName;
partial void OnFirstNameChanging(global::System.String value);
partial void OnFirstNameChanged();
.
.
.
#region Navigation Properties
///
/// No Metadata Documentation available.
///
[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("DotNetSamples.DataAccess.LinqToEntities", "FK_Orders_Customers", "Orders")]
public EntityCollection<Order> Orders
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Order>("DotNetSamples.DataAccess.LinqToEntities.FK_Orders_Customers", "Orders");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Order>("DotNetSamples.DataAccess.LinqToEntities.FK_Orders_Customers", "Orders", value);
}
}
}
#endregion
}
There is a lot to the code sample, but most of it relates to internal plumbing. Some of what we have here:
- Variables to store the data.
- Custom attributes to define additional attributes about the property.
- Partial methods to tap into property changing notifications, etc.
- Navigation properties useful for drilling through the model from primary key to foreign key.
- Static create method that accepts all of the properties of the object.
- DataContract and DataMember are WCF-specific attributes for defining the data contract.
It really doesn't matter what the entity does within its plumbing, because most of it is internal. What you will actually use is the properties, the navigation property references, and possibly reflect upon the attributes of the class/properties. Our customer example uses some simple queries to fetch data and I've attached the sample repository used to generate this example:
Listing 2: Getting the Data from a Repository
public class CustomerRepository : BaseRepository<Customer>
{
public Customer Get(int key)
{
var ctx = CreateContext();
return (from c in ctx.Customers
where c.CustomerID == key
select c).FirstOrDefault();
}
public IQueryable<Customer> GetByEmail(string email)
{
email = email.Trim();
var ctx = CreateContext();
return ctx.Customers.Where(i => i.Email == email);
}
public IQueryable<Customer> GetByPhone(string phone)
{
phone = phone.Trim();
var ctx = CreateContext();
return ctx.Customers.Where(i => i.MainPhone == phone || i.MainPhone.EndsWith(phone));
}
public IQueryable<Customer> GetByOrderFiscalQuarter(int year, int quarter)
{
var ctx = CreateContext();
return (from c in ctx.Customers
join o in ctx.Orders
on c.CustomerID equals o.CustomerID
where o.FiscalYear == year
and o.FiscalQuarter = quarter
select c);
}
}
All of these examples translates using the same process. A SQL query is constructed, the query is executed, and the results are parsed and instantiated as Customer objects. Some return multiple records, some only one. You have to take care in knowing how the process translates the data when using specialty comparison operators (like my EndsWith example), as some operators aren't supported.
Next we have the controller that uses the repository. Note the controller is agnostic to the backend implementation approach, and only knows that we are using a Customer class.
Listing 3: Getting the Data from the Controller
public class CustomersController : Controller
{
public ActionResult Get(int id)
{
var repos = new CustomerRepository();
var customer = repos.Get(id);
if (customer == null)
throw new Exception("The customer could not be found with ID: " + id.ToString());
return View(customer);
}
public ActionResult GetAll(int? pageIndex, int? pageSize)
{
var repos = new CustomerRepository();
return View(repos.GetAll(pageIndex ?? 0, pageSize ?? 25));
}
}
The data from the repository gets pushed down to the view. We're going to skip the view, since the core of the discussion is related to the Entity Framework, and examine the partial views that use our customer object, using ASP.NET MVC's templating approach to displaying model data.
Displaying the Data
Our first partial view displays a read-only form of the data. Note the new colon syntax for encoding the resulting data to the UI. It's pretty simple enough, as the ORM generates simple-enough classes for working with the data.
<table cellspacing="0" border="0">
<thead></thead>
<tbody>
<tr>
<td>First Name</td>
<td><%: Model.FirstName %></td>
</tr>
<tr>
<td>Last Name</td>
<td><%: Model.LastName %></td>
</tr>
<tr>
<td>Address Line 1</td>
<td><%: Model.AddressLine1 %></td>
</tr>
.
.
.
</tbody>
</table>
Note the <%: %> syntax; this automatically encodes the output to ensure it is safe. Next, the edit view uses textboxes to input the customer data. We've seen this in a previous article in this MVC series as well.
<% if (Model != null) { %>
<%= Html.HiddenFor(i => i.CustomerID) %>
<% } %>
<table cellspacing="0" border="0">
<thead></thead>
<tbody>
<tr>
<td>First Name</td>
<td><%= Html.TextBoxFor(i => i.FirstName) %></td>
</tr>
<tr>
<td>Las tName</td>
<td><%= Html.TextBoxFor(i => i.LastName) %></td>
</tr>
<tr>
<td>Address Line 1</td>
<td><%= Html.TextBoxFor(i => i.AddressLine1)%></td>
</tr>
.
.
</tbody>
</table>
Note the special conditioning at the top. Only if the object is being updated, not inserted, do I render a hidden field to store the ID. Storing the ID is important as it allows me to directly attach the object to the backend database. Notice how we can directly reference the data directly in the UI.
Relationships
Relationships to objects in the previous version of Entity Framework had to be loaded by doing something like the following:
Listing 4: Loading a Relationship
if (!customer.Orders.IsLoaded)
customer.Orders.Load();
This was a huge pain for MVC views, especially when using a third-party product that uses a Fluent API like the Telerik ASP.NET MVC components, or when you have to call this method for objects in bulk. It was also painful in cases where the database is third normal form, and the UI must display data from customer object, its Orders relationship, and then the Order's Products relationship, and so on.
I wrote an extension to load the references in a fluent manner that didn't break my chaining of relationships, which was very helpful. However, we no longer have to worry about this as an issue thanks to the new Lazy Loading implementation. Similar to LINQ to SQL, when the customer.Orders property is accessed, ADO.NET Entity Framework loads the collection of Orders immediately and we can continue doing our work.
This allows me to render hierarchies very easily, as in the following grid implementation:
<% if (Model.CustomerID.HasValue) { %>
<tr>
<td>Customer</td>
<td>
<%: Model.Customer.FirstName%>
<%: Model.Customer.LastName%>
</td>
</tr>
<% } %>
<% if (Model.OrganizationID.HasValue) { %>
<tr>
<td>Organization</td>
<td>
<%: Model.Organization.OrganizationName %>
</td>
</tr>
<% } %>
Notice the references to Customer and Organization from the model; previously, we would have been required to call the Load() method or fetch the record via eager loading. Now it works like a charm.
We can navigate as many properties as we like too; for instance, our code could call Model.Customer.Region.RegionType, and LINQ to Entities would load up all of the relationship objects.
Modifying Data/Structuring Code
We will look in part 2 of this mini-series at how updates are done and ways you can structure your code that utilizes entity framework. We'll also look at how we can attach our Customer class to the object context to make updating even easier, and try to create one common, generic repository.
Conclusion
Entity Framework generates classes that represent our database structure. An object context allows us to query data from the database it represents and translates it into business objects. These objects can be rendered directly in the UI, whether in views or partial views, to display meaningful data to the user.
Posted on Utopian.io - Rewarding Open Source Contributors