This chapter is taken from book "Moving to Microsoft Visual Studio 2010" by
Patrice Pelland, Pascel Pare, and Ken Haines published for Microsoft Press.
After reading this chapter, you will be able to
- Use the Entity Framework (EF) to build a
data access layer using an existing database or with the Model-First
approach
- Generate entity types from the Entity Data
Model (EDM) Designer using the ADO.NET Entity Framework POCO templates
- Get data from Web services
- Learn about data caching using the
Microsoft Windows Server AppFabric (formerly known by the codename
"Velocity")
Application Architecture
The Plan My Night (PMN) application allows the user to manage his itinerary
activities and share them with others. The data is stored in a Microsoft SQL
Server database. Activities are gathered from searches to the Bing Maps Web
services.
Let's have a look at the high-level block model of the data model for the
application, which is shown in Figure 5-1.
FIGURE 5-1 Plan My Night application architecture diagram
Defining contracts and entity classes that are cleared of any
persistence-related code constraints allows us to put them in an assembly that
has no persistence-aware code. This approach ensures a clean separation between
the Presentation and Data layers.
Let's identify the contract interfaces for the major components of the PMN
application:
- IItinerariesRepository is the interface to
our data store (a Microsoft SQL Server database).
- IActivitiesRepository allows us to search
for activities (using Bing Maps Web services).
- ICachingProvider provides us with our
data-caching interface (ASP.NET caching or Windows Server AppFabric
caching).
Note This is not an exhaustive list of
the contracts implemented in the PMN application.
PMN stores the user's itineraries into an SQL database. Other users will be able
to comment and rate each other's itineraries. Figure 5-2 shows the tables used
by the PMN application.
FIGURE 5-2 PlanMyNight database schema
Important The Plan My Night application uses the ASP.NET Membership
feature to provide secure credential storage for the users. The user store
tables are not shown in Figure 5-2. You can learn more about this feature on
MSDN: ASP.NET 4 - Introduction to Membership (http://msdn.microsoft.com/en-us/library/yh26yfzy(VS.100).aspx).
Note The ZipCode table is used as a reference repository to provide a
list of available Zip Codes and cities so that you can provide autocomplete
functionality when the user is entering a search query in the application.
Plan My Night Data in Microsoft Visual Studio 2005
It would be straightforward to create the Plan My Night application in Visual
Studio 2005 because it offers all the required tools to help you to code the
application. However, some of the technologies used back then required you to
write a lot more code to achieve the same goals.
Let's take a look at how you could create the required data layer in Visual
Studio 2005. One approach would have been to write the data layer using ADO.NET
DataSet or DataReader directly. (See Figure 5-3.) This solution offers you great
flexibility because you have complete control over access to the database. On
the other hand, it also has some drawbacks:
- You need to know the SQL syntax.
- All queries are specialized. A change in
requirement or in the tables will force you to update the queries affected
by these changes.
- You need to map the properties of your
entity classes using the column name, which is a tedious and error-prone
process.
- You have to manage the relations between
tables yourself.
FIGURE 5-3 ADO.NET Insert query
Another approach would be to use the DataSet
designer available in Visual Studio 2005. Starting from a database with the PMN
tables, you could use the TableAdapter Configuration Wizard to import the
database tables as shown in Figure 5-4. The generated code offers you a typed
DataSet. One of the benefits is type checking at design time, which gives you
the advantage of statement completion. There are still some pain points with
this approach:
- You still need to know the SQL syntax,
although you have access to the query builder directly from the DataSet
designer.
- You still need to write specialized SQL
queries to match each of the requirements of your data contracts.
- You have no control of the generated
classes. For example, changing the DataSet to add or remove a query for a
table will rebuild the generated TableAdapter classes and might change the
index used for a query. This makes it difficult to write predictable code
using these generated items.
- The generated classes associated with the
tables are persistence aware, so you will have to create another set of
simple entities and copy the data from one to the other. This means more
processing and memory usage.
FIGURE 5-4 DataSet designer in Visual Studio 2005
In the next sections of this chapter, you'll
explore some of the new features of Visual Studio 2010 that will help you create
the PMN data layer with less code, give you more control of the generated code,
and allow you to easily maintain and expand it.
Data with the Entity Framework in Visual Studio 2010
The ADO.NET Entity Framework (EF) allows you to easily create the data access
layer for an application by abstracting the data from the database and exposing
a model closer to the business requirements of the application. The EF has been
considerably enhanced in the .NET Framework 4 release.
See Also The MSDN Data Developer Center offers a lot of resources about the
ADO.NET Entity Framework (http://msdn.microsoft.com/en-us/data/aa937723.aspx)
in .NET 4.
You'll use the PlanMyNight project as an example of how to build an application
using some of the features of the EF. The next two sections demonstrate two
different approaches to generating the data model of PMN. In the first one, you
let the EF generate the Entity Data Model (EDM) from an existing database. In
the second part, you use a Model First approach, where you first create the
entities from the EF designer and generate the Data Definition Language (DDL)
scripts to create a database that can store your EDM.
EF: Importing an Existing Database
You'll start with an existing solution that already defines the main projects of
the PMN application. If you installed the companion content at the default
location, you'll find the solution at this location: %userprofile%\Documents\Microsoft
Press\Moving to Visual Studio 2010\Chapter 5\Code\ExistingDatabase. Double-click
the PlanMyNight.sln file.
This solution includes all the projects in the following list, as shown in
Figure 5-5:
- PlanMyNight.Data: Application data layer
- PlanMyNight.Contracts: Entities and
contracts
- PlanMyNight.Bing: Bing Maps services
- PlanMyNight.Web: Presentation layer
- PlanMyNight.AppFabricCaching: AppFabric
caching
FIGURE 5-5 PlanMyNight solution
The EF allows you to easily import an existing
database. Let's walk through this process.
The first step is to add an EDM to the PlanMyNight.Data project. Right-click the
PlanMyNight.Data project, select Add, and then choose New Item. Select the
ADO.NET Entity Data Model item, and change its name to PlanMyNight.edmx, as
shown in Figure 5-6.
FIGURE 5-6 Add New Item dialog with ADO.NET Entity Data Model selected
The first dialog of the Entity Data Model Wizard allows you to choose the model
content. You'll generate the model from an existing database. Select Generate
From Database and then click Next.
You need to connect to an existing database file. Click New Connection. Select
Microsoft SQL Server Database File from the Choose Data Source dialog, and click
Continue. Select the %userprofile%\Documents\Microsoft Press\Moving to Visual
Studio 2010\Chapter 5\ExistingDatabase\PlanMyNight.Web\App_Data\PlanMyNight.mdf
file. (See Figure 5-7. )
FIGURE 5-7 EDM Wizard database connection
Leave the other fields in the form as is for now and click Next.
Note You'll get a warning stating that the local data file is not in the current
project. Click No to close the dialog because you do not want to copy the
database file to the current project.
From the Choose Your Database Objects dialog, select the Itinerary,
ItineraryActivities, ItineraryComment, ItineraryRating, and ZipCode tables and
the UserProfile view. Select the RetrieveItinerariesWithinArea stored procedure.
Change the Model Namespace value to Entities as shown in Figure 5-8.
FIGURE 5-8 EDM Wizard, Choose Your Database Objects page
Click Finish to generate your EDM.
Fixing the Generated Data Model
You now have a model representing a set of entities matching your database.
The wizard has generated all the
navigation properties associated with the foreign keys from the database.
The PMN application requires only the navigation property ItineraryActivities
from the Itinerary table, so you can go ahead and delete all the other
navigation properties. You'll also need to rename the ItineraryActivities
navigation property to Activities. Refer to Figure 5-9 for the updated model.
FIGURE 5-9 Model imported from the PlanMyNight database
Notice that one of the properties of the ZipCode entity has been
generated with the name ZipCode1 because the table itself is already named
ZipCode and the name has to be unique. Let's fix the property name by
double-clicking it. Change the name to Code, as shown in Figure 5-10.
FIGURE 5-10 ZipCode entity
Build the solution by pressing Ctrl+Shift+B. When looking at the output window,
you'll notice two messages from the generated EDM. You can discard the first one
because the Location column is not required in PMN. The second message reads as
follows:
The table/view 'dbo.UserProfile' does not have a primary key defined and no
valid primary key could be inferred. This table/view has been excluded. To use
the entity, you will need to review your schema, add the correct keys, and
uncomment it.
When looking at the UserProfile view, you'll notice it does not explicitly
define a primary key even though the UserName column is unique.
You need to modify the EDM manually to fix the UserProfile view mapping so that
you can access the UserProfile data from the application.
From the project explorer, right-click the PlanMyNight.edmx file and then select
Open With. Choose XML (Text) Editor from the Open With dialog as shown in Figure
5-11. Click OK to open the XML file associated with your model.
FIGURE 5-11 Open PlanMyNight.edmx in the XML Editor
Note You'll get a warning stating that the PlanMyNight.edmx file is
already open. Click Yes to close it.
The generated code was commented out by the code-generation tool because there
was no primary key defined. To be able to use the UserProfile view from the
designer, you need to uncomment the UserProfile entity type and add the Key tag
to it. Search for UserProfile in the file. Uncomment the entity type, add a Key
tag and set its name to UserName, and make the UserName property not nullable.
Refer to Listing 5-1 to see the updated entity type.
LISTING 5-1 UserProfile Entity Type XML Definition
<EntityType
Name="UserProfile">
<Key>
<PropertyRef
Name="UserName"/>
</Key>
<Property
Name="UserName"
Type="uniqueidentifier"
Nullable="false"
/>
<Property
Name="FullName"
Type="varchar"
MaxLength="500"
/>
<Property
Name="City"
Type="varchar"
MaxLength="500"
/>
<Property
Name="State"
Type="varchar"
MaxLength="500"
/>
<Property
Name="PreferredActivityTypeId"
Type="int"
/>
</EntityType>
If you close the XML file and try to open the EDM Designer, you'll get the
following error
message in the designer: "The Entity Data Model Designer is unable to display
the file you
requested. You can edit the model using the XML Editor."
There is a warning in the Error List pane that can give you a little more
insight into what this error is all about:
Error 11002: Entity type 'UserProfile' has no entity set.
You need to define an entity set for the UserProfile type so that it can map the
entity type to the store schema. Open the PlanMyNight.edmx file in the XML
editor so that you can define an entity set for UserProfile. At the top of the
file, just above the Itinerary entity set, add the XML code shown in Listing
5-2.
LISTING 5-2 UserProfile EntitySet XML Definition
<EntitySet
Name="UserProfile"
EntityType="Entities.Store.UserProfile"
store:Type="Views"
store:Schema="dbo"
store:Name="UserProfile">
<DefiningQuery>
SELECT
[UserProfile].[UserName] AS [UserName],
[UserProfile].[FullName] AS [FullName],
[UserProfile].[City] AS [City],
[UserProfile].[State] AS [State],
[UserProfile].[PreferredActivityTypeId] as [PreferredActivityTypeId]
FROM [dbo].[UserProfile] AS [UserProfile]
</DefiningQuery>
</EntitySet>
Save the EDM XML file, and reopen the EDM Designer. Figure 5-12 shows the
UserProfile view in the Entities.Store section of the Model Browser.
Tip You can open the Model Browser from the View menu by clicking Other
Windows and selecting the Entity Data Model Browser item.
FIGURE 5-12 Model Browser with the UserProfile view
Now that the view is available in the store metadata, you add the UserProfile
entity and map it to the UserProfile view. Right-click in the background of the
EDM Designer, select Add, and then choose Entity. You'll see the dialog shown in
Figure 5-13.
FIGURE 5-13 Add Entity dialog
Complete the dialog as shown in Figure 5-13, and click OK to generate the
entity.
You need to add the remaining properties: City, State, and
PreferredActivityTypeId. To do so, right-click the UserProfile entity, select
Add, and then select Scalar Property. After the property is added, set the Type,
Max Length, and Unicode field values. Table 5-1 shows the expected values for
each of the fields.
TABLE 5-1 UserProfile Entity Properties
Name |
Type |
MaxLength |
Unicode |
FullName |
String |
500 |
False |
City |
String |
500 |
False |
State |
String |
500 |
False |
PreferredActivityTypeId |
Int32 |
NA |
NA |
Now that you have created the UserProfile
entity, you need to map it to the UserProfile view. Right-click the UserProfile
entity, and select Table Mapping as shown in Figure 5-14.
FIGURE 5-14 Table Mapping menu item
Then select the UserProfile view from the drop-down box as shown in Figure 5-15.
Ensure that all the columns are correctly mapped to the entity properties. The
UserProfile view of our store is now accessible from the code through the
UserProfile entity.
FIGURE 5-15 UserProfile mapping details
Stored Procedure and Function Imports
The Entity Data Model Wizard has created an entry in the storage model for the
RetrieveItinerariesWithinArea stored procedure you selected in the last step of
the wizard. You need to create a corresponding entry to the conceptual model by
adding a Function Import entry.
From the Model Browser, open the Stored Procedures folder in the Entities.Store
section. Right-click RetrieveItineraryWithinArea, and then select Add Function
Import. The Add Function Import dialog appears as shown in Figure 5-16. Specify
the return type by selecting Entities and then select the Itinerary item from
the drop-down box. Click OK.
FIGURE 5-16 Add Function Import dialog
The RetrieveItinerariesWithinArea function import was added to the Model Browser
as shown in Figure 5-17.
FIGURE 5-17 Function Imports in the Model Browser
You can now validate the EDM by right-clicking on the design surface and
selecting Validate. There should be no error or warning.
EF: Model First
In the prior section, you saw how to use the EF designer to generate the model
by importing an existing database. The EF designer in Visual Studio 2010 also
supports the ability to generate the Data Definition Language (DDL) file that
will allow you to create a database based on your entity model. In this section,
you'll use a new solution to learn how to generate a database script from a
model.
You can start from an empty model by selecting the Empty model option from the
Entity Data Model Wizard. (See Figure 5-18.)
Note To get the wizard, right-click the PlanMyNight.Data project, select
Add, and then choose New Item. Select the ADO.NET Entity Data Model item.
FIGURE 5-18 EDM Wizard: Empty model
Open the PMN solution at %userprofile%\Documents\Microsoft Press\Moving to
Visual Studio 2010\Chapter 5\Code\ModelFirst by double-clicking the
PlanMyNight.sln file.
The PlanMyNight.Data project from this solution already contains an EDM file
named PlanMyNight.edmx with some entities already created. These entities match
the data schema you saw in Figure 5-2.
The Entity Model designer lets you easily add an entity to your data model.
Let's add the missing ZipCode entity to the model. From the toolbox, drag an
Entity item into the designer, as shown in Figure 5-19. Rename the entity as
ZipCode. Rename the Id property as Code, and change its type to String.
FIGURE 5-19 Entity Model designer
You need to add the City and State properties to the entity. Right-click the
ZipCode entity, select Add, and then choose Scalar Property. Ensure that each
property has the values shown in Table 5-2.
TABLE 5-2 ZipCode Entity Properties
Name |
Type |
Fix Length |
Max Length |
Unicode |
Code |
String |
False |
False |
False |
City |
String |
False |
False |
False |
State |
String |
False |
False |
Fals |
Add the relations between the ItineraryComment and Itinerary entities.
Right-click the designer background, select Add, and then choose Association.
(See Figure 5-20.)
FIGURE 5-20 Add Association dialog for FK_ItineraryCommentItinerary
Set the association name to FK_ItineraryCommentItinerary and then select the
entity and the multiplicity for each end, as shown in Figure 5-20. After the
association is created, double-click the association line to set the Referential
Constraint as shown in Figure 5-21.
FIGURE 5-21 Association Referential Constraint dialog
Add the association between the ItineraryRating and Itinerary entities.
Right-click the designer background, select Add, and then choose Association.
Set the association name to FK_ItineraryItineraryRating and then select the
entity and the multiplicity for each end as in the previous step, except set the
first end to ItineraryRating. Double-click on the association line, and set the
Referential Constraint as shown in Figure 5-21. Note that the Dependent field
will read ItineraryRating instead of ItineraryComment.
Create a new association between the ItineraryActivity and Itinerary entities.
For the
FK_ItineraryItineraryActivity association, you also want to create a navigation
property and name it Activities, as shown in Figure 5-22. After the association
is created, set the Referential Constraint for this association by
double-clicking on the association line.
FIGURE 5-22 Add Association dialog for FK_ItineraryActivityItinerary
Generating the Database Script from the Model
Your data model is now completed, but there is no mapping or store associated
with it. The EF designer offers the possibility of generating a database script
from our model.
Right-click on the designer surface, and choose Generate Database From Model as
shown in Figure 5-23.
FIGURE 5-23 Generate Database From Model menu item
The Generate Database Wizard requires a data connection. The wizard uses the
connection information to translate the model types to the database type and to
generate a DDL script targeting this database.
Select New Connection, select Microsoft SQL Server Database File from the Choose
Data Source dialog, and click Continue. Select the database file located at %userprofile%\Documents\Microsoft
Press\Moving to Visual Studio 2010\Chapter 5\Code\ModelFirst\Data\PlanMyNight.mdf.
(See Figure 5-24.)
FIGURE 5-24 Generate a script database connection
After your connection is configured, click Next to get to the final page of the
wizard, as shown in Figure 5-25. When you click Finish, the generated T-SQL
PlanMyNight.edmx.sql file is added to your project. The DDL script will generate
the primary and foreign key constraints for your model.
FIGURE 5-25 Generated T-SQL file
The EDM is also updated to ensure your newly created store is mapped to the
entities. You can now use the generated DDL script to add the tables to the
database. Also, you now have a data layer that exposes strongly typed entities
that you can use in your application.
Important Generating the complete PMN database would require adding the
remaining tables, stored procedures, and triggers used by the application.
Instead of performing all these operations, we will go back to the solution we
had at the end of the "EF: Importing an Existing Database" section.
POCO Templates
The EDM Designer uses T4 templates to generate the code for the entities. So
far, we have let the designer create the entities using the default templates.
You can take a look at the code generated by opening the PlanMyNight.Designer.cs
file associated with PlanMyNight.edmx. The generated entities are based on the
EntityObject type and decorated with attributes to allow the EF to manage them
at run time.
Note T4 stands for Text Template Transformation Toolkit. T4 support in
Visual Studio 2010 allows you to easily create your own templates and generate
any type of text file (Web, resource, or source). To learn more about the code
generation in Visual Studio 2010, visit Code Generation and Text Templates
(http://msdn.microsoft.com/en-us/library/bb126445(VS.100).aspx).
The EF also supports POCO entity types. POCO classes are simple objects with no
attributes or base class related to the framework. (Listing 5-3, in the next
section, shows the POCO class for the ZipCode entity.) The EF uses the names of
the types and the properties of these objects to map them to the model at run
time.
Note POCO stands for Plain-Old CLR Objects.
ADO.NET POCO Entity Generator
Let's re-open the %userprofile%\Documents\Microsoft Press\Moving to Visual
Studio 2010\Chapter 5\Code\ExistingDatabase\PlanMyNight.sln file.
Open the PlanMyNight.edmx file, right-click on the design surface, and choose
Add Code Generation Item. This opens a dialog like the one shown in Figure 5-26,
where you can select the template you want to use. Select the ADO.NET POCO
Entity Generator template, and name it PlanMyNight.tt. Then click the Add
button.
Note You might get a security warning about running this text template.
Click OK to close the dialog because the source for this template is trusted.
FIGURE 5-26 Add New Item dialog
Two files, PlanMyNight.tt and PlanMyNight.Context.tt, have been added to your
project, as shown in Figure 5-27. These files replace the default
code-generation template, and the code is no longer generated in the
PlanMyNight.Designer.cs file.
FIGURE 5-27 Added templates
The PlanMyNight.tt template produces a class file for each entity in the model.
Listing 5-3 shows the POCO version of the ZipCode class.
LISTING 5-3 POCO Version of the ZipCode Class
namespace
Microsoft.Samples.PlanMyNight.Data
{
public partial
class ZipCode
{
#region
Primitive Properties
public
virtual string Code
{
get;
set;
}
public
virtual string City
{
get;
set;
}
public
virtual string State
{
get;
set;
}
#endregion
}
}
Tip C# 3.0 introduced a new feature called automatic properties. The
backing field is created at compile time if the compiler finds empty get or set
blocks.
The other file, PlanMyNight.Context.cs, generates the ObjectContext object for
the PlanMyNight.edmx model. This is the object you'll use to interact with the
database. Tip The POCO templates will automatically update the generated classes
to reflect the changes to your model when you save the .edmx file.
Moving the Entity Classes to the Contracts Project
We have designed the PMN application architecture to ensure that the
presentation layer was persistence ignorant by moving the contracts and entity
classes to an assembly that has no reference to the storage.
Visual Studio 2005 Even though it was possible to extend the XSD processing with
codegenerator tools, it was not easy and you had to maintain these tools. The EF
uses T4 templates to generate both the database schema and the code. These
templates can easily be customized to your needs.
The ADO.NET POCO templates split the generation of the entity classes into a
separate template, allowing you to easily move these entities to a different
project.
You are going to move the PlanMyNight.tt file to the PlanMyNight.Contracts
project. Right-click the PlanMyNight.tt file, and select Cut. Right-click the
Entities folder in the PlanMyNight.Contracts project, and select Paste. The
result is shown in Figure 5-28.
FIGURE 5-28 POCO template moved to the Contracts project
The PlanMyNight.tt template relies on the metadata from the EDM model to
generate the entity type's code. You need to fix the relative path used by the
template to access the
EDMX file.
Open the PlanMyNight.tt template and locate the following line:
string
inputFile = @"PlanMyNight.edmx";
Fix the file location so that it points to the PlanMyNight.edmx file in the
PlanMyNight.Data project:
string
inputFile = @"..\..\PlanMyNight.Data\PlanMyNight.edmx";
The entity classes are regenerated when you save the template.
You also need to update the PlanMyNight.Context.tt template in the
PlanMyNight.Contracts project because the entity classes are now in the
Microsoft.Samples.PlanMyNight.Entities namespace instead of the
Microsoft.Samples.PlanMyNight.Data namespace. Open the PlanMyNight.Context.tt
file, and update the using section to include the new namespace:
using
System;
using
System.Data.Objects;
using
System.Data.EntityClient;
using
Microsoft.Samples.PlanMyNight.Entities;
Build the solution by pressing Ctrl+Shift+B. The project should now compile
successfully.
Putting It All Together
Now that you have created the generic code layer to interact with your SQL
database, you are ready to start implementing the functionalities specific to
the PMN application. In the upcoming sections, you'll walk through this process,
briefly look at getting the data from the Bing Maps services, and get a quick
introduction to the Microsoft Windows Server AppFabric Caching feature used in
PMN.
There is a lot of plumbing pieces of code required to get this all together. To
simplify the process, you'll use an updated solution where the contracts,
entities, and most of the connecting pieces to the Bing Maps services have been
coded. The solution will also include the PlanMyNight.Data.Test project to help
you validate the code from the PlanMyNight.Data project.
Note Testing in Visual Studio 2010 will be covered in Chapter 7.
Getting Data from the Database
At the beginning of this chapter, we decided to group the operations on the
Itinerary entity into the IItinerariesRepository repository interface. Some of
these operations are
- Searching for Itinerary by Activity
- Searching for Itinerary by ZipCode
- Searching for Itinerary by Radius
- Adding a new Itinerary
Let's take a look at the corresponding methods
in the IItinerariesRepository interface:
- SearchByActivity allows searching for
itineraries by activity and returning a page of data.
- SearchByZipCode allows searching for
itineraries by Zip Code and returning a page of data.
- SearchByRadius allows searching for
itineraries from a specific location and returning a page of data.
- Add allows you to add an itinerary to the
database.
Open the PMN solution at %userprofile%\Documents\Microsoft
Press\Moving to Visual Studio 2010\Chapter 5\Code\Final by double-clicking the
PlanMyNight.sln file.
Select the PlanMyNight.Data project, and open the ItinerariesRepository.cs file.
This is the IItinerariesRepository interface implementation. Using the
PlanMyNightEntities Object Context you generated earlier, you can write LINQ
queries against your model, and the EF will translate these queries to native
T-SQL that will be executed against the database.
Note LINQ stands for Language Integrated Query and was introduced in the
.NET Framework 3.5. It adds native data querying capability to the .NET
Framework so that you don't have to worry about learning or maintaining custom
SQL queries. LINQ allows you to use strongly typed objects, and the Visual
Studio IntelliSense lets you select the properties or methods that are in the
current context as shown in Figure 5-29. To learn more about LINQ, visit the
.NET Framework Developer Center (http://msdn.microsoft.com/en-us/netframework/aa904594.aspx).
FIGURE 5-29 IntelliSense support for LINQ queries
Navigate to the SearchByActivity function definition. This method must return a
set of itineraries where the IsPublic flag is set to true and where one of their
activities has the same activityId that was passed in the argument to the
function. You also need to order the result itinerary list by the rating field.
Visual Studio 2005 Implementing each method to retrieve the itinerary in Visual
Studio 2005 would have required writing tailored SQL. With the EF and LINQ, any
query becomes trivial and changes can be easily implemented at the code level!
Using standard LINQ operators, you can implement SearchByActivity as shown in
Listing 5-4. Add the highlighted code to the SearchByActivity method body.
LISTING 5-4 SearchByActivity Implementation
public PagingResult<Itinerary>
SearchByActivity(string activityId,
int pageSize, int
pageNumber)
{
using (var
ctx = new PlanMyNightEntities())
{
ctx.ContextOptions.ProxyCreationEnabled =
false;
var query =
from itinerary in
ctx.Itineraries.Include("Activities")
where
itinerary.Activities.Any(t => t.ActivityId == activityId)
&& itinerary.IsPublic
orderby
itinerary.Rating
select itinerary;
return PageResults(query,
pageNumber, pageSize);
}
}
Note The resulting paging is implemented in the PageResults method:
private static
PagingResult<Itinerary> PageResults(IQueryable<Itinerary> query,
int
page, int pageSize)
{
int rowCount = rowCount =
query.Count();
if (pageSize > 0)
{
query = query.Skip((page - 1) * pageSize)
.Take(pageSize);
}
var result =
new PagingResult<Itinerary>(query.ToArray())
{
PageSize = pageSize,
CurrentPage = page,
TotalItems = rowCount
};
return result;
}
IQueryable<Itinerary> is passed to this function so that it can add the paging
to the base query composition. Passing IQueryable instead of IEnumerable ensures
that the T-SQL created for the query against the repository will be generated
only when query.ToArray is called.
The SearchByZipCode function method is similar to the SearchByActivity method,
but it also adds a filter on the Zip Code of the activity. Here again, LINQ
support makes it easy to implement, as shown in Listing 5-5. Add the highlighted
code to the SearchByZipCode method body.
LISTING 5-5 SearchByZipCode Implementation
public PagingResult<Itinerary> SearchByZipCode(int
activityTypeId, string zip,
int
pageSize, int pageNumber)
{
using (var
ctx = new PlanMyNightEntities())
{
ctx.ContextOptions.ProxyCreationEnabled =
false;
var query =
from itinerary in
ctx.Itineraries.Include("Activities")
where
itinerary.Activities.Any(t => t.TypeId == activityTypeId &&
t.Zip == zip)
&& itinerary.IsPublic
orderby
itinerary.Rating
select itinerary;
return PageResults(query,
pageNumber, pageSize);
}
}
The SearchByRadius function calls the RetrieveItinerariesWithinArea import
function that was mapped to a stored procedure. It then loads the activities for
each itinerary found. You can copy the highlighted code in Listing 5-6 to the
SearchByRadius method body in the ItinerariesRepository.cs file.
LISTING 5-6 SearchByRadius Implementation
public PagingResult<Itinerary> SearchByRadius(int
activityTypeId,
double longitude,
double latitude,
double radius,
int pageSize,
int pageNumber)
{
using (var
ctx = new PlanMyNightEntities())
{
ctx.ContextOptions.ProxyCreationEnabled =
false;
// Stored Procedure with output
parameter
var totalOutput =
new ObjectParameter("total",
typeof(int));
var items =
ctx.RetrieveItinerariesWithinArea(activityTypeId,
latitude, longitude, radius, pageSize, pageNumber,
totalOutput).ToArray();
foreach (var
item in items)
{
item.Activities.ToList().AddRange(this.Retrieve(item.Id).Activities);
}
int total = totalOutput.Value
== DBNull.Value ? 0 : (int)totalOutput.Value;
return
new PagingResult<Itinerary>(items)
{
TotalItems = total,
PageSize = pageSize,
CurrentPage = pageNumber
};
}
}
The Add method allows you to add Itinerary to the data store. Implementing this
functionality becomes trivial because your contract and context object use the
same entity object. Copy and paste the highlighted code in Listing 5-7 to the
Add method body.
LISTING 5-7 Add Implementation
public void
Add(Itinerary itinerary)
{
using (var
ctx = new PlanMyNightEntities())
{
ctx.Itineraries.AddObject(itinerary);
ctx.SaveChanges();
}
}
There you have it! You have completed the ItinerariesRepository implementation
using the context object generated using the EF designer. Run all the tests in
the solution by pressing Ctrl+R, A. The tests related to the
ItinerariesRepository implementation should all succeed.
Getting Data from the Bing Maps Web Services
PMN relies on the Bing Maps services to allow the user to search for activities
to add to her itineraries. To get a Bing Maps Key to use in the PMN application,
you need to create a Bing Maps Developer Account. You can create a free
developer account on the Bing Maps Account Center.
See Also Microsoft Bing Maps Web services is a set of programmable Simple Object
Access Protocol (SOAP) services that allow you to match addresses to the map,
search for points of interest, integrate maps and imagery, return driving
directions, and incorporate other location intelligence into your Web
application. You can learn more about these services by visiting the site for
the Bing Maps Web Services SDK (http://msdn.microsoft.com/en-us/library/cc980922.
aspx).
Visual Studio 2005 In Visual Studio 2005, if you had to add a reference to a Web
service you would have selected the Add Web Service Reference from the
contextual menu to bring up the Add Web Reference dialog and then added a
reference to a Web service to your project. (See Figure 5-30.)
FIGURE 5-30 Visual Studio 2005 Add Web Reference dialog
Introduced in the .NET Framework 3.0, the Windows Communication Foundation (WCF)
services brought the ASMX Web services and other communication technologies into
a unified programming model.
Visual Studio 2010 provides tools for working with WCF services. You can bring
up the new Add Service Reference dialog by right-clicking on a project node and
selecting Add Service Reference as shown in Figure 5-31. In this dialog, you
first need to specify the service metadata address in the Address field and then
click Go to view the available service endpoints. You can then specify a
namespace for the generated code in the Namespace text box and then click OK to
add the proxy to your project.
FIGURE 5-31 Add Service Reference dialog
Tip Click the Discover button to look for WCF services in the current solution.
See Also Click the Advanced button to access the Service Reference Settings
dialog. This dialog lets you tweak the configuration of the WCF service proxy.
You can add the .NET Framework 2.0 style reference by clicking the Add Web
Service button. To learn more about these settings, visit MSDN - Configure
Service Reference Dialog Box (http://msdn.microsoft.com/en-us/library/bb514724(VS.100).aspx).
The generated WCF proxy can be used in the same way you used the ASMX-style
proxy, as shown in Listing 5-8.
LISTING 5-8 Using a Web Service Proxy
public BingCoordinate
GeocodeAddress(ActivityAddress address, string
token)
{
Microsoft.Samples.PlanMyNight.Bing.VEGeocodingService.GeocodeResponse
geocodeResponse
= null;
// Make the geocode request
using (var
geocodeService = new
Microsoft.Samples.PlanMyNight.Bing.VEGeocodingService.GeocodeServiceClient())
{
try
{
geocodeResponse = geocodeService.Geocode(geocodeRequest);
geocodeService.Close();
}
catch
{
geocodeService.Abort();
}
}
if (geocodeResponse !=
null && geocodeResponse.Results !=
null && geocodeResponse.Results.Length > 0)
{
var location =
geocodeResponse.Results[0].Locations[0];
return
new BingCoordinate { Latitude = (float)location.Latitude,
Longitude = (float)location.Longitude };
}
return
default(BingCoordinate);
}
Parallel Programming
With the advances in multicore computing, it is becoming more and more important
for developers to be able to write parallel applications. Visual Studio 2010 and
the .NET Framework 4.0 provide new ways to express concurrency in applications.
The Task Parallel Library (TPL) is now part of the Base Class Library (BCL) for
the .NET Framework. This means that every .NET application can now access the
TPL without adding any assembly reference. PMN stores only the Bing Activity ID
for each ItineraryActivity to the database. When it's time to retrieve the
entire Bing Activity object, a function that iterates through each of the
ItineraryActivity instances for the current Itinerary is used to populate the
Bing Activity entity from the Bing Maps Web services.
One way of performing this operation is to sequentially call the service for
each activity in the Itinerary, as shown in Listing 5-9. This function waits for
each call to RetrieveActivity to complete before making another call, which has
the effect of making its execution time linear.
LISTING 5-9 Activity Sequential Retrieval
public void
PopulateItineraryActivities(Itinerary itinerary)
{
foreach (var
item in itinerary.Activities.Where(i =>
i.Activity == null))
{
item.Activity = this.RetrieveActivity(item.ActivityId);
}
}
In the past, if you wanted to parallelize this task, you had to use threads and
then hand off
work to them. With the TPL, all you have to do now is use a Parallel.ForEach
that will take
care of the threading for you, as seen in Listing 5-10.
LISTING 5-10 Activity Parallel Retrieval
public void
PopulateItineraryActivities(Itinerary itinerary)
{
Parallel.ForEach(itinerary.Activities.Where(i => i.Activity ==
null),
item =>
{
item.Activity = this.RetrieveActivity(item.ActivityId);
});
}
See Also The .NET Framework 4.0 now includes the Parallel LINQ libraries (in
System.Core.dll). PLINQ introduces the .AsParallel extension to perform parallel
operations in LINQ queries. You can also easily enforce the treatment of a data
source as if it was ordered by using the .AsOrdered extensions. Some new
thread-safe collections have also been added in the
System.Collections.Concurrent namespace. You can learn more about these new
features from Parallel Computing on MSDN (http://msdn.microsoft.com/en-us/concurrency/default.aspx).
AppFabric Caching
PMN is a data-driven application that gets its data from the application
database and the Bing Maps Web services. One of the challenges you might face
when building a Web application is managing the needs of a large number of
users, including performance and response time. The operations that use the data
store and the services used to search for activities can increase the usage of
server resources dramatically for items that are shared across many users. For
example, many users have access to the public itineraries, so displaying these
will generate numerous calls to the database for the same items. Implementing
caching at the Web tier will help reduce usage of the resources at the data
store and help mitigate latency for recurring searches to the Bing Maps Web
services. Figure 5-32 shows the architecture for an application implementing a
caching solution at the front-end server.
FIGURE 5-32 Typical Web application architecture
Using this approach reduces the pressure on the data layer, but the caching is
still coupled to a specific server serving the request. Each Web tier server
will have its own cache, but you can still end up with an uneven distribution of
the processing to these servers.
Windows Server AppFabric caching offers a distributed, in-memory cache platform.
The AppFabric client library allows the application to access the cache as a
unified view event if the cache is distributed across multiple computers, as
shown in Figure 5-33. The API provides simple get and set methods to retrieve
and store any serializable common language runtime (CLR) objects easily. The
AppFabric cache allows you to add a cache computer on demand, thus making it
possible to scale in a manner that is transparent to the client. Another benefit
is that the cache can also share copies of the data across the cluster, thereby
protecting data against failure.
FIGURE 5-33 Web application using Windows Server AppFabric caching
See Also Windows Server AppFabric caching is available as a set of extensions to
the .NET Framework 4.0. For more information about how to get, install, and
configure Windows Server AppFabric, please visit Windows Server AppFabric (http://msdn.microsoft.com/en-us/windowsserver/ee695849.aspx).
See Also PMN can be configured to use either ASP.NET caching or Windows Server
AppFabric caching. A complete walkthrough describing how to add Windows Server
AppFabric caching to PMN is available here: PMN: Adding Caching using Velocity (http://channel9.msdn.com/learn/courses/VS2010/ASPNET/EnhancingAspNetMvcPlanMyNight/Exercise-1-Adding-Caching-using-Velocity/).
Summary
In this chapter, you used a few of the new Visual Studio 2010 features to
structure the data layer of the Plan My Night application using the Entity
Framework version 4.0 to access a database. You also were introduced to
automated entity generation using the ADO.NET Entity Framework POCO templates
and to the Windows Server AppFabric caching extensions.
In the next chapter, you will explore how the ASP.NET MVC framework and the
Managed Extensibility Framework can help you build great Web applications.