5. Querying the Data Model
Now that you've taken a tour of the data model code, you're ready to use it in your application. In fact, it's remarkably easy.
Consider the simple data-bound grid of products shown in Figure 7.
To create this page, you simply need to instantiate the
NorthwindEntities class (from the NorthwindModel namespace), retrieve
the collection or Product objects from Products property, and call
DataBind():
Dim entities As New NorthwindEntities()
GridView1.DataSource = entities.Products
GridView1.DataBind()
The NorthwindEntities
class, like all context classes, has three constructors. If you use the
no-argument constructor (as in this example), it uses the connection
string that's defined in the web.config file. Alternatively, you can use
a constructor that allows you to supply the connection string as a
string or EntityConnection object.
6. Handling Errors
Whenever you access a
database, you need to use the proper error handling code, and the Entity
Framework is no exception. However, the Entity Framework can easily
mislead developers into placing their exception handling code in the
wrong place.
As you know, the risky part of
data access is the connected operations you perform—for example, opening
a connection and executing SQL commands. Your attempt to contact the
database or communicate it with can fail for a variety of reasons. The
database server could be offline, the database server could be swamped
by too many simultaneous connections, the table you want to use might
not exist, and so on. Based on these considerations, you probably expect
to place your exception handling code around the code that reads the
Products collection. In the previous example, that means this statement
here:
Dim entities As New NorthwindEntities()
GridView1.DataSource = entities.Products
GridView1.DataBind()
This seems reasonable, but
it isn't correct. Although this line of code appears to access the
Products collection, it actually retrieves something different—a special
iterator object that's able to get the data you need as soon as you
attempt to use it. This sleight of hand is part of LINQ's deferred execution feature, which you learned about earlier.
Usually, database
operations with the Entity Framework happen later than you expect. They
don't happen when you create the context object or access its
properties. Instead, they occur when you iterate over a collection of
entities or when you access a single entity object. In the previous
example, the database work actually takes place when you call
GridView1.DataBind(), because this tells the GridView to iterate over
the Products collection and then triggers the Entity Framework to fetch
your data.
Thus, the correct error handling solution looks like this:
Dim entities As New NorthwindEntities()
GridView1.DataSource = entities.Products
Try
GridView1.DataBind()
Catch err As Exception
' (Do something here, like displaying an error message.)
...
End Try
NOTE
As you'll see a bit
later, you can force the Entity Framework to load your data earlier.
However, doing so may compromise the performance or the flexibility of
your code. Most of the time, you'll stick with deferred execution,
despite the potential confusion it causes.
Unfortunately, error
handling in an application that uses the Entity Framework is not always
this straightforward. In more complex examples, database operations
might take place in several places. For example, in the following
section you'll see how you can use relationships to show related
information from different tables. When taking this approach, a database
operation occurs whenever you begin iterating over a new collection of
records with a For Each block.
So, what's the solution? One
approach is to use the ToList() method, which converts any entity
collection into an ordinary in-memory collection, triggering the
database query in the process:
Dim entities As New NorthwindEntities()
GridView1.DataSource = entities.Products.ToList()
GridView1.DataBind()
Although this makes the data access operations more predictable, it's a dangerous practice. Deferred execution allows LINQ and the Entity Framework to
optimize database queries. If you get into the habit of calling
ToList(), you effectively circumvent this mechanism, which can harm the
performance of your application.
If you don't want to worry
about the precise code location where an exception might occur, you can
use a better solution—just wrap everything in a giant exception handling
block that catches database-specific exceptions. The most
straightforward way to do this is to move your data access code to
another method. Then you can wrap your method call in an exception
block, like this:
Try
' Call the method that does all the data processing and display.
DataBindProductGrid()
Catch err As SqlException
...
End Try
7. Navigating Relationships
The Entity Framework has a
particularly elegant way of dealing with relationships. Rather than
forcing you to use specific methods or separate queries, you can simply
move from one record to its related data using the navigation properties
that are defined in the data model.
It all starts with the
entity model diagram. There, you'll see dotted lines that indicate each
relationship. For example, consider the portion of the diagram shown in Figure 8.
The 0..1, 1, and * characters at either end of the dotted line give you additional information about the relationship.
For example, a relationship
line that goes from 1 to * (like the one between the Order and
Order_Detail entities) indicates a one-to-many relationship. Each parent
record in the first table (Order), where the 1 is shown, has one or
more matches in the child table (Order_Detail), where the * is shown.
(To actually figure out what fields are involved in a given
relationship, you need to double-click the relationship line.)
A relationship line that goes from 0..1 to * indicates a one-to-many relationship that allows null values. In other words, the 0 represents that there may be no match. For example, in the data model shown in Figure 8,
the Order.CustomerID property can be null. That means that for each
Order record, there may be one matching Customer entity or none.
A relationship line that goes
from * to * indicates a many-to-many relationship that's connected with
the help of a junction table. For example, in the Northwind database
every Customer record can have one or more associated
CustomerDemographic records, and every CustomerDemographic record can
have one or more associated Customer records. To implement this design,
the database uses a junction table named CustomerCustomerDemo.
Essentially, each record in CustomerCustomerDemo is a pairing that links
one record in Customers to one record in CustomerDemographics. (In
fact, the CustomerCustomerDemo has just two fields: CustomerID, which
points to the Customers table, and CustomerTypeID, which points to the
CustomerDemographics table.)
This setup may seem a bit
awkward, but it's a standard part of database diagram. The neat part is
that the Entity Framework is intelligent enough to pick up on this sort
of association. Instead of translating the table model to your entity
classes exactly (and creating a Customer, CustomerDemographic, and
CustomerCustomerDemo entity), the Entity Framework generates just the
Customer and CustomerDemographic entities. It links them together using
the power of collections, giving you a more natural object model to work
with.
And best of all, the Entity
Framework manages the CustomerCustomerDemo table behind the scenes. For
example, if you add a new related CustomerDemographic for an existing
Customer , the Entity
Framework automatically creates the CustomerCustomerDemo record that
connects them.
|
The relationships that appear
in your entity model diagram are also represented through navigation
properties. For example, if you look at the bottom of the Order box in Figure 24-10,
you'll see four navigation properties. The Customer, Employee, and
Shipper properties return a linked parent record (or null (Nothing) if
none exists). In other words, Order.Customer returns the Customer who
placed the order. The Order_Details property returns a collection of
linked child records (as indicated by the plural property name). In
other words, Order.Order_Details provides a collection of all the order
items in a given order, as Order_Detail objects.
Here's an example that uses
these navigation properties to display some information in a page, with
no data binding. It begins by retrieving a list of customers and then
retrieves some information about the orders made by each customer:
Dim entities As New NorthwindEntities()
' Build a string full of HTML markup.
Dim sb As New StringBuilder()
For Each customer As Customer In entities.Customers
' Write out the customer information in bold.
sb.Append("<b>")
sb.Append(customer.CompanyName)
sb.Append("</b><br />")
' List this customer's orders.
For Each order As Order In customer.Orders
sb.Append(order.OrderID.ToString())
sb.Append(" - made on date: ")
sb.Append(order.OrderDate.Value.ToShortDateString())
sb.Append("<br />")
Next
' Add a horizontal line.
sb.Append("<hr /><br />")
Next
' Show the HTML in a Label control.
lblData.Text = sb.ToString()
Figure 9 shows the result.
Writing exception handling
logic for this sort of code can be tricky. The database operations occur
whenever you start a For Each block (either to process the list of
customers or to look at the orders for a given customer). To prevent
your code from descending into a tangle of spaghetti-style exception
handlers, it's best to put all this logic in a dedicated method and wrap
your method call in an exception handling block, as described in the
previous section. |