Chapter 1: From 2003 to 2010: Business Logic and Data


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 1-1.

Figure-1-1.gif

FIGURE 1-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 1-2 shows the tables used by the PMN application.

Figure-1-2.gif

FIGURE 1-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 1-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 we can provide autocomplete functionality when the user is entering a search query in the application.

Plan My Night Data in Microsoft Visual Studio 2003

It would be straightforward to create the Plan My Night application in Visual Studio 2003 because it offers all the required tools to help you code the application. However, some of the technologies used back then required you to write a lot more code to achieve the same goals.

In Visual Studio 2003, you could create the required data layer using ADO.NET DataSet or DataReader to access your database. (See Figure 1-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-1-3.gif

FIGURE 1-3 ADO.NET Insert query

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 business requirement 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 1\Code\ExistingDatabase. Double-click the PlanMyNight.sln file.

This solution includes all the projects in the following list, as shown in Figure 1-4:

  • PlanMyNight.Data: Application data layer
  • PlanMyNight.Contracts: Entities and contracts
  • PlanMyNight.Bing: Bing Maps services
  • PlanMyNight.Web: Presentation layer
  • PlanMyNight.AppFabricCaching: AppFabric caching

Figure-1-4.gif


FIGURE 1-4 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 1-5.

Figure-1-5.gif

FIGURE 1-5 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 1\ExistingDatabase\PlanMyNight.Web\App_Data\PlanMyNight.mdf file. (See Figure 1-6.)

Figure-1-6.gif

FIGURE 1-6 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 1-7.

Figure-1-7.gif

FIGURE 1-7 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 1-8 for the updated model.

Figure-1-8.gif

FIGURE 1-8 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 1-9.

Figure-1-9.gif

FIGURE 1-9 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 1-10. Click OK to open the XML file associated with your model.

Figure-1-10.gif

FIGURE 1-10 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 1-1 to see the updated entity type.

LISTING 1-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 1-2.

LISTING 1-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 1-11 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-1-11.gif

FIGURE 1-11 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 1-12.

Figure-1-12.gif

FIGURE 1-12 Add Entity dialog

Complete the dialog as shown in Figure 1-12, 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 1-1 shows the expected values for each of the fields.

TABLE 1-1 UserProfile Entity Properties

Name Type Max Length 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 1-13.

Figure-1-13.gif

FIGURE 1-13 Table Mapping menu item

Then select the UserProfile view from the drop-down box as shown in Figure 1-14. 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-1-14.gif

FIGURE 1-14 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 1-15. Specify the return type by selecting Entities and then select the Itinerary item from the drop-down box. Click OK.

Figure-1-15.gif

FIGURE 1-15 Add Function Import dialog

The RetrieveItinerariesWithinArea function import was added to the Model Browser as shown in Figure 1-16.

Figure-1-16.gif

FIGURE 1-16 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 1-17.)

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-1-17.gif

FIGURE 1-17 EDM Wizard: Empty model

Open the PMN solution at %userprofile%\Documents\Microsoft Press\Moving to Visual Studio 2010\Chapter 1\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 1-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 1-18. Rename the entity as ZipCode. Rename the Id property as Code, and change its type to String.

Figure-1-18.gif

FIGURE 1-18 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 1-2.

TABLE 1-2 ZipCode Entity Properties

Name Type Fixed Length Max Length Unicode
Name String False 5 False
Code String False 150 False
State String False 150 False

Add the relations between the ItineraryComment and Itinerary entities. Right-click the designer background, select Add, and then choose Association. (See Figure 1-19.)

Figure-1-19.gif

FIGURE 1-19 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 1-19. After the association is created, double-click the association line to set the Referential Constraint as shown in Figure 1-20.

Figure-1-20.gif

FIGURE 1-20 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 1-20. 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 1-21. After the association is created, set the Referential Constraint for this association by double-clicking on the association line.

Figure-1-21.gif

FIGURE 1-21 Add Association dialog for FK_ItineraryActivityItinerary

Generating the Database Script from the Model

Your data model is now complete, but there is no mapping or store associated with it. The EF designer offers you 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 1-22.

Figure-1-22.gif

FIGURE 1-22 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 1\Code\ModelFirst\Data\PlanMyNight.mdf. (See Figure 1-23.)

Figure-1-23.gif

FIGURE 1-23 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 1-24. 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-1-24.gif

FIGURE 1-24 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 code generation in Visual Studio 2010, visit Code Generation andText 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 1-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 1\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 1-25, 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-1-25.gif

FIGURE 1-25 Add New Item dialog

Two files, PlanMyNight.tt and PlanMyNight.Context.tt, have been added to your project, as shown in Figure 1-26. These files replace the default code-generation template, and the code is no longer generated in the PlanMyNight.Designer.cs file.

Figure-1-26.gif


FIGURE 1-26 Added templates

The PlanMyNight.tt template produces a class file for each entity in the model. Listing 1-3 shows the POCO version of the ZipCode class.

LISTING 1-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 Partial classes were added to C# 2.0. They allow splitting the implementation of a class over multiple files, where each file can contain one or more members and the files are combined when the application is compiled. Partial classes are really useful if you need to add code to automatically generated classes because the code is added outside of the generated file and it will not be overridden if the class is regenerated.

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 2003 Even though it was possible to write add-ins in Visual Studio 2003 to generate code based on a database, 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 1-27.

Figure-1-27.gif

FIGURE 1-27 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 3.

Getting Data from the Database

At the beginning of this chapter, we decided to group the operations on the Itinerary entity in 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 1\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 Visual Studio IntelliSense lets you select the properties or methods that are in the current context as shown in Figure 1-28. To learn more about LINQ, visit the .NET Framework Developer Center (http://msdn.microsoft.com/en-us/netframework/aa904594.aspx).

Figure-1-28.gif

FIGURE 1-28 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 2003 Implementing each method to retrieve the itinerary in Visual Studio 2003 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 1-4. Add the highlighted code to the SearchByActivity method body.

Visual Studio 2003 Generics were added to version 2.0 of the C# language and the common language runtime (CLR). Generics introduce the concept of type parameters, which make it possible to design classes and methods that defer the specification of types until the class or method is declared. They are often used with a collection, where the type parameter is used as a placeholder for the type of objects that it stores.

PMN uses generics to store results of different types. With Visual Studio 2003, you could have written such a class by using an ArrayList:

    public class PagingResult
    {
        private ArrayList items;

        public PagingResult(Array items)
        {
            this.items = new ArrayList(items);
        }
        public ArrayList Items
        {
            get { return this.items; }
        }
    }

To use this class in your code, you always have to know the type of objects it contains and evaluate the cost or risk of runtime casts or boxing operations. Using a generic type parameter T, you can write a type-safe class so that the compiler will prevent any invalid use at build time:

    public class PagingResult<T>
    {
        public PagingResult(IEnumerable<T> items)
        {
            this.Items = new List<T>(items);
        }

        public ICollection<T> Items { get; }
    }

To learn more about generics, visit Generics in the .NET Framework (http://msdn.microsoft.com/en-us/library/ms172192.aspx).

LISTING 1-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 1-5. Add the highlighted code to the SearchByZipCode method body.

LISTING 1-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 1-6 to the SearchByRadius method body in the ItinerariesRepository.cs file.

LISTING 1-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 1-7 to the Add method body.

LISTING 1-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 (https://www.bingmapsportal.com/).

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 2003 In Visual Studio 2003, 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 1-29.)

Figure-1-29.gif

FIGURE 1-29 Visual Studio 2003 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 1-30. 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 and click OK to add the proxy to your project.

Figure-1-30.gif

FIGURE 1-30 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 the 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 1-8.

LISTING 1-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 1-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 1-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 1-10.

LISTING 1-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 the usage of resources at the data store and help mitigate latency for recurring searches to the Bing Maps Web services. Figure 1-31 shows the architecture for an application implementing a caching solution at the front-end server.

Figure-1-31.gif

FIGURE 1-31 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 1-32. The API provide 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-1-32.gif

FIGURE 1-32 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.

Up Next
    Ebook Download
    View all
    Learn
    View all