Sie sind auf Seite 1von 610

O F F I C I A L M I C R O S O F T L E A R N I N G P R O D U C T

10265A
Lab Instructions and Lab Answer Key:
Developing Data Access Solutions with
Microsoft Visual Studio 2010

Information in this document, including URL and other Internet Web site references, is subject to change without notice.
Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people,
places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain
name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright
laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be
reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic,
mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft
Corporation.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject
matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this
document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

The names of manufacturers, products, or URLs are provided for informational purposes only and Microsoft makes no
representations and warranties, either expressed, implied, or statutory, regarding these manufacturers or the use of the
products with any Microsoft technologies. The inclusion of a manufacturer or product does not imply endorsement of
Microsoft of the manufacturer or product. Links may be provided to third party sites. Such sites are not under the control of
Microsoft and Microsoft is not responsible for the contents of any linked site or any link contained in a linked site, or any
changes or updates to such sites. Microsoft is not responsible for webcasting or any other form of transmission received from
any linked site. Microsoft is providing these links to you only as a convenience, and the inclusion of any link does not imply
endorsement of Microsoft of the site or the products contained therein.

2010 Microsoft Corporation. All rights reserved.


Microsoft, and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or
other countries.

All other trademarks are property of their respective owners.

Product Number: 10265A

Part Number: X17-47847


Released: 10/2010
Lab Instructions: Introduction to Data Access Technologies 1

Module 1
Lab Instructions: Introduction to Data Access Technologies
Contents:
Exercise 1: Identifying Data Access Technologies 4
2 Lab Instructions: Introduction to Data Access Technologies

Lab: Analyzing Data Access Scenarios

Objectives
After completing this lab, you will be able to determine the appropriate technology to use for common
data access scenarios.

Introduction
In this lab, you will analyze four common data access scenarios and decide which data technology to use
for each.
Lab Instructions: Introduction to Data Access Technologies 3

Lab Scenario

You are working as a data expert for Adventure Works Cycles. You have been asked to analyze the
database application requirements to determine which data access technologies should be used to meet
the requirements of a range of applications and scenarios.
4 Lab Instructions: Introduction to Data Access Technologies

Exercise 1: Identifying Data Access Technologies


Scenario
In this exercise, you will determine the appropriate technology for each given data access scenario.

The main tasks for this exercise are as follows:

1. Identify the appropriate data access technology for a customer management application.
2. Identify the appropriate data access technology for an order management application.
3. Identify the appropriate data access technology for a delivery management application.
4. Identify the appropriate data access technology for a product management application.

Task 1: Identify the appropriate data access technology for a customer management
application

Scenario
Adventure Works Cycles has a corporate database that contains customer information. Employees can
browse and maintain customer data, but customers only have read access to the data. Employees use a
Windows Presentation Foundation (WPF) application to access their required data and the corporate
network has no bandwidth issues. The corporate database is several years old and changes are made to
the database structure twice a year.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem.

Task 2: Identify the appropriate data access technology for an order management
application

Scenario
Adventure Works Cycles has a requirement to enable salespeople to view and create orders during offsite
meetings and add them to the database at a later time. Therefore, the data access layer needs to copy
database content on the remote device to the server.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem.

Task 3: Identify the appropriate data access technology for a delivery management
application

Scenario
Adventure Works Cycles has agreed to provide an ASP.NET Model-View-Controller (MVC) Web
application to delivery companies to query and maintain the delivery status of orders as they ship them.
The application has to provide fast and responsive access to the database in a potentially low-bandwidth
environment. Adventure Works Cycles has to produce a highly robust data access layer for this application
in a very compressed time scale.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem.
Lab Instructions: Introduction to Data Access Technologies 5

Task 4: Identify the appropriate data access technology for a product management
application

Scenario
Adventure Works Cycles previously developed what are now legacy applications that enable employees to
browse and maintain a list of products, and enable customers to browse a list of products. The employee
application is a Windows Forms application, and the customer application is a Web application. Both
applications were built by using the .NET Framework 2.0 or earlier. The underlying database structure is
stable and has not been changed since it was first designed. Employees have no bandwidth limitations,
although customers may do.

1. Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem.
2. Discuss the benefits and drawbacks of each of your solutions with one of the other students.
Lab Instructions: Building Entity Data Models 1

Module 2
Lab Instructions: Building Entity Data Models
Contents:
Exercise 1: Generating an EDM from AdventureWorks 4
Exercise 2: Adding Entities and Associations 5
Exercise 3: Using the Generate Database Wizard 7
Exercise 4: Mapping Entities to Multiple Tables 8
Exercise 5: Implementing an Inheritance Hierarchy 9
Exercise 6: Using Stored Procedures 11
Exercise 7: Creating a Complex Type 12
2 Lab Instructions: Building Entity Data Models

Lab: Using Entity Data Models

Objectives
After completing this lab, you will be able to:

Generate an EDM.
Add entities and associations to an EDM.
Use the Generate Database Wizard.
Map entities to multiple tables.
Implement an inheritance hierarchy.
Use stored procedures in an EDM.
Create a complex type.

Introduction
In this lab, you will generate an EDM, add entities and associations to it, generate new tables in the
database for the new entities, and implement an inheritance hierarchy. You will also implement entity
splitting, access stored procedures, and create a complex type.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:

Start the 10265A-GEN-DEV-02 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Building Entity Data Models 3

Lab Scenario

You have been asked to modify the corporate data model to implement a new customer rewards
program. Customers will be awarded points when they purchase items from Adventure Works, and they
can choose to exchange them for air miles, supermarket vouchers, or discounts on future Adventure
Works purchases. You must keep track of the rewards offered and the reward claims made by customers.
Two or more customers may combine their points to claim a better reward. The current points held by a
customer have already been added to the Contact table.
You have been asked to update the database to store current and inactive customers in separate tables,
but you must ensure that this does not impact on the model or the application. You must also add a
stored procedure to the model to track the number of orders a customer has placed and refactor some of
the properties in entities in the model.
4 Lab Instructions: Building Entity Data Models

Exercise 1: Generating an EDM from AdventureWorks


Scenario
In this exercise, you will create a new EDM from the AdventureWorks database, creating one entity per
table. The tables you will use are Contact, SalesTerritory, SalesOrderHeaders, and StoreContact. You
will browse the logical and conceptual models and the mappings in the EDM Designer. Finally, you will
review the XML for the EDM.

The main tasks for this exercise are as follows:

1. Prepare the AdventureWorks database for the lab.


2. Open the starter project.
3. Create the AdventureWorks Entity Data Model.
4. Review the AdventureWorks model.
5. Modify the SalesTerritory entity by using XML.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-02 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat.

Task 2: Open the starter project


1. Open Visual Studio 2010.
2. Open the existing solution, DAL.sln in the E:\Labfiles\Lab02\VB\Ex1\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex1\Starter\DAL folder.

Task 3: Create the AdventureWorks Entity Data Model


Add a new ADO.NET EDM to the DAL project. Generate the EDM from the AdventureWorks SQL
Server database and create entities for the Contact, SalesOrderHeader, SalesTerritory, and
StoreContact tables.

Task 4: Review the AdventureWorks model


1. Review the four entities shown in the Entity Designer pane and the associations between them.
2. Open the Mapping Details pane and review the mappings for each entity in the model.
3. Close the Entity Designer pane.

Task 5: Modify the SalesTerritory entity by using XML


1. Open the AdventureWorksEDM EDM in the XML Editor.
2. Locate the CSDL section of the model.
3. Locate the SalesTerritory entity in the CSDL section of the model.
4. Find the Name property, and then change its Name attribute to TerritoryName.
5. Save the model and close the XML Editor window.
6. Open AdventureWorksEDM.edmx in the Entity Designer pane, verify that the change has been
made, and then change the property back to Name.
7. Save and close the solution.
Lab Instructions: Building Entity Data Models 5

Exercise 2: Adding Entities and Associations


Scenario
In this exercise, you will modify the EDM by adding new entities to it to support the new customer
rewards program.

You will create a new entity named Reward. This entity will provide access to information about the
current rewards that Adventure Works offers. The Reward entity will contain the following properties:
RewardID, RewardType (for example, AM for air miles, SM for supermarket vouchers, and AW for
Adventure Works), RewardName (for example, 100 air miles or $10 off at Adventure Works),
NumberOfAirMilesRequired, PointsPerAirMile, Destination, MoneyBackPerPoint,
NumberOfPointsRequired, and Product.

You will then create a second new entity named RewardsClaimed. This entity will provide access to the
customer claims for rewards in the program. It will have the following properties: ClaimID and
PointsUsed. The PointsUsed property must be tracked because Adventure Works permits families to
combine points; therefore, the points used by a customer are not necessarily the price of the reward.

Next, you will add a 1:n association between the Reward and RewardsClaimed entities, changing the
name of the RewardRewardID property to RewardID. You will also add a 1:n association between the
Contact and RewardsClaimed entities, changing the name of the ContactContactID property to
ContactID.

The main tasks for this exercise are as follows:

1. Open the starter project.


2. Add the Reward entity.
3. Add the RewardsClaimed entity.
4. Add the Reward entity to the RewardsClaimed association.
5. Add the Contact entity to the RewardsClaimed association.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex2\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex2\Starter\DAL folder.

Task 2: Add the Reward entity


1. Open the AdventureWorksEDM model in the Entity Designer pane.
2. Add a new entity named Reward with a Key Property name of RewardID and an EntitySet name of
Reward to the model.
3. Add the scalar properties described in the following table to the entity.

Property name Type Scale Nullable Default Value

RewardType String not available True AW

RewardName String not available False (None)

NumberOfAirMilesRequired Int32 not available True (None)

PointsPerAirMile Int32 not available True (None)

Destination String not available True (None)

MoneyBackPerPoint Decimal 2 True (None)


6 Lab Instructions: Building Entity Data Models

Property name Type Scale Nullable Default Value

NumberOfPointsRequired Int32 not available True (None)

Product String not available True (None)

Task 3: Add the RewardsClaimed entity


1. Add a new entity named RewardsClaimed with a Key Property name of ClaimID and an Entity Set
name of RewardsClaimed to the model.
2. Add the scalar property described in the following table to the entity.

Property name Type

PointsUsed Int32

Task 4: Add the Reward entity to the RewardsClaimed association


1. Add a one-to-many association between the Reward and RewardsClaimed entities.
2. In the RewardsClaimed entity, rename the new RewardRewardID property to RewardID.
3. In the Reward entity, rename the RewardsClaimeds navigation property to RewardsClaimed.

Task 5: Add the Contact entity to the RewardsClaimed association


1. Add a one-to-many association between the Contact and RewardsClaimed entities.
2. Rename the new ContactContactID property in the RewardsClaimed entity to ContactID.
3. Rename the Navigation Property in the Contact entity to RewardsClaimed.
4. Save and close the solution.
Lab Instructions: Building Entity Data Models 7

Exercise 3: Using the Generate Database Wizard


Scenario
In this exercise, you will use the Generate Database Wizard to generate a Transact-SQL script for the
model. You will then remove the Transact-SQL code that creates the Customers, SalesTerritory,
SalesOrderHeaders, and StoreContact tables and their keys from the script to ensure that it does not
overwrite the existing data in the database. You will then execute the Transact-SQL script to create the
new tables in the database and review the mappings generated in your model.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Modify the Database Schema Name property.
3. Generate Transact-SQL script of the model.
4. Modify the generated script.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex3\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex3\Starter\DAL folder.

Task 2: Modify the Database Schema Name property


1. Open the AdventureWorksEDM model in the Entity Designer.

Note: The Error List shows that the Reward and RewardsClaimed entities are not mapped. This is
because you created the entities in the last exercise and have not yet mapped them to any database
objects. You will resolve this error later in this exercise.

2. Change the Database Schema Name property to Sales.

Task 3: Generate Transact-SQL script of the model


Run the Generate Database Wizard to script the AdventureWorksEDM model.

Task 4: Modify the generated script


1. Remove the Dropping existing FOREIGN KEY constraints section from the script.
2. Remove the Dropping existing tables section from the script.
3. Remove the code that creates the following existing tables in the database: Contacts,
SalesOrderHeaders, SalesTerritories, and StoreContacts.
4. Remove the code that creates the existing primary keys on the following tables in the database:
Contacts, SalesOrderHeaders, SalesTerritories, and StoreContacts.
5. Remove the code that creates the existing foreign keys on the following tables in the database:
SalesOrderHeaders and StoreContacts.
6. Modify the code that creates the foreign key on [ContactID] in the RewardsClaimed table to
change the schema for the Contact table to Person and to singularize the table name to Contact.
7. Validate and then execute the script against the 10265A-GEN-DEV
\SQLExpress database engine.
8. Build the solution.

Note: The errors in the Error List have been cleared and the solution builds successfully because the
Reward and RewardsClaimed entities are now mapped to the tables that you have just created.

9. Save and close the solution.


8 Lab Instructions: Building Entity Data Models

Exercise 4: Mapping Entities to Multiple Tables


Scenario
In this exercise, you will use the Update Wizard to add the InactiveStoreContact table to the EDM. You
will then modify the StoreContact entity to contain information from both the StoreContact and
InactiveStoreContact tables.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Create the InactiveStoreContact table.
3. Update the model from the database.
4. Map the StoreContact entity to the InactiveStoreContact table.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex4\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex4\Starter\DAL folder.

Task 2: Create the InactiveStoreContact table


1. Open the InactiveStoreContact.sql script from the E:\Labfiles\Lab02\VB\Ex4\Starter or
E:\Labfiles\Lab02\CS\Ex4\Starter folder.
2. Validate and then execute the script against the 10265A-GEN-DEV
\SQLExpress database engine.

Task 3: Update the model from the database


1. Open the AdventureWorksEDM model in the Entity Designer.
2. Update the model from the database to add the InactiveStoreContacts table to the model.

Note: Due to an issue with the prerelease version of the Entity Designer, the mappings for the original
table have been lost. To resolve this issue, it is necessary to delete the model from the project, re-create
the model, and re-create any default values.

3. Delete the model from the project.


4. Add a new ADO.NET EDM named AdventureWorksEDM.edmx to the DAL project. Generate the
data model from the AdventureWorks database, and create entities for the Contact,
InactiveStoreContact, Reward, RewardsClaimed, SalesOrderHeader, SalesTerritory, and
StoreContact tables.
5. Change the Database Schema Name property to Sales.
6. Rename the Navigation Property in the Reward entity to RewardsClaimed.
7. Rename the Navigation Property in the Contact entity to RewardsClaimed.

Task 4: Map the StoreContact entity to the InactiveStoreContact table


1. Open the Mapping Details pane, and then map the StoreContact entity to the
InactiveStoreContact table.
2. In the Entity Designer pane, delete the InactiveStoreContact entity.
3. Build the solution.
4. Save and close the solution.
Lab Instructions: Building Entity Data Models 9

Exercise 5: Implementing an Inheritance Hierarchy


Scenario
In this exercise, you will create three new entities: AirMilesReward, SupermarketReward, and
AdventureWorksReward. You will move the NumberOfAirMilesRequired, PointsPerAirMile, and
Destination properties to the AirMilesReward entity, the MoneyBackPerPoint property to the
SupermarketReward entity, and the NumberOfPointsRequired and Product properties to the
AdventureWorksReward entity. You will add a condition to each of the new entities so that they only
include data when the RewardType property is the appropriate type. For example, for the
AirMilesReward, the condition will be When RewardType = AM. Finally, you will review the entity types
generated by the EDM to implement this hierarchy.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Add the AirMilesReward entity.
3. Add the SupermarketReward entity.
4. Add the AdventureWorksReward entity.
5. Map the new entities to the Reward table.
6. Add conditions to the mappings.
7. Assign properties to the new entities.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex5\Starter\ DAL or
E:\Labfiles\Lab02\CS\Ex5\Starter\DAL folder.

Task 2: Add the AirMilesReward entity


1. Open AdventureWorksEDM in the Entity Designer pane.
2. Add a new entity named AirMilesReward based on the Reward entity.

Task 3: Add the SupermarketReward entity


Add a new entity named SupermarketReward based on the Reward entity.

Task 4: Add the AdventureWorksReward entity


Add a new entity named AdventureWorksReward based on the Reward entity.

Task 5: Map the new entities to the Reward table


1. Open the Mapping Details pane, and then map the AirMilesReward entity to the Reward table.
2. In the Mapping Details pane, map the SupermarketReward entity to the Reward table.
3. In the Mapping Details pane, map the AdventureWorksReward entity to the Reward table.

Task 6: Add conditions to the mappings


1. Add a condition to the AirMilesReward mapping to specify that this entity should contain rewards
only if the RewardType field is AM.
2. Add a condition to the SupermarketReward mapping to specify that this entity should contain
rewards only if the RewardType field is SM.
3. Add a condition to the AdventureWorksReward mapping to specify that this entity should contain
rewards only if the RewardType field is AW.
4. Remove the RewardType property from the Reward entity.
5. Make the Reward entity abstract.
10 Lab Instructions: Building Entity Data Models

Task 7: Assign properties to the new entities


1. Move the NumberOfAirMilesRequired property to the AirMilesReward entity.
2. Move the PointsPerAirMile property to the AirMilesReward entity.
3. Move the Destination property to the AirMilesReward entity.
4. Move the MoneyBackPerPoint property to the SupermarketReward entity.
5. Move the NumberOfPointsRequired property to the AdventureWorksReward entity.
6. Move the Product property to the AdventureWorksReward entity.
7. Map the AirMilesReward properties to the appropriate columns in the Reward table.
8. Map the SupermarketReward properties to the appropriate columns in the Reward table.
9. Map the AdventureWorksReward properties to the appropriate columns in the Reward table.
10. Build the solution.
11. Save and close the solution.
Lab Instructions: Building Entity Data Models 11

Exercise 6: Using Stored Procedures


Scenario
In this exercise, you will add the uspCountOrders stored procedure to the EDM and add a new function
import to map to the stored procedure. This stored procedure will return a count of the number of orders
that a given contact has made.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Add the uspCountOrders stored procedure to the model.
3. Add a function import to the model.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex6\Starter or
E:\Labfiles\Lab02\CS\Ex6\Starter folder.

Task 2: Add the uspCountOrders stored procedure to the model


1. Open AdventureWorksEDM in the Entity Designer pane.
2. Run the Update Wizard to add the uspCountOrders stored procedure to the model.

Note: Four errors will appear in the Error List because you have updated the model from the database
and the mappings for the inheritance hierarchy that you created in Exercise 5 have been lost. These errors
will not impact this exercise, so you can ignore the errors and continue with the next task.

Task 3: Add a function import to the model


1. Add a function import named CountOrders to the model. Map it to the uspCountOrders stored
procedure and configure it to return a collection of scalar Int32 data types.
2. Build the solution.
3. Save and close the solution.
12 Lab Instructions: Building Entity Data Models

Exercise 7: Creating a Complex Type


Scenario
In this exercise, you will refactor the Name, CountryRegionCode, and Group properties in the
SalesTerritory entity into a new complex type named LocationType. You will then create a complex
property of this type in the SalesTerritory entity, named Location.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Create the complex type.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex7\Starter or
E:\Labfiles\Lab02\CS\Ex7\Starter folder.

Task 2: Create the complex type


1. Open AdventureWorksEDM in the Entity Designer pane.
2. In the SalesTerritory entity, create a complex type named LocationType from the Name,
CountryRegionCode, and Group properties.
3. Change the name of the new complex property in the SalesTerritory entity to Location.
4. Build the solution.
5. Save and close the solution, and then close Visual Studio.
Lab Instructions: Querying Entity Data 1

Module 3
Lab Instructions: Querying Entity Data
Contents:
Exercise 1: Retrieving All Contact Entities 4
Exercise 2: Retrieving Contact Entities by Using a Filter 6
Exercise 3: Retrieving RewardsClaimed Entities 8
Exercise 4: Querying the Rewards Family of Entities 10
Exercise 5: Executing a Stored Procedure 12
2 Lab Instructions: Querying Entity Data

Lab: Querying Entity Data

Objectives
After completing this lab, you will be able to:
Query an entity model by using LINQ to Entities.
Filter data by using LINQ to Entities.
Query an entity model by using Entity SQL.
Query an entity model by using the EntityClient provider for the Entity Framework.
Query an entity model by using a stored procedure.

Introduction
In this lab, you will use several different techniques to execute queries against your entity model. In
addition, you will learn how to create unit tests for your data access code.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-03 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Querying Entity Data 3

Lab Scenario

Adventure Works implements an entity model to support its customer reward program. You have been
asked to implement a data access layer to provide the functionality that the customer rewards client
application requires. In this first phase, you only need to implement the functionality that is necessary to
retrieve data from the model.
You must add functionality to retrieve contact entities, reward entities, rewards claimed data, and the
number of orders that a contact has placed. You must include unit tests for all of your methods, and verify
that the client application displays the retrieved data correctly.
4 Lab Instructions: Querying Entity Data

Exercise 1: Retrieving All Contact Entities


Scenario
In this exercise, you will create a query that retrieves all of the contacts from the Adventure Works
database. You will do this by using LINQ to Entities to query the entity model.

The main tasks for this exercise are as follows:


1. Prepare the Adventure Works database for the lab.
2. Open the starter project for this exercise.
3. Add code to retrieve all of the contacts.
4. Add a unit test to verify your code.
5. Build and test the application.

Task 1: Prepare the Adventure Works database for the lab


1. Log on to the 10265A-GEN-DEV-03 virtual machine as Student with the password Pa$$w0rd.
2. Run AWReset.bat in the E:\Labfiles folder.

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex1\Starter or
E:\Labfiles\Lab03\VB\Ex1\Starter folder.

Task 3: Add code to retrieve all of the contacts


1. In Visual Studio, review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Retrieve all
contacts item in the task list. This task is located in the first GetContactList method.
3. Delete the existing code in the GetContactList method.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

b. Create and define a LINQ to Entities query to select all Contact entities.
c. Execute the query with MergeOption set to NoTracking.
d. Return the results.
5. Save the DataAccessLayer code file.

Task 4: Add a unit test to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex1 - Create a unit
test for GetContactList item in the task list. This task is located in the GetContactListTest method.
3. Delete the existing code in the GetContactListTest method.
Lab Instructions: Querying Entity Data 5

4. Write a unit test to compare the first 10 contacts returned by your query with the 10 contacts
returned by the GetLocalCustomerListFirstTen method. Be sure to release all resources at the end of
the test.
5. Save the DataAccessLayerTest code file.

Task 5: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that the application functions as expected.
4. Close the application.
5. Run all of the tests in the solution.
6. Verify that all of the tests succeed, including GetContactListTest.
7. Close the solution.
6 Lab Instructions: Querying Entity Data

Exercise 2: Retrieving Contact Entities by Using a Filter


Scenario
In this exercise, you will create a query that retrieves all of the contacts from the Adventure Works
database that have a specified last name. You will do this by using a LINQ to Entities query that takes a
parameter that specifies the name to match against.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Add code to retrieve contacts by last name.
3. Add a unit test to verify your code.
4. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex2\Starter or
E:\Labfiles\Lab03\VB\Ex2\Starter folder.

Task 2: Add code to retrieve contacts by last name


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - Retrieve contacts
by last name item in the task list. This task is located in the second GetContactList method.
3. Delete the existing code in the GetContactList method.
4. Add code to the method that performs the following tasks:
a. Instantiate the entities context object if it is currently null.
b. Create and define a LINQ to Entities query to retrieve Contact entities by last name.
c. Execute the query with MergeOption set to NoTracking.
d. Return the results.
5. Save the DataAccessLayer code file.

Task 3: Add a unit test to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex2 - Create a unit
test for GetContactList by last name item in the task list. This task is located in the
GetAContactListTest method.
3. Delete the existing code in the GetAContactListTest method.
4. Write a unit test to compare the first contacts returned by your query, using Adina as the last name
parameter, with the contacts returned by the GetLocalCustomerList method. Be sure to release all
resources at the end of the test.
5. Save the DataAccessLayerTest code file.

Task 4: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
3. In the AdventureWorks Rewards window, in the Name box, type Ward and then click Search. Verify
that the application functions as expected and loads the correct contacts into the data grid.
Lab Instructions: Querying Entity Data 7

4. Close the application.


5. Run all of the tests in the solution.
6. Verify that all of the tests succeed, including GetAContactListTest.
7. Close the solution.
8 Lab Instructions: Querying Entity Data

Exercise 3: Retrieving RewardsClaimed Entities


Scenario
In this exercise, you will create a query that retrieves from the Adventure Works database all of the
rewards that an individual contact has claimed. You will do this by using an Entity SQL query that takes a
parameter that specifies the contact ID of the contact to match against.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Add code to retrieve rewards claimed by contact ID.
3. Add a unit test to verify your code.
4. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex2\Starter or
E:\Labfiles\Lab03\VB\Ex3\Starter folder.

Task 2: Add code to retrieve rewards claimed by contact ID


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex3 - Retrieve rewards
claimed by contact ID item in the task list. This task is located in the GetRewardsClaimedList
method.
3. Delete the existing code in the GetRewardsClaimedList method.
4. Add code that performs the following tasks:
a. Instantiate the entities context object if it is currently null.
b. Create and define an Entity SQL query to retrieve RewardsClaimed entities by contact ID.
c. Execute the query with MergeOption set to NoTracking.
d. Return the results.
5. Save the DataAccessLayer code file.

Task 3: Add a unit test to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex3 - Create a unit
test for GetRewardsClaimedList item in the task list. This task is located in the
GetRewardsClaimedListTest method.
3. Delete the existing code in the GetRewardsClaimedListTest method.
4. Write a unit test to compare the RewardsClaimed entities returned by your query, using a contactID
of 2 as the parameter value, with the RewardsClaimed entities returned by the
GetLocalRewardsClaimedList method. Be sure to release all resources at the end of the test.
5. Save the DataAccessLayerTest code file.

Task 4: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
Lab Instructions: Querying Entity Data 9

3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.
Then, click a contact in the data grid to display rewards claimed by that contact in the second data
grid.
4. Close the application.
5. Run all of the tests in the solution.
6. Verify that all of the tests succeed, including GetRewardsClaimedListTest.
7. Close the solution.
10 Lab Instructions: Querying Entity Data

Exercise 4: Querying the Rewards Family of Entities


Scenario
In this exercise, you will create a query that retrieves details of AdventureWorksReward rewards only.
You will do this by using an Entity SQL query that takes a parameter that specifies the reward ID to match
against, and retrieves rewards of the specified type.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Add code to retrieve reward details by reward ID.
3. Add a unit test to verify your code.
4. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex4\Starter or
E:\Labfiles\Lab03\VB\Ex4\Starter folder.

Task 2: Add code to retrieve reward details by reward ID


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex4 - Get a reward by
reward ID item in the task list. This task is located in the GetReward method.
3. Delete the existing code in the GetReward method.
4. Use the EntityConnection, EntityCommand, and EntityDataReader classes to connect to the entity
model, retrieve the details of a reward by rewardID, and then return the result as a Reward entity.
Set the MergeOption of the query to NoTracking.
5. Save the DataAccessLayer code file.

Task 3: Add a unit test to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex4 - Create a unit
test for GetReward item in the task list. This task is located in the GetRewardTest method.
3. Delete the existing code in the GetRewardTest method.
4. Write a unit test to compare the Reward entity returned by your query, using a rewardID of 21 as
the parameter value, with the Reward entity returned by the GetLocalRewardData method. Be sure
to release all resources at the end of the test.
5. Save the DataAccessLayerTest code file.

Task 4: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.
Click a contact in the data grid to display reward claims in the second data grid. Then, click a rewards
claim in the second data grid to display the reward details on the form.
4. Close the application.
5. Run all of the tests in the solution.
Lab Instructions: Querying Entity Data 11

6. Verify that all of the tests succeed, including GetRewardTest.


7. Close the solution.
12 Lab Instructions: Querying Entity Data

Exercise 5: Executing a Stored Procedure


Scenario
In this exercise, you will invoke a stored procedure that returns the number of orders that a contact has
placed. You will do this by calling a method that wraps an imported function in the entity model.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Add code to retrieve the number of orders that a contact has placed.
3. Add a unit test to verify your code.
4. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex5\Starter or
E:\Labfiles\Lab03\VB\Ex5\Starter folder.

Task 2: Add code to retrieve the number of orders that a contact has placed
1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex5 - Call the
CountOrders stored procedure item in the task list. This task is located in the CountOrders method.
3. Delete the existing code in the CountOrders method.
4. Add code that performs the following tasks:
a. Instantiate the entities context object if it is currently null.
b. Invoke the CountOrders method on the context object.
c. Return the results.
5. Save the DataAccessLayer code file.

Task 3: Add a unit test to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex5 - Create a unit
test for CountOrders item in the task list. This task is located in the CountOrdersTest method.
3. Delete the existing code in the CountOrdersTest method.
4. Write a unit test to verify that the contact with a contact ID of 2 has placed four orders.
5. Save the DataAccessLayerTest code file.

Task 4: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.
Then, click a contact in the data grid to display the number of orders placed by that contact on the
form.
4. Close the application.
5. Run all of the tests in the solution.
6. Verify that all of the tests succeed, including CountOrdersTest.
7. Close Visual Studio.
Lab Instructions: Creating, Updating, and Deleting Entity Data 1

Module 4
Lab Instructions: Creating, Updating, and Deleting Entity
Data
Contents:
Exercise 1: Maintaining Contact and Reward Data 4
Exercise 2: Maintaining RewardsClaim Data 9
2 Lab Instructions: Creating, Updating, and Deleting Entity Data

Lab: Creating, Updating, and Deleting Entity Data

Objectives
After completing this lab, you will be able to:
Create, update, and delete entity data by using the default behavior of the EDM.
Create, update, and delete entity data by using stored procedures embedded in the EDM.

Introduction
In this lab, you will create, modify, and delete entity objects in the ObjectContext object. You will then
persist these changes to the database. You will also use stored procedures embedded in the EDM to
perform the database modifications. Note that data validation will be covered in a later lab.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-04 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Creating, Updating, and Deleting Entity Data 3

Lab Scenario

Adventure Works implements an entity model to support its customer reward program. You have been
asked to extend the data access layer to provide the functionality required by the customer rewards client
application. In this second phase, you must implement the functionality necessary to support creating,
updating, and deleting contacts, rewards, and claims.
4 Lab Instructions: Creating, Updating, and Deleting Entity Data

Exercise 1: Maintaining Contact and Reward Data


Scenario
In this exercise, you will update the data access layer to support creating, updating, and deleting contact
and reward entities. You will use the default behavior of the EDM, leaving the EDM to generate the
required database commands. You must also ensure that you delete any related claims when you delete a
contact.

The main tasks for this exercise are as follows:

1. Prepare the AdventureWorks database for the lab.


2. Open the starter project.
3. Add code to add a new contact.
4. Add code to update a contact.
5. Add code to delete a contact.
6. Add unit tests to verify your code.
7. Add code to add a new reward.
8. Add code to update a reward.
9. Add code to delete a reward.
10. Add unit tests to verify your code.
11. Build and test the application.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-04 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat.

Task 2: Open the starter project


1. Open Microsoft Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\CS\Ex1\Starter or
E:\Labfiles\Lab04\VB\Ex1\Starter folder.

Task 3: Add code to add a new contact


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Add a new Contact in
the task list. This task is located in the AddContact method.
3. In the AddContact method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

b. Encrypt the password in the contact passed as a parameter by calling the EncryptPassword
method.
c. Set the ModifiedDate property of the contact to the current date and time.
d. Assign a globally unique identifier (GUID) to the rowguid property of the contact.
e. Add the contact to the Contacts entityset.
Lab Instructions: Creating, Updating, and Deleting Entity Data 5

f. Save the changes to the database and return the new ContactID value.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 4: Add code to update a contact


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Modify an existing
Contact in the task list. This task is located in the UpdateContact method.
3. In the UpdateContact method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Get the EntityKey property of the detached contact passed to the method. This detached
contact contains the modified properties.
c. Use the TryGetObjectByKey method to load the correct contact into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a
parameter.
e. Save the changes to the database and return true.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 5: Add code to delete a contact


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Delete an existing
Contact in the task list. This task is located in the DeleteContact method.
3. In the DeleteContact method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Use the contactID variable passed as a parameter to create the EntityKey object of the contact to
delete.
c. Use the TryGetObjectByKey method to load the correct contact into the context.
d. Delete all of the related RewardsClaimed entities belonging to the contact from the context
e. Delete the contact from the context.
f. Save all of the changes to the database and return true.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 6: Add unit tests to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Add a test for
AddContact in the task list. This task is located in the AddContactTest method.
6 Lab Instructions: Creating, Updating, and Deleting Entity Data

3. In the AddContactTest method, delete the existing code.


4. Add a unit test to verify the behavior of the AddContact method. Use the CreateTestContact
method to create a contact to add to the database, and use the GetContactById method to retrieve
the contact from the database. Ensure that you remove the contact and release any resources at the
end of the test.
5. Locate the ModifyContactTest method by double-clicking the comment TODO: Ex1 - Add a test
for ModifyContact in the task list. This task is located in the ModifyContactTest method.
6. In the ModifyContactTest method, delete the existing code.
7. Add a unit test to verify the behavior of the ModifyContact method. Use the CreateTestContact
method to create a contact to add to the database, which you can then modify, and use the
GetContactById method to retrieve the contact from the database. Ensure that you remove the
contact and release any resources at the end of the test.
8. Locate the DeleteContactTest method by double-clicking the comment TODO: Ex1 - Add a test for
DeleteContact in the task list. This task is located in the DeleteContactTest method.
9. In the DeleteContactTest method, delete the existing code.
10. Add a unit test to verify the behavior of the DeleteContact method. Use the CreateTestContact
method to create a contact to add to the database, which you can then delete, and use the
GetContactById method to try to retrieve the contact from the database. Ensure that you release any
resources at the end of the test.
11. Save the DataAccessLayerTest file.

Task 7: Add code to add a new reward


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Add a new Reward in
the task list. This task is located in the AddReward method.
3. In the AddReward method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

b. Get the next available RewardID value by calling the GetNextRewardID method.
c. Add the reward to the Rewards entity set.
d. Save the changes to the database and return the RewardID value.
e. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 8: Add code to update a reward


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Modify an existing
Reward in the task list. This task is located in the UpdateReward method.
Lab Instructions: Creating, Updating, and Deleting Entity Data 7

3. In the UpdateReward method, delete the existing code.


4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Get the EntityKey property of the detached contact passed to the method. This detached
contact contains the modified properties.
c. Use the TryGetObjectByKey method to load the correct contact into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a
parameter.
e. Save the changes to the database and return true.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 9: Add code to delete a reward


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Delete an existing
Reward in the task list. This task is located in the DeleteReward method.
3. In the DeleteReward method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Use the rewardID value passed as a parameter to create the EntityKey object of the reward to
delete.
c. Use the TryGetObjectByKey method to load the correct reward into the context.
d. Delete the reward from the context.
e. Save the change to the database and return true.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 10: Add unit tests to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Add a test for
AddReward in the task list. This task is located in the AddRewardTest method.
3. In the AddRewardTest method, delete the existing code.
4. Add a unit test to verify the behavior of the AddReward method. Use the
CreateAdventureWorksRewardData and CreateSupermarketRewardData methods to create
rewards to add to the database, and use the GetRewardById method to retrieve the rewards from
the database. Ensure that you remove the rewards and release any resources at the end of the test.
5. Locate the ModifyRewardTest method by double-clicking the comment TODO: Ex1 - Add a test
for ModifyReward in the task list. This task is located in the ModifyRewardTest method.
6. In the ModifyRewardTest method, delete the existing code.
8 Lab Instructions: Creating, Updating, and Deleting Entity Data

7. Add a unit test to verify the behavior of the ModifyReward method. Use the
CreateSupermarketRewardData method to create a reward to add to the database, which you can
then modify, and use the GetRewardById method to retrieve the reward from the database. Ensure
that you remove the reward and release any resources at the end of the test.
8. Locate the DeleteRewardTest method by double-clicking the comment TODO: Ex1 - Add a test for
DeleteReward in the task list. This task is located in the DeleteRewardTest method.
9. In the DeleteRewardTest method, delete the existing code.
10. Add a unit test to verify the behavior of the DeleteReward method. Use the
CreateAdventureWorksRewardData method to create a reward to add to the database, which you
can then delete. Ensure that you release any resources at the end of the test.
11. Save the DataAccessLayerTest file.

Task 11: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model
into the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add
and modify Adventure Works reward data.
6. Close the application.
7. Close the solution.
Lab Instructions: Creating, Updating, and Deleting Entity Data 9

Exercise 2: Maintaining RewardsClaim Data


Scenario
In this exercise, you will update the data access layer to support creating, updating, and deleting claim
entities. You will import stored procedures into your EDM that will be used to perform the changes to the
database. You must also ensure that you adjust the number of points a contact has when you make a
change to the claims associated with the contact.
The main tasks for this exercise are as follows:
1. Open the starter project.
2. Add data modification stored procedures to your model.
3. Add code to add a new RewardsClaim record.
4. Add code to update a RewardsClaim record.
5. Add code to delete a RewardsClaim record.
6. Add unit tests to verify your code.
7. Build and test the application.

Task 1: Open the starter project


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\CS\Ex2\Starter or
E:\Labfiles\Lab04\VB\Ex2\Starter folder

Task 2: Add data modification stored procedures to your model


1. Open the AdventureWorksEDM model in the Entity Designer.
2. Run the Update Wizard to add the uspInsertRewardsClaim, uspUpdateRewardsClaim, and
uspDeleteRewardsClaim stored procedures to the model.
3. Map the RewardsClaimed entity to the stored procedures in the following table.

Function Stored Procedure

Insert uspInsertRewardsClaim
Update uspUpdateRewardsClaim
Delete uspDeleteRewardsClaim

4. Map the stored procedure parameters to the entity properties as shown in the following table.

Stored Procedure Parameter Property

uspInsertRewardsClaim claimID : int ClaimID : Int32

uspInsertRewardsClaim pointsUsed : int PointsUsed : Int32

uspInsertRewardsClaim rewardID : int RewardID : Int32

uspInsertRewardsClaim contactID : int ContactID : Int32

uspUpdateRewardsClaim claimID : int ClaimID : Int32

uspUpdateRewardsClaim pointsUsed : int PointsUsed : Int32

uspUpdateRewardsClaim rewardID : int RewardID : Int32

uspUpdateRewardsClaim contactID : int ContactID : Int32

uspDeleteRewardsClaim claimID : int ClaimID : Int32


10 Lab Instructions: Creating, Updating, and Deleting Entity Data

5. Save the AdventureWorksEDM model.

Task 3: Add code to add a new RewardsClaim record


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - Add a new
RewardsClaimed entity in the task list. This task is located in the CreateRewardsClaim method.
3. In the CreateRewardsClaim method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

b. Set the ClaimID value by calling the GetNextClaimID method.


c. Add the claim to the RewardsClaimed entity set.
d. Decrement the points for the associated contact by the points used for the claim.
e. Save all of the changes to the database and return the new ClaimID value.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 4: Add code to update a RewardsClaim record


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - Update a
RewardsClaimed entity in the task list. This task is located in the UpdateRewardsClaim method.
3. In the UpdateRewardsClaim method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Get the EntityKey property of the detached claim passed to the method. This detached claim
contains the modified properties.
c. Use the TryGetObjectByKey method to load the correct claim into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a
parameter.
e. Adjust the points for the associated contact by the difference between the old and the new
points for the claim.
f. Save all of the changes to the database and return true.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 5: Add code to delete a RewardsClaim record


1. Review the task list.
Lab Instructions: Creating, Updating, and Deleting Entity Data 11

2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - Delete a
RewardsClaimed entity in the task list. This task is located in the DeleteRewardsClaim method.
3. In the DeleteRewardsClaim method, delete the existing code.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Use the claimID value passed as a parameter to create the EntityKey object of the claim to
delete.
c. Use the TryGetObjectByKey method to load the correct claim into the context.
d. Give the points of the claim back to the associated contact.
e. Delete the claim from the context.
f. Save all of the changes to the database and return true.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
5. Save the DataAccessLayer file.

Task 6: Add unit tests to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex2 - Add a test for
CreateRewardsClaim in the task list. This task is located in the CreateRewardsClaimTest method.
3. In the CreateRewardsClaimTest method, delete the existing code.
4. Add a unit test to verify the behavior of the CreateRewardsClaim method. Use the
CreateLocalClaim method to create a claim to add to the database, and use the
GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you remove
the rewards and release any resources at the end of the test.
5. Locate the UpdateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a
test for UpdateRewardsClaim in the task list. This task is located in the UpdateRewardsClaimTest
method.
6. In the UpdateRewardsClaimTest method, delete the existing code.
7. Add a unit test to verify the behavior of the UpdateRewardsClaim method. Use the
CreateLocalClaim method to create a claim to add to the database, which you can then modify, and
use the GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you
remove the claim and release any resources at the end of the test.
8. Locate the DeleteRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a
test for DeleteRewardsClaim in the task list. This task is located in the DeleteRewardsClaimTest
method.
9. In the DeleteRewardsClaimTest method, delete the existing code.
10. Add a unit test to verify the behavior of the DeleteRewardsClaim method. Use the
CreateLocalClaim method to create a claim to add to the database, which you can then delete.
Ensure that you release any resources at the end of the test.
11. Save the DataAccessLayerTest file.

Task 7: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
12 Lab Instructions: Creating, Updating, and Deleting Entity Data

3. Verify that all of the tests succeed, including the GetContactListTest test.
4. Start the application in Debug mode.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model
into the data grid. Verify that you can add, modify, and delete claims and that the points for the
contact are adjusted correctly.
6. Close the application.
7. Close the solution, and then close Visual Studio.
Lab Instructions: Handling Multi-User Scenarios by Using Object Services 1

Module 5
Lab Instructions: Handling Multi-User Scenarios by Using
Object Services
Contents:
Exercise 1: Handling Concurrency of Rewards Claimed Data 4
Exercise 2: Updating the RewardsClaimed and ArchivedRewardsClaimed
Information by Using a Transaction 7
2 Lab Instructions: Handling Multi-User Scenarios by Using Object Services

Lab: Handling Multi-User Scenarios by Using Object


Services

Objectives
After completing this lab, you will be able to:
Manage concurrency in a multi-user application that uses the Entity Framework.
Create and manage transactions in an application that uses the Entity Framework.

Introduction
In this lab, you will update your EDM to define how the Entity Framework detects concurrency conflicts.
You will then add code to your data access layer that resolves any concurrency conflicts. You will also
define a transaction that guarantees the integrity of your data when several updates take place together.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-05 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Handling Multi-User Scenarios by Using Object Services 3

Lab Scenario

Adventure Works implements an EDM to support its customer reward program. You have been asked to
modify the data access layer to ensure that rewards claim data is correctly saved to the database in
circumstances where multiple users edit the same records simultaneously.
You have also been asked to save copies of all updated and inserted rewards claim data to an archive
table to provide an audit trail of the data modifications performed by users.
4 Lab Instructions: Handling Multi-User Scenarios by Using Object Services

Exercise 1: Handling Concurrency of Rewards Claimed Data


Scenario
Users have reported that sometimes a contact's reward points do not update correctly when they modify
claims. You have been asked to implement concurrency checking in the data access layer to prevent these
errors. You should create unit tests to verify your solution.

The main tasks for this exercise are as follows:


1. Prepare the AdventureWorks database for the lab.
2. Open the starter project for this exercise.
3. Set the concurrency behavior of the Contact and RewardsClaimed entities.
4. Add code to set the ModifiedDate property of the contact.
5. Add code to handle OptimisticConcurrencyException exceptions in the CreateRewardsClaim
method.
6. Add code to handle OptimisticConcurrencyException exceptions in the UpdateRewardsClaim
method.
7. Add code to handle OptimisticConcurrencyException exceptions in the DeleteRewardsClaim
method.
8. Add unit tests to verify your code.
9. Build and test the application.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-05 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat.

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex1\Starter or
E:\Labfiles\Lab05\CS\Ex1\Starter folder.

Task 3: Set the concurrency behavior of the Contact and RewardsClaimed entities
1. Open the AdventureWorksEDM model in the Entity Designer.
2. In the AdventureWorksEDM model, set the Concurrency Mode property of the ModifiedDate
property of the Contact entity to Fixed.
3. In the AdventureWorksEDM model, set the Concurrency Mode property of the TimeStamp property
of the RewardsClaimed entity to Fixed.
4. Save the AdventureWorks EDM model.

Task 4: Add code to set the ModifiedDate property of the contact


1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Update the contact's
Modified Date property item in the task list. This task is located in the UpdateContact method.
3. Immediately after the comment, add code that sets the ModifiedDate property of the contact being
saved to the current date and time.
4. Save the DataAccessLayer file.
Lab Instructions: Handling Multi-User Scenarios by Using Object Services 5

Task 5: Add code to handle OptimisticConcurrencyException exceptions in the


CreateRewardsClaim method
1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Handle the
OptimisticConcurrencyException in CreateRewardsClaim item in the task list. This task is located
in the CreateRewardsClaim method.
3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException
exception. In the catch block, add code that performs the following tasks:
a. Refresh the contact with the current values from the database.
b. Deduct the points used for the claim from the contact.
c. Set the ModifiedDate property of the contact to the current date and time.
d. Save the changes to the database.
e. Refresh the contact and the claim with the current values from the database.
f. Return the new claimID value.
4. Save the DataAccessLayer file.

Task 6: Add code to handle OptimisticConcurrencyException exceptions in the


UpdateRewardsClaim method
1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Handle the
OptimisticConcurrencyException in UpdateRewardsClaim item in the task list. This task is located
in the UpdateRewardsClaim method.
3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException
exception. In the catch block, add code that performs the following tasks:
a. Refresh the contact with the current values from the database.
b. Refresh the claim, keeping any changes made in the context.
c. Deduct the points used for the original claim from the contact, and add the points used for the
modified claim to the contact.
d. Set the ModifiedDate property of the contact to the current date and time.
e. Save the changes to the database.
f. Refresh the contact and the claim with the current values from the database.
g. Return true.
4. Save the DataAccessLayer file.

Task 7: Add code to handle OptimisticConcurrencyException exceptions in the


DeleteRewardsClaim method
1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Handle the
OptimisticConcurrencyException in DeleteRewardsClaim item in the task list. This task is located
in the DeleteRewardsClaim method.
3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException
exception. In the catch block, add code that performs the following tasks:
a. Refresh the contact with the current values from the database.
b. Refresh the claim, keeping any changes made in the context.
c. Add the points used for the deleted claim to the contact.
6 Lab Instructions: Handling Multi-User Scenarios by Using Object Services

d. Set the ModifiedDate property of the contact to the current date and time.
e. Save the changes to the database.
f. Refresh the contact with the current values from the database.
g. Return true.
4. Save the DataAccessLayer file.

Task 8: Add unit tests to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Create a test to
verify that CreateRewardsClaim handles concurrency issues item in the task list. This task is
located in the CreateRewardsClaimConcurrencyTest method.
3. Using the comments in the CreateRewardsClaimConcurrencyTest method for guidance, write code
that verifies the behavior of the CreateRewardsClaim method when two users modify the same
contact while they are adding new claims to the database.
4. Locate the UpdateRewardsClaimConcurrencyTest method by double-clicking the comment TODO:
Ex1 - Create a test to verify that UpdateRewardsClaim handles concurrency issues item in the
task list. This task is located in the UpdateRewardsClaimConcurrencyTest method.
5. Using the comments in the UpdateRewardsClaimConcurrencyTest method for guidance, write
code that verifies the behavior of the UpdateRewardsClaim method when two users modify the
same contact while they are updating claims to the database.
6. Locate the DeleteRewardsClaimConcurrencyTest method by double-clicking the comment TODO:
Ex1 - Create a test to verify that DeleteRewardsClaim handles concurrency issues item in the
task list. This task is located in the DeleteRewardsClaimConcurrencyTest method.
7. Using the comments in the DeleteRewardsClaimConcurrencyTest method for guidance, write code
that verifies the behavior of the DeleteRewardsClaim method when two users modify the same
contact while they are inserting and deleting claims in the database.
8. Save the DataAccessLayerTest file.

Task 9: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed, including the CreateRewardsClaimConcurrencyTest,
UpdateRewardsClaimConcurrencyTest, and DeleteRewardsClaimConcurrencyTest tests.
4. Close the solution.
Lab Instructions: Handling Multi-User Scenarios by Using Object Services 7

Exercise 2: Updating the RewardsClaimed and ArchivedRewardsClaimed


Information by Using a Transaction
Scenario
You have been asked to modify the data access layer to save a copy of every new and updated reward
claimed to an archive table in a separate database as an audit trail. You must ensure that you create the
archived record only if the change to the reward claimed record succeeds. You should create a unit test to
verify that your transaction works correctly.

The main tasks for this exercise are as follows:

1. Open the starter project for this exercise.


2. Create the AdventureWorksArchivedEDM Entity Data Model.
3. Modify the CreateRewardsClaim method to save a copy of the claim to the archive table as part of a
transaction.
4. Modify the UpdateRewardsClaim method to save a copy of the claim to the archive table as part of
a transaction.
5. Modify the unit tests to verify your code.
6. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex2\Starter or
E:\Labfiles\Lab05\CS\Ex2\Starter folder.

Task 2: Create the AdventureWorksArchivedEDM Entity Data Model


1. Add a new ADO.NET Entity Data Model to the DAL project. Generate the data model from the
AdventureWorks Microsoft SQL Server database and create entities for the
ArchivedRewardsClaimed table.
2. Copy the AdventureWorksArchivedEntities connection string from the App.Config file in the DAL
project to the App.Config file in the DALTest project.

Task 3: Modify the CreateRewardsClaim method to save a copy of the claim to the
archive table as part of a transaction
1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - In
CreateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an
ArchivedRewardsClaim item in the task list. This task is located in the CreateRewardsClaim
method.
3. Place the call to the SaveChanges method inside a new TransactionScope code block. In the
TransactionScope code block, after the call to the SaveChanges method, add code to perform the
following tasks:
a. Create a new AdventureWorksArchivedEntities context called archivedEntities.
b. In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a
copy of the data in the RewardsClaimed entity.
c. Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.
d. Save all of the changes in the archivedEntities context.
e. At the end of the TransactionScope code block, call the Complete method of the
TransactionScope object.
8 Lab Instructions: Handling Multi-User Scenarios by Using Object Services

4. Add code to the CreateRewardsClaim method to handle the TransactionAbortedException


exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 -
In CreateRewardsClaim, handle TransactionAbortedException item in the task list.
5. Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh
the contact and the claim from the database, and throw a new DALException exception to report the
error.
6. Save the DataAccessLayer file.

Task 4: Modify the UpdateRewardsClaim method to save a copy of the claim to the
archive table as part of a transaction
1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - In
UpdateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an
ArchivedRewardsClaim item in the task list. This task is located in the UpdateRewardsClaim
method.
3. Place the call to the SaveChanges method inside a new TransactionScope code block. In the
TransactionScope code block, after the call to the SaveChanges method, add code to perform the
following tasks:
a. Create a new AdventureWorksArchivedEntities context called archivedEntities.
b. In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a
copy of the data in the RewardsClaimed entity.
c. Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.
d. Save all of the changes in the archivedEntities context.
e. At the end of the TransactionScope code block, call the Complete method of the
TransactionScope object.
4. Add code to the UpdateRewardsClaim method to handle the TransactionAbortedException
exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 -
In UpdateRewardsClaim, handle TransactionAbortedException item in the task list.
5. Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh
the contact and the claim from the database, and throw a new DALException exception to report the
error.
6. Save the DataAccessLayer file.

Task 5: Modify the unit tests to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex2 - Count the
archived claims before the insert item in the task list. This task is located in the
CreateRewardsClaimTest method.
3. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method.
4. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived
claims after the insert and test item in the task list. This task is located in the
CreateRewardsClaimTest method.
Lab Instructions: Handling Multi-User Scenarios by Using Object Services 9

5. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method, and verify that the number of archived rewards has
increased by one.
6. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived
claims before the update item in the task list. This task is located in the UpdateRewardsClaimTest
method.
7. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method.
8. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived
claims after the update and test item in the task list. This task is located in the
UpdateRewardsClaimTest method.
9. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method, and verify that the number of archived rewards has
increased by one.
10. Save the DataAccessLayerTest file.

Task 6: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed, including the CreateRewardsClaimTest and
UpdateRewardsClaimTest tests.
4. Close the solution, and then close Visual Studio.
Lab Instructions: Building Optimized Solutions by Using Object Services 1

Module 6
Lab Instructions: Building Optimized Solutions by Using
Object Services
Contents:
Exercise 1: Improving the Performance of Query Operations 4
Exercise 2: Improving the Performance of Update Operations 7
2 Lab Instructions: Building Optimized Solutions by Using Object Services

Lab: Building Optimized Solutions by Using Object


Services

Objectives
After completing this lab, you will be able to:
Compare the performance of different query implementations.
Create, update, and delete entity data asynchronously.

Introduction
In this lab, you will write code to analyze the performance of a query that you will implement by using
different technologies and options. You will compare the results of the same query when you implement
it by using LINQ to Entities, compiled LINQ to Entities, and Entity SQL. You will explore the effect of
change tracking and generating views at design time on query performance. You will also perform data
modifications asynchronously.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-06 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Building Optimized Solutions by Using Object Services 3

Lab Scenario

Adventure Works implements an entity model to support its customer reward program. Users of the client
application have complained about the poor performance of some operations.
You have been asked to evaluate the various options for running queries that the Entity Framework offers.
You have also been asked to modify the existing data access layer to perform data modifications
asynchronously.
4 Lab Instructions: Building Optimized Solutions by Using Object Services

Exercise 1: Improving the Performance of Query Operations


Scenario
In this exercise, you will create a console application to explore the performance of different query
implementations in the data access layer and analyze the results.

The main tasks for this exercise are as follows:


1. Prepare the AdventureWorks database for the lab.
2. Open the starter project for this exercise.
3. Print timing information during query execution.
4. Add code to define a compiled LINQ query.
5. Add code to invoke the compiled LINQ query.
6. Add code to retrieve all of the contact entities by using Entity SQL.
7. Modify the GetContactList method to check the NoTracking variable.
8. Build and test the application.
9. Pre-generate views to improve query performance.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-06 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat.

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex1\Starter or
E:\Labfiles\Lab06\CS\Ex1\Starter folder.

Task 3: Print timing information during query execution


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Return all contacts
with timings item in the task list. This task is located in the GetContactListDetail method.
3. Delete the comment in the GetContactListDetail method.
4. Write code that performs the following tasks:
a. Instantiate and start two Stopwatch objects called totaltime and stagetime.
b. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

c. Print the value of the ElapsedTime property from the stagetime object, and then restart the
Stopwatch.
d. Retrieve an ObjectQuery object from the context's Contacts property.
e. Print the value of the ElapsedTime property from the stagetime object, and then restart the
Stopwatch.
f. Define and execute a LINQ query to retrieve all of the contacts from the ObjectQuery object,
and then save the results to a List object.
Lab Instructions: Building Optimized Solutions by Using Object Services 5

g. Print the value of the ElapsedTime property from the stagetime object, and then restart the
Stopwatch.
h. Print the value of the ElapsedTime property from the totaltime object, and then restart the
Stopwatch.
i. Return the List object.
5. Save the DataAccessLayer code file.

Task 4: Add code to define a compiled LINQ query


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Define the compiled
LINQ query item in the task list. This task is located in the DataAccessLayer class.
3. Immediately after the comment, add code to define a compiled LINQ query called compiledQuery
by using a static function. The query should return all of the contact entities from the EDM.
4. Save the DataAccessLayer code file.

Task 5: Add code to invoke the compiled LINQ query


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Retrieve all contacts
using the compiled query item in the task list. This task is located in the
GetContactListEntityCompiledLINQ method.
3. Delete the comment in the GetContactListEntityCompiledLINQ method.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Obtain an ObjectQuery object by invoking the compiled LINQ query.
c. If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption
property to NoTracking.
d. Return the contact entities in a List object.
5. Save the DataAccessLayer code file.

Task 6: Add code to retrieve all of the contact entities by using Entity SQL
1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Retrieve all
contacts using Entity SQL item in the task list. This task is located in the
GetContactListEntityQuery method.
3. Delete the comment in the GetContactListEntityQuery method.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Obtain an ObjectQuery object by creating a query that uses Entity SQL to retrieve all of the
contact entities from the EDM.
c. If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption
property to NoTracking.
6 Lab Instructions: Building Optimized Solutions by Using Object Services

d. Return the contact entities in a List object.


5. Save the DataAccessLayer code file.

Task 7: Modify the GetContactList method to check the NoTracking variable


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Check
NoTracking item in the task list. This task is located in the GetContactList method.
3. Modify the line of code immediately below the comment to check whether the value of the
NoTracking variable is true before you set the MergeOption property to NoTracking.
4. Save the DataAccessLayer code file.

Task 8: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
3. Observe the timing results obtained by running the TimingTests application:
a. In the command window, on the Detailed Timing for GetContactList() page, make a note of
the Total Time values, and then press ENTER.
b. In the command window, on the Compare Implementations of GetContactList() page, write
down the Average values, and then press ENTER.

Task 9: Pre-generate views to improve query performance


1. Open the AdventureWorksEDM.edmx file and change the Metadata Artifact Processing property to
Copy to Output Directory.
2. Save the AdventureWorksEDM.edmx file and build the solution.
3. Add view generation to the DAL project by using EdmGen.exe to generate the views during the pre-
build event.
4. Add the generated views to the project.
5. Update the connection strings in the TimingTests project to use the new metadata resources.
6. Start the application in Debug mode.
7. Observe the timing results obtained by running the TimingTests application:
a. In the command window, on the Detailed Timing for GetContactList() page, write down the
Total Time values, and then press ENTER.
b. In the command window, on the Compare Implementations of GetContactList() page, write
down the Average values, and then press ENTER.
8. Close the solution.
Lab Instructions: Building Optimized Solutions by Using Object Services 7

Exercise 2: Improving the Performance of Update Operations


Scenario
In this exercise, you will modify the data access layer so that you can create, update, and delete claim
entities asynchronously. You will use the BackgroundWorker class to perform the modifications on a
background thread.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Modify the CreateRewardsClaim method to run asynchronously.
3. Modify the UpdateRewardsClaim method to run asynchronously.
4. Modify the DeleteRewardsClaim method to run asynchronously.
5. Modify your unit tests to verify your asynchronous code.
6. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex2\Starter or
E:\Labfiles\Lab06\CS\Ex2\Starter folder.

Task 2: Modify the CreateRewardsClaim method to run asynchronously


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In
CreateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in
the CreateRewardsClaim method.
3. Immediately after the comment, write code that performs the following tasks:
a. Instantiate a new BackgroundWorker object.
b. Set the WorkerSupportsCancellation property to false.
c. Set the WorkerReportsProgress property to false.
4. Locate the next comment TODO: Ex2 - In CreateRewardsClaim, place existing code in DoWork
item in the task list. This task is located in the CreateRewardsClaim method.
5. Assign the existing code in the CreateRewardsClaim method to the BackgroundWorker object's
DoWork event handler by using a lambda expression. Replace the two existing return statements
with statements that assign the new claim to the Result property of the DoWork event's args
parameter.
6. Locate the next comment TODO: Ex2 - In CreateRewardsClaim, implement the
BackgroundWorkers RunWorkerCompleted event item in the task list. This task is located in the
CreateRewardsClaim method.
7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted
event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted
method with false as the first parameter, the error message as the second parameter, and -1 as the
third parameter. If the DoWork event completed without errors, call the
OnDataModificationCompleted method with true as the first parameter, a success message as the
second parameter, and the claimID property of the new claim as the third parameter.
8. Locate the next comment TODO: Ex2 - In CreateRewardsClaim, start the BackgroundWorker item
in the task list. This task is located in the CreateRewardsClaim method.
8 Lab Instructions: Building Optimized Solutions by Using Object Services

9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
10. Save the DataAccessLayer code file.

Task 3: Modify the UpdateRewardsClaim method to run asynchronously


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In
UpdateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in
the UpdateRewardsClaim method.
3. Immediately after the comment, write code that performs the following tasks:
a. Instantiate a new BackgroundWorker object.
b. Set the WorkerSupportsCancellation property to false.
c. Set the WorkerReportsProgress property to false.
4. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,
place existing code in DoWork item in the task list. This task is located in the
UpdateRewardsClaim method.
5. Assign the existing code in the UpdateRewardsClaim method to the BackgroundWorker object's
DoWork event handler by using a lambda expression. Replace the two existing return statements
with statements that assign the updated claim to the Result property of the DoWork event's args
parameter.
6. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,
implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is
located in the UpdateRewardsClaim method.
7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted
event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted
method with false as the first parameter, the error message as the second parameter, and -1 as the
third parameter. If the DoWork event completed without errors, call the
OnDataModificationCompleted method with true as the first parameter, a success message as the
second parameter, and the claimID property of the updated claim as the third parameter.
8. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,
start the BackgroundWorker item in the task list. This task is located in the UpdateRewardsClaim
method.
9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
10. Save the DataAccessLayer code file.

Task 4: Modify the DeleteRewardsClaim method to run asynchronously


1. Review the task list.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In
DeleteRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in
the DeleteRewardsClaim method.
3. Immediately after the comment, write code that performs the following tasks:
a. Instantiate a new BackgroundWorker object.
b. Set the WorkerSupportsCancellation property to false.
Lab Instructions: Building Optimized Solutions by Using Object Services 9

c. Set the WorkerReportsProgress property to false.


4. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,
place existing code in DoWork item in the task list. This task is located in the DeleteRewardsClaim
method.
5. Assign the existing code in the DeleteRewardsClaim method to the BackgroundWorker object's
DoWork event handler by using a lambda expression. Delete the two existing return statements.
6. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,
implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is
located in the DeleteRewardsClaim method.
7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted
event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted
method with false as the first parameter, the error message as the second parameter, and -1 as the
third parameter. If the DoWork event completed without errors, call the
OnDataModificationCompleted method with true as the first parameter, a success message as the
second parameter, and the claimID property of the deleted claim as the third parameter.
8. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,
start the BackgroundWorker item in the task list. This task is located in the DeleteRewardsClaim
method.
9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
10. Save the DataAccessLayer code file.

Task 5: Modify your unit tests to verify your asynchronous code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex2 - In
CreateRewardsClaimTest, call CreateRewardsClaim item in the task list. This task is located in the
CreateRewardsClaimTest method.
3. Review the existing code in the CreateRewardsClaimTest method.
4. Immediately after the comment, call the CreateRewardsClaim method in the data access layer,
passing the test claim called claim as a parameter.
5. Locate the next TODO comment in the CreateRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In CreateRewardsClaimTest, check the values retrieved from the
database item in the task list.
6. Immediately after the comment, add code to check that the property values of the claim object
match those of the lastClaim object.
7. Locate the first TODO comment in the UpdateRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In UpdateRewardsClaimTest, modify the claim and save the changes
item in the task list.
8. Review the existing code in the UpdateRewardsClaimTest method.
9. Immediately after the comment, modify the PointsUsed and RewardID properties of the claim
object, and call the UpdateRewardsClaim method in the data access layer, passing the test claim
called claim as a parameter.
10. Locate the next TODO comment in the UpdateRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In UpdateRewardsClaimTest, check the values retrieved from the
database item in the task list.
10 Lab Instructions: Building Optimized Solutions by Using Object Services

11. Immediately after the comment, add code to check that the property values of the claim object
match those of the updatedClaim object and that the value of the updateResult variable is true.
12. Locate the first TODO comment in the DeleteRewardsClaimTest method by double-clicking the
TODO: Ex2 - In DeleteRewardsClaimTest, delete the claim item in the task list.
13. Review the existing code in the DeleteRewardsClaimTest method.
14. Immediately after the comment, call the DeleteRewardsClaim method in the data access layer,
passing the claim object's ClaimID property as a parameter.
15. Locate the next TODO comment in the DeleteRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In DeleteRewardsClaimTest, check the delete succeeded item in the task
list.
16. Immediately after the comment, add code to check that the value of the deleteResult variable is true.
17. Save the DataAccessLayerTest code file.

Task 6: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed.
4. Start the Customer Rewards application in Debug mode.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that you can add, modify, and delete claims and that the contacts points are
adjusted correctly.
6. Close the application.
7. Close the solution, and then close Visual Studio.
Lab Instructions: Customizing Entities and Building Custom Entity Classes 1

Module 7
Lab Instructions: Customizing Entities and Building Custom
Entity Classes
Contents:
Exercise 1: Using a Template to Add Custom Functionality to Entity Classes 4
Exercise 2: Creating Custom Entity Classes 7
2 Lab Instructions: Customizing Entities and Building Custom Entity Classes

Lab: Customizing Entities and Building Custom Entity


Classes

Objectives
After completing this lab, you will be able to:
Use code templates to add custom functionality to entity classes.
Create custom entity classes.

Introduction
In this lab, you will use a T4 template to add custom functionality to the existing Contact class. You will
then replace this class with an existing business class that already contains all of the business functionality
that you require. You will modify this class to inherit from EntityObject and work with Object Services.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-07 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Customizing Entities and Building Custom Entity Classes 3

Lab Scenario

You are concerned that some of the older existing applications that use your data access code might be
saving invalid contact data. You decide to implement a defense-in-depth strategy and add some custom
validation logic to the Contact entity class in your data access layer to support these older applications.

You then discover that the business logic and validation rules for contacts is much more complicated than
you originally anticipated. Instead of implementing the validation logic for this class yourself, you decide
to expose the existing business class, which already validates data as an entity class.
4 Lab Instructions: Customizing Entities and Building Custom Entity Classes

Exercise 1: Using a Template to Add Custom Functionality to Entity Classes


Scenario
In this exercise, you will define an interface called IValidate that exposes a single method called Validate.
You will add a code generation item to the EDM and customize the T4 template to include the IValidate
interface in all generated entities and invoke a partial method called OnValidate. You will then add a
partial class file for the Contact entity in the EDM and implement the OnValidate method for the entity.
The OnValidate method will validate the data in the entity and throw an exception if any of this data is
invalid when adding or updating a contact.

The main tasks for this exercise are as follows:


1. Prepare the AdventureWorks database for the lab.
2. Open the starter project for this exercise.
3. Create the IValidate class.
4. Create the template.
5. Customize the template.
6. View the generated code.
7. Implement the OnValidate method.
8. Modify the DAL code to validate the data.
9. Add unit tests to verify your code.
10. Build and test the application.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-07 virtual machine as Student with the password Pa$$w0rd.
2. Run AWReset.bat in the E:\Labfiles folder.

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab07\VB\Ex1\Starter or
E:\Labfiles\Lab07\CS\Ex1\Starter folder.

Task 3: Create the IValidate class


1. Add a new interface named IValidate to the DAL project.
2. Modify the interface definition to make it public, and add a void method named Validate that takes
no arguments.

Task 4: Create the template


1. Add an ADO.NET EntityObject Generator item named AWModel.tt to the DAL project.
2. Open AdventureWorksEDM.Designer.cs or AdventureWorksEDM.Designer.vb and review the
comment that it contains.

Task 5: Customize the template


1. In AWModel.tt, locate the line of code that begins <#=Accessibility.ForType(entity)#>.
2. Edit the line of code to make every entity object implement the IValidate interface.
3. Within the body of this section, declare a partial void method named OnValidate that takes no
arguments.
4. Immediately after the statement that declares the OnValidate method, implement the
IValidate.Validate method. Inside this method, call the OnValidate method that you have just
declared.
Lab Instructions: Customizing Entities and Building Custom Entity Classes 5

5. If you are using Microsoft Visual Basic, you must also manually adjust the namespace to match the
rest of the project.

Task 6: View the generated code


Build the solution, and then review the generated code in each class in AWModel.cs or AWModel.vb.

Task 7: Implement the OnValidate method


1. Add a new class named ContactExtension to the DAL project.
2. Modify the class definition to define the class as a public partial class for the Contact class.
3. Add a void method named OnValidate to the class. If you are using Microsoft Visual C#, this
method should be declared partial.
4. Add code to the OnValidate method to throw a DALValidationException exception in each of the
following scenarios:
If the CurrentPoints property is set to a negative value.
If the EmailAddress property does not contain an @ symbol.
If the EmailAddress property does not contain a period.

Task 8: Modify the DAL code to validate the data


1. In the DataAccessLayer class, modify the UpdateContact method to call the Validate method
before saving changes to the object.
2. In the DataAccessLayer class, modify the AddContact method to call the Validate method before
saving changes to the object.
3. In the CustomerRewards project, in the MainWindow.xaml.cs or MainWindow.xaml.vb class, in the
contacts_MouseDoubleClick event, refresh the contacts list.

Task 9: Add unit tests to verify your code


1. Review the task list.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex1 - Add a test
for AddContact when there is a CurrentPoints validation exception item in the task list. This task
is located in the AddContactCurrentPointsValidationTest method.
3. Add an ExpectedException attribute to the method for the DALValidationException type.
4. Delete the comment in the AddContactCurrentPointsValidationTest method.
5. Add a unit test to create a Contact object, set the CurrentPoints property of the Contact object to
an invalid value, and then add the contact to the database. Be sure to release all resources at the end
of the test.
6. Locate the AddContactAtSymbolValidationTest method by double-clicking the comment TODO:
Ex1 - Add a test for AddContact when there is a missing @ sign in the e-mail address
validation exception item in the task list.
7. Add an ExpectedException attribute to the method for the DALValidationException type.
8. Delete the comment in the AddContactAtSymbolValidationTest method.
9. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to
an invalid value with a missing @ symbol, and then add the contact to the database. Be sure to
release all resources at the end of the test.
10. Locate the AddContactPeriodValidationTest method by double-clicking the comment TODO: Ex1
- Add a test for AddContact when there is a missing period in the e-mail address validation
exception item in the task list.
11. Add an ExpectedException attribute to the method for the DALValidationException type.
12. Delete the comment in the AddContactPeriodValidationTest method.
6 Lab Instructions: Customizing Entities and Building Custom Entity Classes

13. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to
an invalid value with a missing period, and then add the contact to the database. Be sure to release all
resources at the end of the test.
14. Locate the UpdateContactCurrentPointsValidationTest method by double-clicking the comment
TODO: Ex1 - Add a test for UpdateContact when there is a CurrentPoints validation exception
item in the task list.
15. Add an ExpectedException attribute to the method for the DALValidationException type.
16. Delete the comment in the UpdateContactCurrentPointsValidationTest method.
17. Add a unit test to create a Contact object, retrieve that contact from the database, set the
CurrentPoints property of that Contact object to an invalid value, and then update the contact in
the database. Be sure to release all resources at the end of the test.
18. Locate the UpdateContactAtSymbolValidationTest method by double-clicking the comment
TODO: Ex1 - Add a test for UpdateContact when there is a missing @ sign in the e-mail
address validation exception item in the task list.
19. Add an ExpectedException attribute to the method for the DALValidationException type.
20. Delete the comment in the UpdateContactAtSymbolValidationTest method.
21. Add a unit test to create a Contact object, retrieve that contact from the database, set the
EmailAddress property of the Contact object to an invalid value with a missing @ symbol, and then
update the contact in the database. Be sure to release all resources at the end of the test.
22. Locate the UpdateContactPeriodValidationTest method by double-clicking the comment TODO:
Ex1 - Add a test for UpdateContact when there is a missing period in the e-mail address
validation exception item in the task list.
23. Add an ExpectedException attribute to the method for the DALValidationException type.
24. Delete the comment in the UpdateContactPeriodValidationTest method.
25. Add a unit test to create a Contact object, retrieve that contact from the database , set the
EmailAddress property of the Contact object to an invalid value with a missing period, and then add
the contact to the database. Be sure to release all resources at the end of the test.
26. Save the DataAccessLayerTest code file.

Task 10: Build and test the application


1. Build the solution and correct any errors.
2. Start the application in Debug mode.
3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that the application functions as expected.
4. Close the application.
5. Run all of the tests in the solution.
6. Verify that all of the tests succeed.
7. Close the solution.
Lab Instructions: Customizing Entities and Building Custom Entity Classes 7

Exercise 2: Creating Custom Entity Classes


Scenario
In this exercise, you will modify an existing business class that models customers and add functionality
that enables it to operate as an entity class. You will inherit from the EntityObject class, and will add
scalar and navigational properties that are exposed to the EDM. You will replace the existing Contact
entity class in the data access layer with this custom implementation.

The main tasks for this exercise are as follows:

1. Open the starter project for this exercise.


2. Remove the existing Contact class from the DAL project.
3. Add the existing business class to the DAL project.
4. Modify the business class to operate as an entity class.
5. Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class (for Visual Basic only).
6. Modify the user interface to catch the validation exception.
7. Build and test the application.

Task 1: Open the starter project for this exercise


1. Open the DAL solution in the E:\Labfiles\Lab07\VB\Ex2\Starter or E:\Labfiles\Lab07\CS\Ex2\Starter
folder.
2. If you are using Visual C#, if a Problem Loading message is displayed, on the Build menu, click
Rebuild Solution, and then in the designer pane, click Reload the designer.

Task 2: Remove the existing Contact class from the DAL project
Open AdventureWorksEDM.Designer.cs or AdventureWorksEDM.Designer.vb, and then in the Entities
region, comment out all of the Contact partial class.

Task 3: Add the existing business class to the DAL project


1. Add the businessLogicCustomer.cs or businessLogicCustomer.vb file in the
E:\Labfiles\Lab07\CS\Ex2\Starter or E:\Labfiles\Lab07\VB\Ex2\Starter folder to the DAL project.
2. If you are using Visual Basic, rename the businessLogicCustomer class file and class to Contact.vb and
Contact.
3. If you are using Visual C#, rename the businessLogicCustomer class file and class to Contact.cs and
Contact.

Task 4: Modify the business class to operate as an entity class


1. In the Contact class, add using statements for the following namespaces:
System.Data
System.Data.Objects.DataClasses
System.Data.Metadata.Edm
2. In the Contact class, modify the class definition to inherit from EntityObject.
3. In the Contact class, use the EdmEntityType attribute to link the class to the Contact entity in the
AdventureWorksModel namespace.
4. Use the EdmScalarProperty attribute to configure the entity properties in the class, as the following
table shows.
8 Lab Instructions: Customizing Entities and Building Custom Entity Classes

Property name EntityKeyProperty IsNullable

ContactID true false

NameStyle false false

Title false true

FirstName false false

MiddleName false true

LastName false false

Suffix false true

EmailAddress false true

EmailPromotion false false

Phone false true

PasswordHash false false

PasswordSalt false false

AdditionalContactInfo false true

rowguid false false

ModifiedDate false false

CurrentPoints false false


5. Modify the Set statements for each property to notify the change tracker when a property change is
pending and then completed, and to use the SetValidValue method of the StructuralObject object
to change the property value.
6. Add navigation properties to link the Contact entity to the SalesOrderHeader, StoreContact, and
RewardsClaimed entities.
7. If you are using Visual C#, build the solution and correct any errors.

Task 5: Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class
(for Visual Basic only)
1. Open the AdventureWorksEDM.Designer.vb file
2. At the top of the file, if it is not already present, add a statement to bring the DAL namespace into
scope.
3. Update code that references AdventureWorksModel.Contact to reference DAL.Contact.
4. Build the solution and correct any errors.

Task 6: Modify the user interface to catch the validation exception


1. In CustomerWindow.xaml or CustomerAddWindow.xaml, add the ExceptionValidationRule rule to
the binding validation rules for the CurrentPoints text box to catch the validation exception.
2. In CustomerWindow.xaml or CustomerAddWindow.xaml, add the binding exception validation rule
for the EmailAddress text box to catch the validation exception and change the Style attribute of the
text box to display errors.
Lab Instructions: Customizing Entities and Building Custom Entity Classes 9

Task 7: Build and test the application


1. Build the solution and correct any errors.
2. Start the application without debugging.
3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that the application functions as expected.
4. Close the application.
5. Run all of the tests in the solution.
6. Verify that all of the tests succeed.
7. Save and close the solution, and then close Visual Studio.
Lab Instructions: Using POCO Classes with the Entity Framework 1

Module 8
Lab Instructions: Using POCO Classes with the Entity
Framework
Contents:
Exercise 1: Using POCO Classes 4
Exercise 2: Extending Your POCO Classes 7
2 Lab Instructions: Using POCO Classes with the Entity Framework

Lab: Using POCO Classes with the Entity Framework

Objectives
After completing this lab, you will be able to:
Create POCO entity classes that support lazy loading and automatic change tracking.
Create and use POCO entity classes that do not support lazy loading and automatic change tracking.

Introduction
In this lab, you will disable object layer generation in the EDM. You will then complete the
implementation of the custom POCO entity classes for Adventure Works. You will modify the data access
layer where necessary to work with the POCO entity classes.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-08 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Using POCO Classes with the Entity Framework 3

Lab Scenario

Adventure Works implements an EDM to support its customer reward program. You have been asked to
modify the data access layer to use custom POCO entity classes that implement some of the business
logic that Adventure Works requires.

You have also been asked to update the data access layer in two stages. In the first stage, you will replace
the generated entity classes with simple POCO entity classes that support lazy loading and automatic
change tracking. You will then enhance the POCO entity classes to include additional business logic and
adapt the data access layer to work with these enhanced entity classes.
4 Lab Instructions: Using POCO Classes with the Entity Framework

Exercise 1: Using POCO Classes


Scenario
You have been asked to replace the existing generated entity classes with custom POCO entity classes. At
this stage, the POCO classes will support lazy loading and automatic change tracking, but you must create
a new ObjectContext class to load the new custom entity classes. You must also ensure that change
tracking is enabled by instantiating entity objects by using the CreateObject method instead of the new
operator.

The main tasks for this exercise are as follows:


1. Prepare the AdventureWorks database for the lab.
2. Open the starter project for this exercise.
3. Disable object layer generation for your EDM.
4. Implement a custom ObjectContext class.
5. Complete the RewardsClaimed class in the AdventureWorks project.
6. Modify the data access layer to work with the new POCO classes.
7. Build and test the application.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-08 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat.

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex1\Starter or
E:\Labfiles\Lab08\VB\Ex1\Starter folder.

Task 3: Disable object layer generation for your EDM


1. Open the AdventureWorks EDM model in the ADO.NET Entity Data Model Designer (Entity Designer).
2. In the AdventureWorks EDM model, set the Code Generation Strategy property to None.
3. Save the AdventureWorks EDM model.

Task 4: Implement a custom ObjectContext class


1. Review the task list.
2. Open the AdventureWorksContext file by double-clicking the TODO: Ex1 - Add a constructor task
in the task list. This task is located in the AdventureWorksContext class.
3. Immediately after the comment, add a no-argument constructor that enables automatic lazy loading.
The constructor should invoke the base class constructor passing the strings
"name=AdventureWorksEntities" and "AdventureWorksEntities" as parameters.
4. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the Contacts entity set task in the task list. This task is located in the
AdventureWorksContext class.
5. Immediately after the comment, add a read-only property called Contacts based on the ObjectSet
generic type. Specify Contact as the type parameter for the ObjectSet type. Create the ObjectSet
object if it does not exist by calling the CreateObjectSet method in the base class, and then save the
ObjectSet object in a private field.
Lab Instructions: Using POCO Classes with the Entity Framework 5

6. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the RewardsClaimed entity set task in the task list. This task is located in the
AdventureWorksContext class.
7. Immediately after the comment, add a read-only property called RewardsClaimed based on the
ObjectSet generic type. Specify RewardsClaimed as the type parameter for the ObjectSet type.
Create the ObjectSet object if it does not exist by calling the CreateObjectSet method in the base
class, and then save the ObjectSet object in a private field.
8. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the Rewards entity set task in the task list. This task is located in the
AdventureWorksContext class.
9. Immediately after the comment, add a read-only property called Rewards based on the ObjectSet
generic type. Specify Rewards as the type parameter for the ObjectSet type. Create the ObjectSet
object if it does not exist by calling the CreateObjectSet method in the base class, and then save the
ObjectSet object in a private field.
10. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the AddToContacts method task in the task list. This task is located in the
AdventureWorksContext class.
11. Immediately after the comment, add a void method called AddToContacts that takes a contact
entity as a parameter. The method should call the AddObject method in the base class to add the
contact entity to the contacts entity set.
12. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the AddToRewards method task in the task list. This task is located in the
AdventureWorksContext class.
13. Immediately after the comment, add a void method called AddToRewards that takes a reward entity
as a parameter. The method should call the AddObject method in the base class to add the reward
entity to the rewards entity set.
14. Save the AdventureWorksContext file.

Task 5: Complete the RewardsClaimed class in the AdventureWorks project


1. Review the task list.
2. Open the RewardsClaimed file by double-clicking the TODO: Ex1 - Add virtual public accessors for
every RewardsClaimed entity property task in the task list. This task is located in the
RewardsClaimed class.
3. Immediately after the comment, add a virtual public property for every entity property of the
RewardsClaimed entity object in the EDM.
4. Locate the next comment in the RewardsClaimed file by double-clicking the TODO: Ex1 - Add
virtual public accessors for every RewardsClaimed navigation property task in the task list. This
task is located in the RewardsClaimed class.
5. Immediately after the comment, add a virtual public property for every navigation property of the
RewardsClaimed entity object in the EDM.
6. Save the RewardsClaimed file.

Task 6: Modify the data access layer to work with the new POCO classes
1. Review the task list.
6 Lab Instructions: Using POCO Classes with the Entity Framework

2. Open the DataAccessLayer file by double-clicking the TODO: Ex1 - Add a using clause for the
AdventureWorks namespace task in the task list. This task is located near the top of the
DataAccessLayer file.
3. Immediately after the comment, add a using statement for the AdventureWorks namespace.
4. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Use the
custom ObjectContext class task in the task list. This task is located in the SetContext method.
5. Immediately after the comment, modify the next line of code to use the AdventureWorksContext
class instead of the AdventureWorksEntities class.
6. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a
new contact by using the CreateObject method task in the task list. This task is located in the
AddContact method.
7. Immediately after the comment, add code that creates a new contact entity by calling the
CreateObject method. Then, use the Copy method of the contact object to copy the values from the
parameter passed to the AddContact method.
8. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a
new reward by using the CreateObject method task in the task list. This task is located in the
AddReward method.
9. Immediately after the comment, add code that creates a new reward entity by calling the
CreateObject method. Then, use the Copy method of the reward object to copy the values from the
parameter passed to the AddReward method. You must check the type of reward passed as a
parameter to the AddReward method (AdventureWorksReward, SupermarketReward, or
AirMilesReward), and then create the correct reward type.
10. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a
new claim by using the CreateObject method task in the task list. This task is located in the
CreateRewardsClaim method.
11. Immediately after the comment, add code that creates a new RewardsClaimed entity by calling the
CreateObject method. Then, use the Copy method of the RewardsClaimed object to copy the
values from the parameter passed to the CreateRewardsClaim method.
12. Save the DataAccessLayer file.

Task 7: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that you can add, search for, and delete customer data and that you can add and
delete claim data.
6. Close the application.
7. Close the solution.
8. Reset the AdventureWorks Database. In the E:\Labfiles folder, run AWReset.bat.
Lab Instructions: Using POCO Classes with the Entity Framework 7

Exercise 2: Extending Your POCO Classes


Scenario
You have been asked to enhance the custom POCO entity classes to include some custom business logic.
These changes mean that your custom entity classes no longer support lazy loading or automatic change
tracking, so you must update your data access layer.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Add business operations to the Contact class.
3. Add a business operation to the RewardsClaimed class.
4. Modify the data access layer to work with your new POCO entities.
5. Build and test the application.

Task 1: Open the starter project for this exercise


Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex2\Starter or
E:\Labfiles\Lab08\VB\Ex2\Starter folder.

Task 2: Add business operations to the Contact class


1. Review the task list.
2. Open the Contact file by double-clicking the TODO: Ex2 - Create the Salt and Hash task in the task
list. This task is located in the Password property.
3. Immediately after the comment, generate a value for the PasswordSalt property by calling the static
CreateSalt method of the Hashing class with a parameter value of 5. Then, generate a value for the
PasswordHash property by calling the CreatePasswordHash method of the Hashing class, passing
the password and PasswordSalt value as parameters.
4. Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the
AddRewardClaim method task in the task list. This task is located in the AddRewardClaim method.
5. Immediately after the comment, add code to decrement the CurrentPoints property by the value of
the PointsUsed property of the claim object, set the ModifiedDate property to the current date and
time, and then add the claim object to the _rewardsClaimed list.
6. Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the
RemoveRewardClaim method task in the task list. This task is located in the RemoveRewardClaim
method.
7. Immediately after the comment, add code to increment the CurrentPoints property by the value of
the PointsUsed property of the claim object, set the ModifiedDate property to the current date and
time, and then add the claim object to the _rewardsClaimed list.
8. Save the Contact file.

Task 3: Add a business operation to the RewardsClaimed class


1. Review the task list.
2. Open the RewardsClaimed file by double-clicking the TODO: Ex2 - Implement the ModifyClaim
method task in the task list. This task is located in the ModifyClaim method.
3. Immediately after the comment, add code to perform the following tasks:
a. Increment the CurrentPoints property of the Contact property by the value of the PointsUsed
property of the current claim object.
8 Lab Instructions: Using POCO Classes with the Entity Framework

b. Decrement the CurrentPoints property of the Contact property by the value of the pointsUsed
parameter.
c. Assign the rewardID parameter to the RewardID property.
d. Assign the pointsUsed parameter to the PointsUsed property.
4. Save the RewardsClaimed file.

Task 4: Modify the data access layer to work with your new POCO entities
1. Review the task list.
2. Open the DataAccessLayer file by double-clicking the TODO: Ex2 - Delete the call to the
EncryptPassword method task in the task list. This task is located in the AddContact method.
3. The Contact class now handles password encryption. Delete the line of code after the comment that
calls the EncryptPassword method.
4. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Remove
the EncryptPassword method task in the task list. This task is located in the DataAccessLayer class.
5. The password encryption functionality is now in the AdventureWorks project. Delete the whole of the
EncryptPassword method from the DataAccessLayer class.
6. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Make sure
that all the claims are loaded task in the task list. This task is located in the DeleteContact method.
7. The new POCO classes do not support automatic lazy loading. Immediately after the comment, add
code to load all of the claims that are related to the contact by using the LoadProperty method.
8. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Load the
contact and then call the AddRewardClaim method task in the task list. This task is located in the
CreateRewardsClaim method.
9. Immediately after the comment, add code to perform the following tasks:
a. Create an EntityKey object for the contact associated with the claim.
b. Use the TryGetObjectByKey method to load the contact entity.
c. Use the AddRewardClaim method to add the claim to the contact.
10. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Remove
the claim before you refresh the contact task in the task list. This task is located in the
CreateRewardsClaim method.
11. Immediately after the comment, add code to remove the claim from the contact by calling the
RemoveRewardClaim method.
12. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
AddRewardClaim method task in the task list. This task is located in the CreateRewardsClaim
method.
13. Immediately after the comment, add code to add the claim to the contact by calling the
AddRewardClaim method on the Contact property of the claim variable.
14. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
ModifyClaim business method task in the task list. This task is located in the UpdateRewardsClaim
method.
15. Immediately after the comment, add code to call the ModifyClaim method.
Lab Instructions: Using POCO Classes with the Entity Framework 9

16. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Give the
original points back to the Contact task in the task list. This task is located in the
UpdateRewardsClaim method.
17. Immediately after the comment, add code to call the ModifyClaim method, passing the
originalPoints variable as the second parameter.
18. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
ModifyClaim method to give the points to the contact task in the task list. This task is located in
the UpdateRewardsClaim method.
19. Immediately after the comment, add code to call the ModifyClaim method, passing the RewardID
property of the rewardClaim object as the first parameter and the PointsUsed property of the
rewardClaim object as the second parameter.
20. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
RemoveRewardClaim method task in the task list. This task is located in the DeleteRewardsClaim
method.
21. Immediately after the comment, add code to call the RemoveRewardClaim method of the
relatedContact object, passing the rewardClaimToDelete object as a parameter.
22. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Add the
claim back while you refresh the contact task in the task list. This task is located in the
DeleteRewardsClaim method.
23. Immediately after the comment, add code to call the AddRewardClaim method of the
relatedContact object, passing the rewardClaimToDelete object as a parameter.
24. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
RemoveRewardClaim method again task in the task list. This task is located in the
DeleteRewardsClaim method.
25. Immediately after the comment, add code to call the RemoveRewardClaim method of the
relatedContact object, passing the rewardClaimToDelete object as a parameter.
26. Save the DataAccessLayer file.

Task 5: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add,
modify, and delete Adventure Works reward data.
6. Close the application.
7. Close the solution, and then close Visual Studio.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 1

Module 9
Lab Instructions: Building an N-Tier Solution by Using the
Entity Framework
Contents:
Exercise 1: Creating the Contacts and Orders Data Access Tier 4
Exercise 2: Protecting Data Access Operations 15
2 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

Lab: Building an N-Tier Solution by Using the Entity


Framework

Objectives
After completing this lab, you will be able to:
Create a data access layer for an n-tier application.
Protect the data access tier.

Introduction
In this lab, you will develop a data access tier to fetch and manage contact and order data. You will
configure the data access tier to defend against common attacks and then implement authentication and
authorization to ensure that clients can only access the data they are allowed to use.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-09 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 3

Lab Scenario

You have been asked to modify an existing client/server Orders application to use a separate data access
tier. As a proof of concept, you have been asked to build a data access tier that client applications can use
to query data. In this architecture, you will need to redesign the data access layer to transport objects
between the data access tier and the business tier. You will also ensure that the data access tier is
protected against common threats, and that it restricts each group of employees so that they can only
access data appropriate to their role.
4 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

Exercise 1: Creating the Contacts and Orders Data Access Tier


Scenario
In this exercise, you will create the orders data access tier holding the Contact, Order, and OrderDetail
entity objects. The data access tier will be implemented by using a WCF service that will expose business
methods that return data as self-tracking entities.

Each operation will include logic to prevent a request from returning too much data that could potentially
swamp the network and hog resources on the server.

When a client application requests the details of an order, the corresponding entity objects are created,
populated, and transported to the business layer in the client application.

The main tasks for this exercise are as follows:

1. Prepare the environment for the lab.


2. Prepare the AdventureWorks database for the lab.
3. Open the starter project.
4. Create the OrdersDAL class library.
5. Create the OrdersClientLibrary class library.
6. Create the IOrdersService interface.
7. Implement exception handling and logging.
8. Implement the IOrdersService interface.
9. Create the OrdersWebService Web service.
10. Configure the OrderManagement application.
11. Test the OrderManagement application.

Task 1: Prepare the environment for the lab


1. Log on to the 10265A-GEN-DEV-09 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures IIS and creates the
required users and groups.

Task 2: Prepare the AdventureWorks database for the lab


In the E:\Labfiles folder, run AWReset.bat.

Task 3: Open the starter project


1. In the E:\Labfiles\Lab09\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic), or
E:\Labfiles\Lab09\CS\Ex1\Starter folder (if you are using Microsoft Visual C#), run ExSetup.bat as an
administrator. This script adds the required virtual directories to IIS.
2. Open Visual Studio 2010 as an administrator.
3. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex1\Starter\OrdersDAL or
E:\Labfiles\Lab09\CS\Ex1\Starter\OrdersDAL folder.

Task 4: Create the OrdersDAL class library


1. Add a new Class Library project named OrdersDAL to the solution.
2. Delete the Class1 file.
3. Add a new ADO.NET Entity Data Model (EDM) named AdventureWorksModel to the OrdersDAL
project. Generate the EDM from the AdventureWorks Microsoft SQL Server database and create
entities for the Contact, SalesOrderHeader, and SalesOrderDetail tables.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 5

4. Modify the EDM to generate self-tracking entities by adding the ADO.NET Self-Tracking Entity
Generator code generation item to the EDM. Name the code generation item
AdventureWorksModel.tt. Allow Visual Studio to overwrite the existing AdventureWorks.Context.tt
file when prompted.
5. Build the OrdersDAL project and correct any errors.

Important: Only build the OrdersDAL project. The OrderManagement project will not build successfully
because it is not yet complete.

Task 5: Create the OrdersClientLibrary class library


1. Add a new Class Library project named OrdersClientLibrary to the solution.
2. Delete the Class1 file.
3. Add a reference to the System.Runtime.Serialization assembly.
4. If you are using Visual Basic, change the Root namespace property of the OrdersClientLibrary project
to OrdersDAL.
5. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.
6. Add a reference to the OrdersClientLibrary assembly to the OrdersDAL project.
7. Add a class named AdditionalMethods to the OrdersClientLibrary project.
8. In the AdditionalMethods class, perform the following tasks:
a. Remove the AdditionalMethods class declaration.
b. If you are using Visual C#, change the namespace to OrdersDAL.
c. Create a public partial class named SalesOrderHeader.
d. Create a public partial class named SalesOrderDetail.
9. In the partial SalesOrderHeader class, add code to overwrite the ToString method by returning a
string that contains the SalesOrderID, ContactID, AccountNumber, OrderDate,
PurchaseOrderNumber, and TotalDue properties from the current object.
10. In the partial SalesOrderDetail class, add code to overwrite the ToString method by returning the
ProductID, OrderQty, UnitPrice, UnitPriceDiscount, and LineTotal properties of the current object.
11. Build the OrdersClientLibrary project and correct any errors.

Important: Only build the OrdersClientLibrary project. The OrderManagement project will not build
successfully because it is not yet complete.

Task 6: Create the IOrdersService interface


1. Add a new Class Library project named OrdersService to the solution.
2. Delete the Class1 file.
3. Create an interface named IOrdersService in the OrdersService project.
4. Add a reference to the OrdersClientLibrary assembly.
5. Add a reference to the OrdersDAL assembly.
6. Add a reference to the System.ServiceModel assembly.
6 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

7. Add a reference to the System.Runtime.Serialization assembly.


8. Add a reference to the System.Data.Entity assembly.
9. In the IOrdersService code file, add code to bring the System.Runtime.Serialization,
System.ServiceModel, and OrdersDAL namespaces into scope.
10. In the IOrdersService file, before the interface declaration, write code to perform the following tasks:
a. Add a new class called ServiceFault. Prefix the class with the DataContract attribute.
b. Add a public string field to the ServiceFault class called ExceptionType. Prefix the field with the
DataMember attribute.
c. Add a public string field to the ServiceFault class called ExceptionMessage. Prefix the field with
the DataMember attribute.
11. In the IOrdersService file, perform the following tasks:
a. Make the IOrdersService interface public (if it is not already public).
b. Specify that the IOrdersService interface defines a service contract called OrdersWebService. Use
http://microsoft.com as the Web service namespace.
12. In the IOrdersService interface, perform the following tasks:
a. Define a method called GetContactDetails that returns an object of type Contact and accepts
an integer named contactID as a parameter.
b. Specify that the GetContactDetails method is Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetContactDetails method returns a message of type ServiceFault if an
exception occurs.
13. In the IOrdersService interface, perform the following tasks:
a. Define a method called GetAllContactsInRange that returns a generic IEnumerable object of
with a type parameter of Contact, and accepts an integer named lowerBound and an integer
named upperBound as parameters.
b. Specify that the GetAllContactsInRange method is Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetAllContactsInRange method returns a message of type ServiceFault if an
exception occurs.
14. In the IOrdersService interface, perform the following tasks:
a. Define a method called GetOrderDetails that returns an object of type SalesOrderHeader and
accepts an integer named orderID as a parameter.
b. Specify that the GetOrderDetails method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetOrderDetails method returns a message of type ServiceFault if an exception
occurs.
15. In the IOrdersService interface, perform the following tasks:
a. Define a method called GetOrdersForContact that returns a generic IEnumerable object of with
a type parameter of SalesOrderHeader and accepts an integer named contactID as a
parameter.
b. Specify that the GetOrdersForContact method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetOrdersForContact method returns a message of type ServiceFault if an
exception occurs.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 7

16. In the IOrdersService interface, perform the following tasks:


a. Define a method called GetOrdersForProduct that returns a generic IEnumerable object of
with a type parameter of SalesOrderHeader and accepts an integer named productID as a
parameter.
b. Specify that the GetOrdersForProduct method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetOrdersForProduct method returns a message of type ServiceFault if an
exception occurs.
17. In the IOrdersService interface, perform the following tasks:
a. Define a method called GetAllOrdersInRange that returns a generic IEnumerable object with a
type parameter of SalesOrderHeader and accepts an integer named lowerBound and an
integer named upperBound as parameters.
b. Specify that the GetAllOrdersInRange method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetAllOrdersInRange method returns a message of type ServiceFault if an
exception occurs.
18. Build the project and correct any errors.

Important: Only build the OrdersService project. The OrderManagement project will not build
successfully because it is not yet complete.

Task 7: Implement exception handling and logging


1. Create a class named OrdersServiceImpl in the OrdersService project.
2. In the OrdersServiceImpl code file, write code to bring the following namespaces into scope:
System.Text

Note: If you are using Visual C#, the System.Text namespace is already in scope and there is no need to
add it again.

System.ServiceModel
System.Diagnostics
System.Threading
OrdersDAL
System.Security.Permissions
3. In the OrdersServiceImpl code file, perform the following tasks:
a. Prefix the OrdersWebService class with the ServiceBehavior attribute, specify the name of the
service as OrdersWebService, and set the namespace to http://microsoft.com.
b. Set the InstanceContextMode property of the ServiceBehavior attribute to PerCall.
c. Set the ConcurrencyMode property of the ServiceBehavior attribute to Multiple.
d. Declare the OrdersServiceImpl class as a public class (if it is not already public) that implements
the IOrdersService interface.
4. In the OrdersServiceImpl class, write code to perform the following tasks:
8 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

a. Declare a constant integer named maxContactsCount and assign it a value of 500.


b. Declare a constant integer named maxOrdersCount and assign it a value of 400.
c. Declare a constant string named eventSource and assign it the value "Orders Service".
d. Declare a constant string named eventLog and assign it the value "Application".
5. In the OrdersServiceImpl class, write code to perform the following tasks:
a. Create a new private method named logException that accepts an Exception object named ex
and a string object named eventName as parameters. This method should not return a value.
b. In the logException method, verify that the event source identified by the eventSource constant
exists by calling the SourceExists method of the EventLog class in the System.Diagnostics
namespace. If the event source does not exist, create it by calling the CreateEventSource
method of the EventLog class, specifying the eventSource and eventLog constants as
parameters.
c. Declare a string named eventMessage and assign it a value by combining the eventName
variable, the Message property of the ex variable, and the value of the
Thread.CurrentPrincipal.Identity.Name property.
d. Call the WriteEntry method of the EventLog class, passing the eventSource and eventMessage
variables as parameters and specifying Error as the EventLogEntryType parameter.
6. In the OrdersServiceImpl class, create a new private method named handleException that accepts
the following parameters:
An Exception object named ex.
A string parameter named operationName.
An optional integer parameter named operationData with a default value of 0.
7. In the handleException method, write code to perform the following tasks:
a. Create a new StringBuilder object named eventMessageBuilder.
b. Append the message "Failure in {0}" to the eventMessageBuilder object, and specify the value
of the operationName parameter as the {0} placeholder.
c. If the value of the operationData variable is not equal to 0, append the value of the
operationData parameter to the eventMessageBuilder object.
d. Call the logException method, passing the ex object and the string value of the
eventMessageBuilder object as parameters.
8. In the handleException method, add code to perform the following tasks:
a. If the ex object is of type ApplicationException, create a new ServiceFault object named sf. Set
the ExceptionType property to the type of the ex object, and set the ExceptionMessage
property by using Message property of the ex object.
b. Throw a new FaultException<ServiceFault> exception, specifying the sf object and the string
value of the eventMessageBuilder object as parameters.
c. If the ex object is some other type of exception, create a new ServiceFault object named sf, and
set the ExceptionType property to the type of the ex object and the value of the
ExceptionMessage property to the string "Exception occurred fetching data".
d. Throw a new FaultException<ServiceFault> exception. Pass the value of the sf object and the
message "Failure in {0}" where the {0} placeholder is the value of the operationName variable as
parameters to the constructor.
9. Save the OrdersServiceImpl file.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 9

Task 8: Implement the IOrdersService interface


1. In the OrdersServiceImpl class, generate stub methods for each of the items in the IOrdersService
interface.
2. Locate the GetContactDetails method. This method takes an integer value as a parameter and
returns a Contact object.
3. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
4. In the body of the method, add code to perform the following tasks:
a. Create a Contact object named contact and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query named matchingContacts that retrieves all of the Contact entities by using
the AdventureWorksEntities object, with a ContactID property that is equal to the value of the
contactID variable.
d. If there is at least one Contact object in the matchingContacts collection, return it; otherwise,
return a null (Nothing in Visual Basic) value. If there is more than one matching contact, return
the first one found.

Note: The ContactID should be unique, so there should only be at most one matching contact. However,
it is good practice to write defensive code just in case a database administrator amends the structure of
the Contact table in the database and creates a different key column.

e. Handle any exceptions by calling the handleException method; pass the Exception object, the
method name, and the contactID variable as parameters before returning a null (Nothing in
Visual Basic) value.
5. Locate the GetAllContactsInRange method. This method takes two integer values, lowerBound and
upperBound, as parameters and returns an IEnumerable list of Contact objects.
6. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
7. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named contacts by using the Contact type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the Contact entities where the ContactID property is
between the values of the lowerBound and upperBound variables. The result of this query should
be assigned to the contacts object.
d. If the number of objects in the contacts collection is greater than or equal to the value of the
maxContactsCount constant, throw a new ApplicationException exception with the message
"Too many contacts".
e. Return the contacts collection as a generic List object. If you are using Visual C#, specify the
Contact type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object and
the method name as parameters before returning null (Nothing in Visual Basic).
8. Locate the GetOrderDetails method. This method takes an integer value as a parameter and returns
a SalesOrderHeader object.
10 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

9. If you are using Visual C#, delete the default method that throws a NotImplementedException
exception.
10. In the body of the method, add code to perform the following tasks:
a. Create a SalesOrderHeader object named order and assign it the value null (Nothing in Visual
Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query named matchingOrders that retrieves all of the SalesOrderHeader entities
and the related SalesOrderDetail entities where the SalesOrderID property matches the value in
the orderID variable.
d. If the number of objects in the matchingOrders collection is greater than zero, return the first
SalesOrderHeader object in the collection; otherwise, return null (Nothing in Visual Basic).
e. Handle any Exception exceptions by calling the handleException method; pass the exception
object, the method name, and the orderID variable as parameters before returning null (Nothing
in Visual Basic).
11. Locate the GetOrdersForContact method. This method takes an integer value as a parameter and
returns an IEnumerable list of SalesOrderHeader objects.
12. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
13. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related
SalesOrderDetail entities where the ContactID property matches the value in the contactID
variable. Assign the result of this query to the orders object.
d. If the number of objects in the orders collection is greater than or equal to the value of the
maxOrdersCount constant, throw a new ApplicationException exception with the message "Too
many orders".
e. Return the orders collection as a generic List object. If you are using Visual C#, specify the
SalesOrderHeader type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object, the
method name, and the contactID variable as parameters before returning null (Nothing in Visual
Basic).
14. Locate the GetOrdersForProduct method. This method takes an integer value as a parameter and
returns an IEnumerable list of SalesOrderHeader objects.
15. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
16. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related
SalesOrderDetail entities where the ProductID property of at least one of the SalesOrderDetail
entities for the SalesOrderHeader entity matches the productID variable. Assign the result of this
query to the orders object.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 11

Note: The SalesOrderDetail records for an order specify the products being ordered. An order can have
one or more SalesOrderDetail records. To find all orders for a specific product, you must find all
SalesOrderDetail records that match the product and return the SalesOrderHeader objects that
reference these SalesOrderDetail records.

d. If the number of objects in the orders collection is greater than or equal to the value of the
maxOrdersCount constant, throw a new ApplicationException exception with the message "Too
many orders".
e. Return the orders collection as a generic List object. If you are using Visual C#, specify the
SalesOrderHeader type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object, and
the method name as parameters before returning null (Nothing in Visual Basic).
17. Locate the GetAllOrdersInRange method. This method takes two integer values, lowerBound and
upperBound, as parameters and returns an IEnumerable list of Contact objects.
18. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
19. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related
SalesOrderDetails entities where the value in the SalesOrderID property is between the
lowerBound and upperBound variables. The result of this query should be assigned to the orders
object.
d. If the number of objects in the orders collection is greater than or equal to the value of the
maxOrdersCount constant, throw a new ApplicationException exception with the message "Too
many orders".
e. Return the orders collection as a generic List object. If you are using Visual C#, specify the
SalesOrderHeader type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object, and
the method name as parameters before returning null (Nothing in Visual Basic).
20. Build the OrdersService project and correct any errors.

Important: Only build the OrdersService project. The OrderManagement project will not build
successfully because it is not yet complete.

Task 9: Create the OrdersWebService Web service


1. Add a new empty ASP.NET Web Application project named OrdersWebService to the solution.
2. Configure the project to use the local IIS Web server with the URL
http://localhost/OrdersWebService.
3. Add a new WCF service called OrdersWebService.svc to the OrdersWebService project.
4. Delete the IOrdersWebService code file from the OrdersWebService project.
5. Add a reference to the OrdersClientLibrary assembly.
6. Add a reference to the OrdersDAL assembly.
12 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

7. Add a reference to the OrdersService assembly.


8. Delete the OrdersWebService code-behind file.
9. In the OrdersWebService.svc file, delete the existing markup code.
10. In the OrdersWebService.svc file, add markup statements that perform the following tasks:
a. Create a ServiceHost instance and set the Service property to point to the OrdersServiceImpl
service that is defined in the OrdersService project.
b. Specify that this service is located in the OrdersService assembly.

Note: Visual Studio reports that it cannot find the OrdersService assembly. This warning will disappear
when you build the project and you can safely ignore it.

11. Delete the Web.config file from the OrdersWebService project.


12. Add the existing Web.config file from the E:\Labfiles\Lab09\CS\Ex1\Starter or
E:\Labfiles\Lab09\VB\Ex1\Starter folder to the OrdersWebService project.
13. Build the OrdersWebService project and correct any errors.

Important: Only build the OrdersWebService project. The OrderManagement project will not build
successfully because it is not yet complete.

Task 10: Configure the OrderManagement application


1. In the OrderManagement project, add a reference to the OrdersClientLibrary assembly.
2. Add a service reference to the OrdersWebService service to the OrderManagement application.
Generate the service reference in the OWService namespace. The URL of the OrdersWebService is
http://localhost/OrdersWebService/OrdersWebService.svc. Use System.Collections.Generic.List as
the collection type and ensure that you reuse types in referenced assemblies.
3. In the app.config file, modify the definition of the wsHttpBinding binding by increasing the
maxBufferPoolSize property to 5242880 and the maxReceivedMessageSize property to 5242880.
4. Review the task list.
5. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Lab 9, Ex1 - Fetch a single contact task in the task list. This task is located in the
RetrieveContacts method.
6. Immediately after the TODO: Lab9, Ex1 - Fetch a single contact comment, write code to perform
the following tasks:
a. Create a new Contact object named contact by calling the GetContactDetails method of the
service object. Pass the rangeFrom variable as a parameter.

Note: The service variable is an OrdersWebServiceClient object. The OdersWebServiceClient type was
generated when you added the service reference to the OrdersWebService service. This type provides the
Web service proxy for connecting to the OrdersWebService service, and it exposes methods that you can
call to invoke the operations in the OrdersWebService service.

b. If the contact object is not null (Nothing in Visual Basic), instantiate the contacts generic List
collection and specify Contact as the type parameter for this list.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 13

c. Add the contact object to the contacts list.


7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch all contacts in range task in the task list.
8. Immediately after the TODO: Lab 9, Ex1 - Fetch all contacts in range comment, write code to call
the GetAllContactsInRange method of the service object. Specify the rangeFrom and rangeTo
variables as parameters. Assign the returned value to the contacts object.
9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact task in the task
list. This task is located in the getOrdersForContact_Click method.
10. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact comment,
write code to call the GetOrdersForContact method of the service object. Specify the contactID
variable as the parameter. Assign the returned value to the orders object.
11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified product task in the task
list. This task is located in the getOrdersForProduct_Click method.
12. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified product comment,
write code to call the GetOrdersForProduct method of the service object. Specify the productID
variable as the parameter. Assign the returned value to the orders object.
13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch a single order task in the task list. This task is located
in the RetrieveOrders method.
14. Immediately after the comment, write code to create a new SalesOrderHeader object named order.
Assign this the value that is returned by calling the GetOrderDetails method of the service object.
Specify the rangeFrom variable as a parameter. If an order is returned by the GetOrderDetails
method, instantiate the orders generic List object and add the order to this list. Specify
SalesOrderHeader as the type parameter for the orders list.
15. Locate the next comment in the code behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch all orders in range task in the task list.
16. Immediately after the TODO: Lab 9, Ex1 - Fetch all orders in range comment, write code to call the
GetAllOrdersInRange method of the service object. Specify the rangeFrom and rangeTo variables as
parameters. Assign the returned value to the orders object.
17. Build the solution and correct any errors.

Task 11: Test the OrderManagement application


1. Check that the OrderManagement application is set as the startup project.
2. Start the application without debugging.
3. In the Order Management window, on the Contacts tab, click Get. One contact is displayed in the
contacts grid.
4. Adjust the To slider to a value that is less than 500, and then click Get. Check that the correct number
of rows is returned.
5. Adjust the To slider to a value that is greater than 500, and then click Get. An exception will be
thrown containing the message "Too many contacts".
6. In the Service Fault occurred dialog box, click OK.
7. In the Order Management window, click the General Orders tab.
14 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

8. On the General Orders tab, adjust the From slider to a value that is greater than 50000, and then
click Get. A single order is displayed in the orders grid. Expand the order to view the order details.
9. Adjust the To slider to retrieve a range of between 20 and 50 orders, and then click Get. Verify that
the correct number of orders is displayed.

You can use the arrow keys to adjust the slider in small increments.

10. Adjust the To slider to retrieve a range of over 500 orders. Verify that a message box appears with the
message Too many orders.
11. In the Service Fault occurred dialog box, click OK.
12. On the Orders By Contact tab, in the Contact ID box, type 10 and then click Get. The four orders
placed by customer 10 should appear. Expand each order to view the details.
13. On the Orders By Product tab, in the Product ID box, type 710 and then click Get. The 44 orders for
this product should appear. Expand each order to view the details.
14. Close the Order Management window and return to Visual Studio.
15. Open the Orders Service event log to view the details of exceptions generated by the Web service.
16. Close the solution.
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 15

Exercise 2: Protecting Data Access Operations


Scenario
In this exercise, you will configure the data access tier to defend against common attacks, and implement
authentication and authorization to ensure that client applications can only access the data that they are
allowed to use.

You will define two rolesOrderAdmin and ContactAdminand then configure the DAL so that only
users in the ContactAdmin role can use the GetContactDetails and GetAllContactsInRange operations, and
only users in the OrderAdmin role can invoke the GetOrderDetails, GetOrdersForContact,
GetOrdersForProduct, and GetAllOrdersInRange operations.
You will then configure the service to encrypt data as it traverses the network, and will protect against
replay and DoS attacks.

The main tasks for this exercise are as follows:

1. Open the starter project.


2. Host the OrdersWebService Web service by using SSL.
3. Apply security demands.
4. Modify the OrdersWebService Web service to use transport security.
5. Modify the OrderManagement application.
6. Create unit tests.
7. Build and test the application.

Task 1: Open the starter project


1. In the E:\Labfiles\Lab09\VB\Ex2\Starter folder (if you are using Visual Basic), or
E:\Labfiles\Lab09\CS\Ex2\Starter folder (if you are using Visual C#), run ExSetup.bat as an
administrator.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex2\Starter\OrdersDAL or
E:\Labfiles\Lab09\CS\Ex2\Starter\OrdersDAL folder.

Task 2: Host the OrdersWebService Web service by using SSL


1. Open IIS Manager as an administrator.
2. Add a new self-signed certificate named OrdersWebService.
3. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.
4. Set the OrdersWebService Web service to require SSL security.
5. Close IIS Manager.
6. Set the Project Url property for the OrdersWebService project to use HTTPS.

Task 3: Apply security demands


1. Review the task list.
2. Open the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2 - Allow
ContactAdmins to call GetAllContactsInRange task in the task list.
3. Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetAllContactsInRange
comment, add the PrincipalPermission attribute to specify that users must be members of the
ContactAdmin role.
4. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow ContactAdmins to call GetContactDetails task in the task list.
16 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

5. Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetContactDetails
comment, add the PrincipalPermission attribute to specify that users must be members of the
ContactAdmin role.
6. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetAllOrdersInRange task in the task list.
7. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetAllOrdersInRange
comment, add the PrincipalPermission attribute to specify that users must be members of the
OrderAdmin role.
8. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetOrderDetails task in the task list.
9. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrderDetails comment,
add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin
role.
10. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetOrdersForContact task in the task list.
11. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForContact
comment, add the PrincipalPermission attribute to specify that users must be members of the
OrderAdmin role.
12. Locate the next comment in the OrderServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetOrdersForProduct task in the task list.
13. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForProduct
comment, add the PrincipalPermission attribute to specify that users must be members of the
OrderAdmin role.
14. Build the solution and correct any errors.

Task 4: Modify the OrdersWebService Web service to use transport security with
message-level credentials
1. In the OrdersWebService project, open the web.config file.
2. In the web.config file, locate the TODO: Lab 9, Ex2 - Use TransportWithMessageCredential
security mode comment. This comment is located in the bindings section.
3. Change the security mode from None to TransportWithMessageCredential.
4. In the web.config file, locate the TODO: Lab 9, Ex2 - Enable https, disable http comment. This
comment is located in the serviceBehaviors section.
5. Change the serviceMetadata property to enable HTTPS and disable HTTP.
6. Build the solution and correct any errors.

Task 5: Modify the OrderManagement application


1. Update the OrdersWebService service reference to use the new binding and security.
2. Review the task list.
3. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Lab9, Ex2 - Add credentials in getContacts_Click task in the task list.
4. Immediately after the TODO: Lab9, Ex2 - Add credentials in getContacts_Click comment, add
code to perform the following tasks:
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 17

a. Assign the value of the userName.Text property to the


service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrders_Click task in the task list.
6. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrders_Click comment, add code
to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click task in the
task list.
8. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click comment,
add code to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click task in the
task list.
10. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click
comment, add code to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
11. Build the solution and correct any errors.

Task 6: Create unit tests


1. Review the task list.
2. Open the OrderServiceImplTest code file by double-clicking the TODO: Lab9, Ex2 - Create a unit
test for the GetAllContactsInRange method task in the task list.
3. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllContactsInRange
method comment, add the following unit test code. This code calls the GetAllContactsInRange
method to fetch all contacts in the range 100 to 500, and verifies that the method returns the correct
number of rows. This method specifies the user name Fred and the password Pa$$w0rd. This user is
a member of the ContactAdmin role.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()>
Public Sub GetAllContactsInRangeTest()
18 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim lowerBound As Integer = 100


Dim upperBound As Integer = 500
Dim expectedCount As Integer = 401
Dim expectedFirstName As String = "Jackie"
Dim actual As IEnumerable(Of Contact) =
service.GetAllContactsInRange(lowerBound, upperBound)
Assert.AreEqual(expectedCount, actual.Count())
Assert.AreEqual(expectedFirstName, actual.First().FirstName)

End Sub

[Visual C#]

[TestMethod()]

public void GetAllContactsInRangeTest()


{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
int lowerBound = 100;
int upperBound = 500;
int expectedCount = 401;
string expectedFirstName = "Jackie";
IEnumerable<Contact> actual =
service.GetAllContactsInRange(lowerBound, upperBound);
Assert.AreEqual(expectedCount, actual.Count());
Assert.AreEqual(expectedFirstName, actual.First().FirstName);
}

4. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetAllOrdersInRange method task in the task list.
5. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllOrdersInRange method
comment, add the following unit test code. This code calls the GetAllOrdersInRange method to
retrieve all orders in the range 43650 to 43700, and verifies that the method returns the correct
number of rows. This method specifies the user name Fred and the password Pa$$w0rd.

[Visual Basic]

<TestMethod()>
Public Sub GetAllOrdersInRangeTest()
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim lowerBound As Integer = 43650


Dim upperBound As Integer = 43700
Dim expectedCount As Integer = 42
Dim expectedSalesOrderDetailCount As Integer = 12
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 19

Dim actual As IEnumerable(Of SalesOrderHeader) =


service.GetAllOrdersInRange(lowerBound, upperBound)
Assert.AreEqual(expectedCount, actual.Count())
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count())
End Sub

[Visual C#]
[TestMethod()]
public void GetAllOrdersInRangeTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int lowerBound = 43650;


int upperBound = 43700;
int expectedCount = 42;
int expectedSalesOrderDetailCount = 12;
IEnumerable<SalesOrderHeader> actual =
service.GetAllOrdersInRange(lowerBound, upperBound);
Assert.AreEqual(expectedCount, actual.Count());
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count());
}

6. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetContactDetails method task in the task list.
7. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetContactDetails method
comment, add the following unit test code. This code calls the GetContactDetails method to retrieve
the details of contact 13, and verifies that the method returns the correct data. This method specifies
the user name Fred and the password Pa$$w0rd.

[Visual Basic]
<TestMethod()>
Public Sub GetContactDetailsTest()
service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim contactID As Integer = 13


Dim expectedFirstName As String = "Robert"
Dim expectedLastName As String = "Ahlering"
Dim actual As Contact = service.GetContactDetails(contactID)
Assert.AreEqual(expectedFirstName, actual.FirstName)
Assert.AreEqual(expectedLastName, actual.LastName)
End Sub

[Visual C#]
[TestMethod()]
public void GetContactDetailsTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred";
service.ClientCredentials.Windows.ClientCredential.Password =
20 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

"Pa$$w0rd";
int contactID = 13;
string expectedFirstName = "Robert";
string expectedLastName = "Ahlering";
Contact actual = service.GetContactDetails(contactID);
Assert.AreEqual(expectedFirstName, actual.FirstName);
Assert.AreEqual(expectedLastName, actual.LastName);
}

8. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetOrderDetails method task in the task list.
9. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrderDetails method
comment, add the following unit test code. This code calls the GetOrderDetails method to retrieve
the details of order 71780, and verifies that the method returns the correct data. This method
specifies the user name Bert and the password Pa$$w0rd. This user is a member of the OrderAdmin
role.

[Visual Basic]
<TestMethod()>
Public Sub GetOrderDetailsTest()
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"
Dim orderID As Integer = 71780
Dim expectedSalesOrderID As Integer = 71780
Dim expectedAccountNumber As String = "10-4020-000340"
Dim expectedSalesOrderDetailsCount As Integer = 29
Dim actual As SalesOrderHeader = service.GetOrderDetails(orderID)
Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID)
Assert.AreEqual(expectedAccountNumber, actual.AccountNumber)
Assert.AreEqual(expectedSalesOrderDetailsCount,
actual.SalesOrderDetails.Count)
End Sub

[Visual C#]
[TestMethod()]
public void GetOrderDetailsTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int orderID = 71780;


int expectedSalesOrderID = 71780;
string expectedAccountNumber = "10-4020-000340";
int expectedSalesOrderDetailsCount = 29;
SalesOrderHeader actual = service.GetOrderDetails(orderID);
Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID);
Assert.AreEqual(expectedAccountNumber, actual.AccountNumber);
Assert.AreEqual(expectedSalesOrderDetailsCount,
actual.SalesOrderDetails.Count);

}
Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 21

10. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetOrdersForContact method task in the task list.
11. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForContact method
comment, add the following unit test code. This code calls the GetOrdersForContact method to
retrieve the orders for contact 100, and verifies that the method returns the correct data. This method
specifies the user name Bert and the password Pa$$w0rd.

[Visual Basic]

<TestMethod()>

Public Sub GetOrdersForContactTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim contactID As Integer = 100


Dim expectedOrdersCount As Integer = 4
Dim expectedSalesOrderDetailCount As Integer = 3
Dim actual As IEnumerable(Of SalesOrderHeader) =
service.GetOrdersForContact(contactID)
Assert.AreEqual(expectedOrdersCount, actual.Count())
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count())

End Sub

[Visual C#]

[TestMethod()]
public void GetOrdersForContactTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int contactID = 100;


int expectedOrdersCount = 4;
int expectedSalesOrderDetailCount = 3;
IEnumerable<SalesOrderHeader> actual =
service.GetOrdersForContact(contactID);
Assert.AreEqual(expectedOrdersCount, actual.Count());
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count());
}

12. Locate the final comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetOrdersForProduct method task in the task list.
13. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForProduct method
comment, add the following unit test code. This code calls the GetOrdersForProduct method to
retrieve the orders that contain product 709, and verifies that the method returns the correct data.
This method specifies the user name Bert and the password Pa$$w0rd.
22 Lab Instructions: Building an N-Tier Solution by Using the Entity Framework

[Visual Basic]

<TestMethod()>
Public Sub GetOrdersForProductTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim productID As Integer = 709


Dim expectedOrderCount As Integer = 188
Dim expectedOrderDetailsCount As Integer = 12
Dim actual As IEnumerable(Of SalesOrderHeader) =
service.GetOrdersForProduct(productID)
Assert.AreEqual(expectedOrderCount, actual.Count())
Assert.AreEqual(expectedOrderDetailsCount,
actual.First().SalesOrderDetails.Count)

End Sub

[Visual C#]

[TestMethod()]
public void GetOrdersForProductTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int productID = 709;


int expectedOrderCount = 188;
int expectedOrderDetailsCount = 12;
IEnumerable<SalesOrderHeader> actual =
service.GetOrdersForProduct(productID);
Assert.AreEqual(expectedOrderCount, actual.Count());
Assert.AreEqual(expectedOrderDetailsCount,
actual.First().SalesOrderDetails.Count);
}

14. Build the solution and correct any errors.

Task 7: Build and test the application


1. Run all of the tests in the solution and verify that they are successful.
2. Start the OrderManagement application without debugging.
3. In the Order Management window, on the Contacts tab, click Get to retrieve the details of contact 1
from the Web service. No credentials have been supplied; therefore, an exception is thrown. In the
message box displaying the message "Access is denied", click OK.
4. Test the application by using the two sets of credentials in the following table. You should only be
able to use the Contacts tab when you are authenticated as Fred, and you should only be able to use
the General Orders, Orders By Contact, and Orders By Product tabs when you are authenticated
as Bert. You should not be able to access any data if you omit or specify incorrect credentials.

Username Password Role


Lab Instructions: Building an N-Tier Solution by Using the Entity Framework 23

Username Password Role

Fred Pa$$w0rd ContactAdmin

Bert Pa$$w0rd OrderAdmin


5. Close Visual Studio.
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 1

Module 10
Lab Instructions: Handling Updates in an N-Tier Solution by
Using the Entity Framework
Contents:
Exercise 1: Handling Updates in the Data Access Tier 4
Exercise 2: Detecting and Handling Order Conflicts 11
2 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

Lab: Handling Updates in an N-Tier Solution by Using


the Entity Framework

Objectives
After completing this lab, you will be able to:
Track the changes that are made in a client application by using STEs.
Handle any concurrency errors that the data access layer detects when it saves changes to the
database.

Introduction
In this lab, you will extend the Adventure Works Orders n-tier application to support data modifications
by using STEs. You will enable the client application to select the strategy for the data access layer to use
when the data access layer detects a concurrency error.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-10 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 3

Lab Scenario

The Adventure Works Orders application is an n-tier application that enables users to search for orders by
using different criteria. The client application is a Windows Presentation Foundation (WPF) application
that uses a WCF service to access the EDM.
You have been asked to extend this application to enable users to make changes to orders. Users must be
able to create new orders, modify existing orders, and delete orders.

This is a multiuser application, so it must be able to detect and handle any concurrency conflicts that
occur when two or more users edit the same data simultaneously. Users of the application should be able
to choose what action to take when the application detects a conflict.
4 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

Exercise 1: Handling Updates in the Data Access Tier


Scenario
You have been asked to extend the existing Adventure Works Orders application to support the
modification of order data. You will use the existing STE classes to transfer data modification requests to
the data access layer. In this first phase, you will not detect any concurrency errors.

The main tasks for this exercise are as follows:


1. Prepare the environment for the lab.
2. Create the virtual directory for the Web service.
3. Enable Secure Sockets Layer (SSL) for the OrdersWebService Web service.
4. Open the starter project for this exercise.
5. Modify the entity model and rebuild the self-tracking entities.
6. Define the PlaceOrder, CancelOrder, and AmendOrder methods in the OrdersService project.
7. Implement the PlaceOrder, CancelOrder, and AmendOrder methods in the OrdersService project.
8. Update the WPF client application.
9. Add a unit test for the PlaceOrder, CancelOrder, and AmendOrder methods.
10. Build and test the application.

Task 1: Prepare the environment for the lab


1. Log on to the 10265A-GEN-DEV-10 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator to install IIS and create the user
accounts for the application.
3. In the E:\Labfiles folder, run AWReset.bat to create the AdventureWorks database.

Task 2: Create the virtual directory for the Web service


In the E:\Labfiles\Lab10\CS\Ex1\Starter folder (if you are using Microsoft Visual C#) or the
E:\Labfiles\Lab10\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic), run ExSetup.bat as
an administrator. This script adds the required virtual directories to IIS.

Task 3: Enable SSL for the OrdersWebService Web service


1. Open IIS Manager as an administrator.
2. Add a new self-signed certificate named OrdersWebService.
3. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.
4. Configure the OrdersWebService application to require SSL security.
5. Close IIS Manager.

Task 4: Open the starter project for this exercise


1. Open Visual Studio 2010 as an administrator.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab10\VB\Ex1\Starter\OrdersDAL or
E:\Labfiles\Lab10\CS\Ex1\Starter\OrdersDAL folder.

Task 5: Modify the entity model and rebuild the self-tracking entities
1. Open the AdventureWorks EDM in the ADO.NET Entity Data Model Designer (Entity Designer).
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 5

2. In the AdventureWorks EDM model, set the StoreGeneratedPattern property of the SubTotal field
of the SalesOrderHeader entity to None.
3. Save the AdventureWorks EDM model.
4. Delete the existing T4 template files.
5. Re-create the STEs by using the T4 templates.
6. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.
7. Close the AdventureWorks EDM model.
8. Review the task list.
9. Open the AdditionalMethods code file by double-clicking the TODO: Ex1 - Add
CalculateOrderTotal method to recalculate the sub total task in the task list. This task is located in
the partial SalesOrderHeader class.
10. Immediately after the TODO: Ex1 - Add CalculateOrderTotal method to recalculate the sub total
comment, add a void method called CalculateOrderTotal that recalculates the value of the SubTotal
property of the SalesOrderHeader object. For each SalesOrderDetail object, the total is calculated
according the following formula:
LineTotal = UnitPrice * (1 - UnitPriceDiscount) * Quantity
11. Locate the next comment in the AdditionalMethods file by double-clicking the TODO: Ex1 - Validate
an order object and verify that it contains order details task in the task list. This task is located in
the partial SalesOrderHeader class.
12. Immediately after the TODO: Ex1 - Validate an order object and verify that it contains order
details comment, add a method called Validate that returns a Boolean value. This method should
return true if the SalesOrderHeader object contains at least one SalesOrderDetail object; otherwise,
it should return false.
13. Save the AdditionalMethods file.

Task 6: Define the PlaceOrder, CancelOrder, and AmendOrder methods in the


OrdersService project
1. Review the task list.
2. Open the IOrdersService code file by double-clicking the TODO: Ex1 - PlaceOrder task in the task
list. This task is located in the IOrdersService interface.
3. Immediately after the TODO: Ex1 - PlaceOrder comment, define a method called PlaceOrder that
returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method with
the OperationContract attribute and the FaultContract attribute with a type parameter of
ServiceFault.
4. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex1 -
AmendOrder task in the task list. This task is located in the IOrdersService interface.
5. Immediately after the TODO: Ex1 - AmendOrder comment, define a method called AmendOrder
that returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method
with the OperationContract attribute and the FaultContract attribute with a type parameter of
ServiceFault.
6. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex1 - CancelOrder
task in the task list. This task is located in the IOrdersService interface.
7. Immediately after the TODO: Ex1 - CancelOrder comment, define a method called CancelOrder
that returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method
with the OperationContract attribute and the FaultContract attribute with a type parameter of
ServiceFault.
8. Save the IOrdersService file.
6 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

Task 7: Implement the PlaceOrder, CancelOrder, and AmendOrder methods in the


OrdersService project
1. Review the task list.
2. Open the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
updateOrderEntityCollectionAndSaveChangesToDatabase task in the task list. This task is located
in the OrdersServiceImpl class.
3. Immediately after the TODO: Ex1 - updateOrderEntityCollectionAndSaveChangesToDatabase
comment, define a method called updateOrderEntityCollectionAndSaveChangesToDatabase that
returns a Boolean value and takes a string object called operationName as the first parameter and a
SalesOrderHeader object called order as the second parameter. In the method, write code to
perform the following tasks:

a. Create a new AdventureWorksEntities context object.


b. Call the ApplyChanges method on the SalesOrderHeaders entity set object in this context,
passing the order object as a parameter.
c. Call the SaveChanges method of the context object. Return true if the number of changes that
are saved is greater than zero; otherwise, return false.
d. If any exceptions are thrown, record the details by calling the logException method, and then
throw a new FaultException exception. Specify the value of the operationName parameter and
the order ID for the order in the log message and the fault.
4. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
PlaceOrder implementation task in the task list. This task is located in the OrdersServiceImpl class.
5. Immediately after the TODO: Ex1 - PlaceOrder implementation comment, define a method called
PlaceOrder that returns a Boolean value and takes a SalesOrderHeader object called newOrder as a
parameter. Mark the method with the PrincipalPermission attribute and specify that this method
can only be run by members of the OrderAdmin security role. In the method, write code to perform
the following tasks:

a. Call the Validate method on the SalesOrderHeader object. If the validation fails, create a
ServiceFault object with the message "Order has no details" and throw a new FaultException
exception that wraps this ServiceFault object together with the message "Orders must have
details".
b. Call the CalculateOrderTotal method on the SalesOrderHeader object.
c. Call the updateOrderEntityCollectionAndSaveChangesToDatabase method, passing the string
"PlaceOrder" as the first parameter and the newOrder object as the second parameter, and
return the result of the method call.
6. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
AmendOrder implementation task in the task list. This task is located in the OrdersServiceImpl
class.
7. Immediately after the TODO: Ex1 - AmendOrder implementation comment, define a method
called AmendOrder that returns a Boolean value and takes a SalesOrderHeader object called order
as a parameter. Mark the method with the PrincipalPermission attribute and specify that this
method can only be run by members of the OrderAdmin security role. In the method, write code to
perform the following tasks:

a. Call the Validate method on the SalesOrderHeader object. If the validation fails, create a
ServiceFault object with the message "Order has no details" and throw a new FaultException
exception that wraps this ServiceFault object together with the message "Orders must have
details".
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 7

b. Call the CalculateOrderTotal method on the SalesOrderHeader object.


c. Call the updateOrderEntityCollectionAndSaveChangesToDatabase method, passing the string
"AmendOrder" as the first parameter and the order object as the second parameter, and return
the result of the method call.
8. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
CancelOrder implementation task in the task list. This task is located in the OrdersServiceImpl
class.
9. Immediately after the TODO: Ex1 - CancelOrder implementation comment, define a method called
CancelOrder that returns a Boolean value and takes a SalesOrderHeader object called order as a
parameter. Mark the method with the PrincipalPermission attribute and specify that this method
can only be run by members of the OrderAdmin security role. In the method, write code to perform
the following tasks:

a. Call the MarkAsDeleted method on the order object.


b. Call the updateOrderEntityCollectionAndSaveChangesToDatabase method, passing the string
"CancelOrder" as the first parameter and the order object as the second parameter, and return
the result of the method call.
10. Save the OrdersServiceImpl file.
11. Build the solution and correct any errors.

Task 8: Update the WPF client application


1. In the OrderManagement project, open the OrderManagementWindow.xaml file.
2. Click the Orders By Contact tab. The functionality that the controls on this tab provide has been
extended to enable a user to create, edit, and delete orders for a contact, as follows:

a. The New Order button creates a new order. Pressing INSERT in the TreeView control also
creates a new order.
b. Pressing DELETE in the TreeView control deletes an order.
c. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an
order; you cannot delete items from an order.
3. Update the service reference in the OrderManagement project.
4. Review the task list.
5. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Ex1 - Save the order to the database task in the task list. This task is located in the
editOrder method, which is called when the user has made changes to an order and wants to save
them.
6. Immediately after the TODO: Ex1 - Save the order to the database comment, write code to
perform the following tasks:

a. Call the AmendOrder method of the service object, passing the order object as a parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation object to "Order
saved". This object is a status bar item that appears at the bottom of the window.
c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order
not saved".
7. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking
the TODO: Ex1 - Delete the order from the database task in the task list. This task is located in the
deleteOrder method, which is called when the user wants to cancel an order.
8 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

8. Immediately after the TODO: Ex1 - Delete the order from the database comment, write code to
perform the following tasks:

a. Call the CancelOrder method of the service object, passing the order object as a parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation object to "Order
deleted".
c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order
not deleted".
9. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking
the TODO: Ex1 - Add the new order to the database task in the task list. This task is located in the
addOrder method, which is called when the user wants to place a new order for a contact.
10. Immediately after the TODO: Ex1 - Add the new order to the database comment, write code to
perform the following tasks:

a. Call the PlaceOrder method of the service object, passing the order object as a parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation object to "New
order saved".
c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order
not saved".
d. Update the user interface (UI) by simulating the user double-clicking the getOrdersForContact
button.

11. Save the code file behind the OrderManagementWindow.xaml window.

Task 9: Add a unit test for the PlaceOrder, CancelOrder, and AmendOrder methods
1. Update the service reference in the OrdersServiceTest project.
2. Review the task list.
3. Open the OrdersServiceImplTest file by double-clicking the TODO: Ex1 - Implement a test for
PlaceOrder, AmendOrder, CancelOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method.
4. Immediately after the TODO: Ex1 - Implement a test for PlaceOrder, AmendOrder, CancelOrder
comment, add the following test code. This code performs the following tasks:
a. It connects to the Web service as the user Bert with a password of Pa$$w0rd. This user is a
member of the OrderAdmin role.
b. It places a new order and verifies that the service has added the order correctly. It uses the
CreateOrder helper method to create a new order and populate a SalesOrderHeader object.
c. It modifies the order that was just added and verifies that the service has modified the order
correctly.
d. It deletes the order that was just added and verifies that the service has deleted the order
correctly.

[Visual Basic]

service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"
service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"

Dim productID As Integer = 709


Dim expectedOrderCount As Integer = 188
Dim expectedOrderDetailsCount As Integer = 2

' Create a new order.

Dim order As SalesOrderHeader = CreateOrder()


Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 9

order.CalculateOrderTotal()
service.PlaceOrder(order)

Dim actual As IEnumerable(Of SalesOrderHeader) =


service.GetOrdersForProduct(productID)

' Check the Order Header was added.


Assert.AreEqual(expectedOrderCount + 1, actual.Count())
' Check the Order Details were added.

Dim addedOrder As SalesOrderHeader = actual.Last()


Assert.AreEqual(expectedOrderDetailsCount,
addedOrder.SalesOrderDetails.Count)

' Modify the order.

Dim expectedComment As String = "Modified"


addedOrder.Comment = expectedComment
service.AmendOrder(addedOrder)

actual = service.GetOrdersForProduct(productID)

Dim modifiedOrder As SalesOrderHeader = actual.Last()


Assert.AreEqual(expectedComment, modifiedOrder.Comment)

' Delete the order.

service.CancelOrder(modifiedOrder)

actual = service.GetOrdersForProduct(productID)
Assert.AreEqual(expectedOrderCount, actual.Count())

[Visual C#]

service.ClientCredentials.Windows.ClientCredential.UserName
= "Bert";
service.ClientCredentials.Windows.ClientCredential.Password
= "Pa$$w0rd";

int productID = 709;


int expectedOrderCount = 188;
int expectedOrderDetailsCount = 2;

// Create a new order.


SalesOrderHeader order = CreateOrder();
order.CalculateOrderTotal();
service.PlaceOrder(order);
IEnumerable<SalesOrderHeader> actual = service.GetOrdersForProduct(productID);

// Check the Order Header was added.


Assert.AreEqual(expectedOrderCount+1, actual.Count());
// Check the Order Details were added.
SalesOrderHeader addedOrder = actual.Last();
Assert.AreEqual(expectedOrderDetailsCount, addedOrder.SalesOrderDetails.Count);

// Modify the order.


string expectedComment = "Modified";
addedOrder.Comment = expectedComment;
service.AmendOrder(addedOrder);
actual = service.GetOrdersForProduct(productID);
SalesOrderHeader modifiedOrder = actual.Last();
10 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

Assert.AreEqual(expectedComment, modifiedOrder.Comment);

// Delete the order.


service.CancelOrder(modifiedOrder);
actual = service.GetOrdersForProduct(productID);
Assert.AreEqual(expectedOrderCount, actual.Count());

5. Save the OrdersServiceImplTest file.

Task 10: Build and test the application


1. Build the solution and correct any errors.
2. Run all of the tests in the solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode.
5. In the Order Management window perform the following tasks:
a. Click the Orders By Contact tab.
b. In the Username box, type Bert
c. In the Password box, type Pa$$w0rd
d. In the Contact ID box, type 200 and then click Get. Verify that you can add a new order, modify
an existing order, and delete an order. Note that you can add line items to an order, but you
cannot remove them after an order has been saved. However, you can amend existing line items.
Note the following features of the application and the data in the database:

a. Make sure that you use a valid product ID, for example, 905 or 906.
b. Make sure that you use a discount of less than 1.0, for example, 0.05 otherwise the order will not
be saved (a constraint in the database prevents you from creating orders that have a negative
value.)
c. The New Order button creates a new order. Pressing INSERT in the TreeView control also
creates a new order.
d. Pressing DELETE in the TreeView control deletes an order.
e. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an
order; you cannot delete items from an order.
6. Close the application.
7. Close the solution.
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 11

Exercise 2: Detecting and Handling Order Conflicts


Scenario
You have been asked to enhance the application so that it detects concurrency errors when two or more
users attempt to edit the same order data. When the application detects a conflict, the client application
should give the current user the choice of whether to overwrite the existing data in the database or
discard the changes.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Modify the entity model and rebuild the self-tracking entities.
3. Add the types that are required to serialize faults to the client.
4. Modify the service implementation to detect and handle concurrency issues.
5. Update the WPF client application.
6. Update the unit tests for the PlaceOrder, CancelOrder, and AmendOrder methods.
7. Build and test the application.

Task 1: Open the starter project for this exercise


1. In the E:\Labfiles\Lab10\CS\Ex2\Starter folder (if you are using Visual C#) or the
E:\Labfiles\Lab10\VB\Ex2\Starter folder (if you are using Visual Basic), run ExSetup.bat as an
administrator.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab10\VB\Ex2\Starter\OrdersDAL or
E:\Labfiles\Lab10\CS\Ex2\Starter\OrdersDAL folder.

Task 2: Modify the entity model and rebuild the self-tracking entities
1. Open the AdventureWorks EDM in the Entity Designer.
2. In the AdventureWorks EDM model, set the Concurrency Mode property of the RevisionNumber
field of the SalesOrderHeader entity to Fixed.
3. Save the AdventureWorks EDM model.
4. Delete the existing T4 template files.
5. Re-create the STEs by using the T4 templates.
6. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.
7. Close the AdventureWorks EDM model.

Task 3: Add the types that are required to serialize faults to the client
1. Review the task list.
2. Open the IOrdersService file by double-clicking the TODO: Ex2 - Create
OptimisticConcurrencyExceptionReason enumeration task in the task list. This task is located in
the IOrdersService file.
3. Immediately after the TODO: Ex2 - Create OptimisticConcurrencyExceptionReason enumeration
comment, add an enumeration called OptimisticConcurrencyExceptionReason with three values
called None, ItemAlreadyDeleted, and ItemAlreadyAddedOrUpdated. Mark the enumeration with
the DataContract attribute, and mark each value with the EnumMember attribute.
4. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Create
ConcurrencyFault class task in the task list.
12 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

5. Immediately after the TODO: Ex2 - Create ConcurrencyFault class comment, add a class called
ConcurrencyFault with two public fields. The first field is called Reason and is of type
OptimisticConcurrencyExceptionReason. The second field is called ConflictingValues and is of
type Dictionary; both the key and the value are strings. Mark the class with the DataContract
attribute, and mark each field with the DataMember attribute. Additionally, mark the class with the
ServiceKnownType attribute and specify the type of the OptimisticConcurrencyException
enumeration; this enables WCF to serialize and deserialize the Reason field correctly.
6. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Define
ConflictResolutionStrategy enumeration task in the task list. This task is located in the
OrdersService namespace.
7. Immediately after the TODO: Ex2 - Define ConflictResolutionStrategy enumeration comment,
add an enumeration called ConflictResolutionStrategy with three values called None, ClientWins,
and StoreWins. Mark the enumeration with the DataContract attribute, and mark each value with
the EnumMember attribute.
8. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConflictResolutionStrategy as a known serializable type for the service task in the task list. This
task is located just before the IOrdersService interface.
9. Delete the TODO: Ex2 - Add ConflictResolutionStrategy as a known serializable type for the
service comment, and replace it with the ServiceKnownType attribute with a parameter of type
ConflictResolutionStrategy.
10. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConcurrencyFault to the list of faults thrown by PlaceOrder task in the task list. This task is
located just before the PlaceOrder method.
11. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by PlaceOrder
comment, and replace it with the FaultContract attribute with a parameter of type
ConcurrencyFault.
12. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
parameters to specify the conflict resolution strategy to use in PlaceOrder task in the task list.
This task is located just before the PlaceOrder method.
13. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in
PlaceOrder comment, and modify the parameter list of the PlaceOrder method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
14. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConcurrencyFault to the list of faults thrown by AmendOrder task in the task list. This task is
located just before the AmendOrder method.
15. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by AmendOrder
comment, and replace it with the FaultContract attribute with a parameter of type
ConcurrencyFault.
16. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
parameters to specify the conflict resolution strategy to use in AmendOrder task in the task list.
This task is located just before the AmendOrder method.
17. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in
AmendOrder comment, and modify the parameter list of the AmendOrder method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 13

18. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConcurrencyFault to the list of faults thrown by CancelOrder task in the task list. This task is
located just before the CancelOrder method.
19. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by CancelOrder
comment, and replace it with the FaultContract attribute with a parameter of type
ConcurrencyFault.
20. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
parameters to specify the conflict resolution strategy to use in CancelOrder task in the task list.
This task is located just before the CancelOrder method.
21. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in
CancelOrder comment, and modify the parameter list of the CancelOrder method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
22. Save the IOrdersService file.

Task 4: Modify the service implementation to detect and handle concurrency issues
1. Review the task list.
2. Open the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Save changes to the database,
and possibly attempt to resolve any concurrency errors task in the task list. This task is located in the
saveChangesToDatabase method.
3. Immediately after the TODO: Ex2 - Save changes to the database, and possibly attempt to
resolve any concurrency errors comment, write code to perform the following tasks:

a. Call the SaveChanges method of the context object in a try block.


b. If the number of changes is greater than zero, return true; otherwise, return false.
c. Create a catch block to handle OptimisticConcurrencyException exceptions.
d. If the resolveConcurrencyException parameter is false, re-throw the exception.
e. If the resolveConcurrencyException parameter is true, check the value of the resolutionStrategy
parameter:
i. If the value of the resolutionStrategy parameter is StoreWins, call the Refresh method of the
context object to refresh the contents of the changedObject object from the database, and
then return false.
Note that the changedObject object is a parameter that is passed to the
saveChangesToDatabase method. It references the object that is updated, inserted, or
deleted from the database.

ii. If the value of the resolutionStrategy parameter is ClientWins, call the Refresh method of
the context object to refresh the contents of the changedObject object from the client. If
the number of changes is greater than zero, return true; otherwise, return false.
iii. For any other value of the resolutionStrategy parameter, throw a new Exception exception to
report an invalid conflict resolution strategy.
4. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a
ConcurrencyFault object, and populate it with the conflicting values task in the task list. This task
is located in the determineCauseOfOptimisticConcurrencyException method. This method runs
when a concurrency fault occurs when saving an order to the database. The purpose of this method is
to determine the cause of the concurrency exception.
14 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

5. Immediately after the TODO: Ex2 - Create a ConcurrencyFault object, and populate it with the
conflicting values comment, write code to perform the following tasks:

a. Create a new ConcurrencyFault object.


b. Use a Language-Integrated Query (LINQ) to Entities query to determine whether the order
object is still in the database.
c. If the order is not found, set the Reason property of the ConcurrencyFault object to
ItemAlreadyDeleted, and set the ConflictingValues property of the ConcurrencyFault object
to null (Nothing in Visual Basic).
d. If the order has been updated by another user, set the Reason property of the
ConcurrencyFault object to ItemAlreadyAddedOrUpdated, and add the properties of the
order in the database to a new Dictionary object in the ConflictingValues property of the
ConcurrencyFault object.
e. Return the populated ConcurrencyFault object.
6. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the updateOrderEntityCollectionAndSaveChangesToDatabase task in the task list.
This task is located just before the updateOrderEntityCollectionAndSaveChangesToDatabase
method.
7. Delete the TODO: Ex2 - Add parameters to the
updateOrderEntityCollectionAndSaveChangesToDatabase comment, and modify the parameter
list of the updateOrderEntityCollectionAndSaveChangesToDatabase method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
8. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Log the
details of the concurrency exception task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method.
9. Immediately after the TODO: Ex2 - Log the details of the concurrency exception comment, add
code that creates a string containing an error message that includes the operation name and the sales
order ID. Write the message to the log file by calling the logException method.
10. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a
ConcurrencyFault object and throw a WCF FaultException that encapsulates the
ConcurrencyFault object task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method.
11. Immediately after the TODO: Ex2 - Create a ConcurrencyFault object and throw a WCF
FaultException that encapsulates the ConcurrencyFault object comment, add code that creates a
ConcurrencyFault object by calling the determineCauseOfOptimisticConcurrencyException
method and throws a new FaultException exception of type ConcurrencyFault containing this
ConcurrencyFault object.
12. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Log the
details of the update exception task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method.
13. Immediately after the TODO: Ex2 - Log the details of the update exception comment, add code
that creates a string containing an error message that includes the operation name and the sales
order ID. Write the message to the log file by calling the logException method.
14. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a
ServiceFault object and throw a WCF FaultException task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method.
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 15

15. Immediately after the TODO: Ex2 - Create a ServiceFault object and throw a WCF FaultException
comment, add code that creates a ServiceFault object by using the message from the
InnerException property of the UpdateException exception and throws a new FaultException
exception of type ServiceFault by using this ServiceFault object.
16. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the PlaceOrder method task in the task list. This task is located just before the
PlaceOrder method.
17. Delete the TODO: Ex2 - Add parameters to the PlaceOrder method comment, and modify the
parameter list of the PlaceOrder method to include two additional parameters. The first new
parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is
called resolutionStrategy and is of type ConflictResolutionStrategy.
18. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass
parameters from the PlaceOrder method task in the task list. This task is located in the PlaceOrder
method.
19. Modify the parameter list of the call to the
updateOrderEntityCollectionAndSaveChangesToDatabase method to include two additional
parameters. The first new parameter is the resolveConcurrencyException object. The second new
parameter is the resolutionStrategy object.
20. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the AmendOrder method task in the task list. This task is located just before the
AmendOrder method.
21. Delete the TODO: Ex2 - Add parameters to the AmendOrder method comment, and modify the
parameter list of the AmendOrder method to include two additional parameters. The first new
parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is
called resolutionStrategy and is of type ConflictResolutionStrategy.
22. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass
parameters from the AmendOrder method task in the task list. This task is located in the
AmendOrder method.
23. Modify the parameter list of the call to the
updateOrderEntityCollectionAndSaveChangesToDatabase method to include two additional
parameters. The first new parameter is the resolveConcurrencyException object. The second new
parameter is the resolutionStrategy object.
24. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the CancelOrder method task in the task list. This task is located just before the
CancelOrder method.
25. Delete the TODO: Ex2 - Add parameters to the CancelOrder method comment, and modify the
parameter list of the CancelOrder method to include two additional parameters. The first new
parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is
called resolutionStrategy and is of type ConflictResolutionStrategy.
26. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass
parameters from the CancelOrder method task in the task list. This task is located in the
CancelOrder method.
27. Modify the parameter list of the call to the
updateOrderEntityCollectionAndSaveChangesToDatabase method to include two additional
parameters. The first new parameter is the resolveConcurrencyException object. The second new
parameter is the resolutionStrategy object.
28. Save the OrdersServiceImpl file.
16 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

29. Build the OrdersService project and correct any errors.

Task 5: Update the WPF client application


1. Update the service reference in the OrderManagement project.
2. Review the task list.
3. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Ex2 - Try to save the order to the database task in the task list. This task is located in the
editOrder method.
4. Immediately after the TODO: Ex2 - Try to save the order to the database comment, write code to
perform the following tasks:

a. Call the AmendOrder method of the service object, passing the order object as the first
parameter, false as the second parameter, and specifying a conflict resolution strategy of None
as the third parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation status bar item
to "Order saved".
c. If the call returns false, set the Content property of the statusOfLastOperation status bar item
to "Order not saved".
5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Ex2 - Handle a concurrency exception in AmendOrder task in the task
list. This task is located in the editOrder method.
6. Immediately after the TODO: Ex2 - Handle a concurrency exception in AmendOrder comment,
write code to perform the following tasks:

a. If the concurrency fault was caused by another user deleting the item, set the Content property
of the statusOfLastOperation status bar item to "Order already cancelled by another user".
b. If the concurrency fault was caused by another user amending the item, set the Content property
of the statusOfLastOperation status bar item to "Order changed by another user", and then
prompt the user by using a message box to determine whether he or she still wants to save the
changes.
c. If the user replies "Yes", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of ClientWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order saved".
d. f the user replies "No", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of StoreWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order not saved".
7. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking
the TODO: Ex2 - Try to delete the order from the database task in the task list. This task is located
in the deleteOrder method.
8. Immediately after the TODO: Ex2 - Try to delete the order from the database comment, write
code to perform the following tasks:

a. Call the CancelOrder method of the service object, passing the order object as the first
parameter, false as the second parameter, and specifying a conflict resolution strategy of None
as the third parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation status bar item
to "Order deleted".
Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 17

c. If the call returns false, set the Content property of the statusOfLastOperation status bar item
to "Order not deleted".

9. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking


the TODO: Ex2 - Handle a concurrency exception in CancelOrder task in the task list. This task is
located in the deleteOrder method.
10. Immediately after the TODO: Ex2 - Handle a concurrency exception in CancelOrder comment,
write code to perform the following tasks:
a. If the concurrency fault was caused by another user deleting the item, set the Content property
of the statusOfLastOperation status bar item to "Order already cancelled by another user".
b. If the concurrency fault was caused by another user amending the item, set the Content property
of the statusOfLastOperation status bar item to "Order changed by another user", and then
prompt the user by using a message box to determine whether he or she still wants to cancel the
order.
c. If the user replies "Yes", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of ClientWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order cancelled".

d. If the user replies "No", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of StoreWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order not cancelled".
11. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking
the TODO: Ex2 - Try to save the new order to the database task in the task list. This task is located
in the addOrder method.
12. Immediately after the TODO: Ex2 - Try to save the new order to the database comment, write
code to perform the following tasks:

a. Call the PlaceOrder method of the service object, passing the order object as the first
parameter, false as the second parameter, and specifying a conflict resolution strategy of None
as the third parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation status bar item
to "New order saved".
c. If the call returns false, set the Content property of the statusOfLastOperation status bar item
to "Order not saved".
13. Save the OrderManagementWindow.xaml code-behind file.

Task 6: Update the unit tests for the PlaceOrder, CancelOrder, and AmendOrder
methods
1. Update the service reference in the OrdersServiceTest project.
2. Review the task list.
3. Open the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass additional
parameters to PlaceOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method.
4. Immediately after the TODO: Ex2 - Pass additional parameters to PlaceOrder comment, modify
the call to the PlaceOrder method to include two additional parameters. The first new parameter to
the PlaceOrder method is true, and the second new parameter specifies a conflict resolution strategy
of StoreWins.
18 Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework

5. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass
additional parameters to AmendOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method.
6. Immediately after the TODO: Ex2 - Pass additional parameters to AmendOrder comment, modify
the call to the AmendOrder method to include two additional parameters. The second parameter to
the AmendOrder method is true, and the third parameter specifies a conflict resolution strategy of
StoreWins.
7. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass
additional parameters to CancelOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method.
8. Immediately after the TODO: Ex2 - Pass additional parameters to CancelOrder comment, modify
the call to the CancelOrder method to include two additional parameters. The second parameter to
the CancelOrder method is true, and the third parameter specifies a conflict resolution strategy of
StoreWins.
9. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Try
to modify the original copy and test for a ConcurrencyFault task in the task list. This task is
located in the AmendOrderConcurrencyTest method.
10. Immediately after the TODO: Ex2 - Try to modify the original copy and test for a
ConcurrencyFault comment, modify the Comment property of the addedOrder object. Next, call
the AmendOrder method and add a test to check that the service returns a concurrency fault, as the
following code example shows.

[Visual Basic]

addedOrder.Comment = "Original order"


Dim expected As Boolean = False

Try
service.AmendOrder(addedOrder, False,
ConflictResolutionStrategy.None)
Catch cf As FaultException(Of ConcurrencyFault)
expected = True
End Try

Assert.IsTrue(expected)

[Visual C#]

addedOrder.Comment = "Original order";


bool expected = false;
try
{
service.AmendOrder(addedOrder, false,
ConflictResolutionStrategy.None);
}
catch (FaultException<ConcurrencyFault> cf)
{
expected = true;
}
Assert.IsTrue(expected);

11. Save the OrdersServiceImplTest file.


Lab Instructions: Handling Updates in an N-Tier Solution by Using the Entity Framework 19

Task 7: Build and test the application


1. Build the solution and correct any errors.
2. As an administrator, stop and restart IIS.
3. In the E:\Labfiles folder, run AWReset.bat to reset the AdventureWorks database to a known state.
4. In Visual Studio, run all of the tests in the solution.
5. Verify that all of the tests succeed.
6. Start the application without debugging.
7. In the Order Management window, click the Orders By Contact tab, and then in the Username box,
type Bert
8. In the Password box, type Pa$$w0rd
9. In the Contact ID box, type 200 and then click Get. Verify that you can still add a new order, modify
an existing order, and delete an order:

a. Make sure that you use a valid product ID, for example, 905 or 906.
b. Make sure that you use a discount of less than 1.0, for example, 0.05.
c. The New Order button creates a new order. Pressing INSERT in the TreeView control also
creates a new order.
d. Pressing DELETE in the TreeView control deletes an order.
e. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an
order; you cannot delete items from an order.

10. If time allows, start a second instance of the application, and attempt to make conflicting changes to
the same orders in each instance. Verify that the application detects the conflicts and resolves them.
Some possible suggestions include:
Changing the order quantity for the same order in both instances.
Deleting an order in one instance, and attempting to modify the order quantity in the second
instance.
Deleting the same order in both instances.
11. Close the application.
12. Close Visual Studio.
Lab Instructions: Building Occasionally Connected Solutions 1

Module 11
Lab Instructions: Building Occasionally Connected Solutions
Contents:
Exercise 1: Modifying the Orders Application to Use Offline XML Data 4
Exercise 2: Modifying the Orders Application to Synchronize Locally
Cached Data 11
2 Lab Instructions: Building Occasionally Connected Solutions

Lab: Building Occasionally Connected Solutions

Objectives
After completing this lab, you will be able to:

Use LINQ to XML to cache entity data in XML files.


Use the Sync Framework to synchronize a SQL Server Compact 3.5 database with a SQL Server Express
database.

Introduction
In this lab, you will use LINQ to XML to cache data in local XML files. You will also use the Sync Framework
to synchronize local data with server data.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:

Start the 10265A-GEN-DEV-11 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Building Occasionally Connected Solutions 3

Lab Scenario

You have been asked to extend the Orders application to support salespeople who need to be able to
query and update the SalesOrderHeader and SalesOrderDetails tables when they work in the field.
You decide to evaluate two different technologies for building this application. One technology caches
data locally in XML files when it is retrieved from the existing data access tier and then uses LINQ to XML
to access this data if the data access tier is inaccessible. The other technology caches data locally in a SQL
Server Compact database and then uses the Sync Framework to refresh the data cache and merge
changes when the application is online.
4 Lab Instructions: Building Occasionally Connected Solutions

Exercise 1: Modifying the Orders Application to Use Offline XML Data


Scenario
In this exercise, you will modify an existing application that connects to the existing data access tier Web
service. You will modify the logic in the application to cache data locally in encrypted XML files when it is
retrieved. If the Web service is not available, the application will use LINQ to XML to query the local XML
data instead. In this mode, the data is read-only.

The main tasks for this exercise are as follows:

1. Prepare the environment for the lab.


2. Open the starter project for this exercise.
3. Write code to construct XML file names.
4. Write code to determine whether the Web service is available.
5. Write code to cache the contacts.
6. Write code to cache the orders.
7. Build and test the application.

Task 1: Prepare the environment for the lab


1. Log on to the 10265A-GEN-DEV-11 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures IIS and creates the
required users and groups.
3. In the E:\Labfiles folder, run AWReset.bat.
4. In the E:\Labfiles\Lab11\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic), or
E:\Labfiles\Lab11\CS\Ex1\Starter folder (if you are using Microsoft Visual C#), run ExSetup.bat as an
administrator. This script adds the required virtual directories to IIS.
5. Open IIS Manager as an administrator.
6. Add a new self-signed certificate named OrdersWebService.
7. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.
8. Configure the OrdersWebService application to require Secure Sockets Layer (SSL) security.
9. Close IIS Manager.

Task 2: Open the starter project for this exercise


1. Run Visual Studio 2010 as an administrator.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab11\VB\Ex1\Starter\OrdersDAL (if you
are using Visual Basic) or E:\Labfiles\Lab11\CS\Ex1\Starter\OrdersDAL (if you are using Visual C#)
folder.

Task 3: Write code to construct XML file names


1. In Visual Studio, review the task list.
2. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Add the System.Xml.Linq namespace task in the task list.
3. Immediately after the comment, add a statement that brings the System.Xml.Linq namespace into
scope.
4. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Define paths and filenames for XML files task in the task list.
5. Immediately after the comment, add code that defines three private string variables named filePath,
contactsFile, and ordersFile.
Lab Instructions: Building Occasionally Connected Solutions 5

6. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Set the file paths task in the task list. This task is located in the
OrderManagementWindow constructor.
7. Immediately after the comment, add code that sets the filePath variable to the path of the user's My
Documents folder and sets the contactsFile variable to this path concatenated with the file name
contacts.xml.
8. Immediately after the next comment, add code that sets the ordersFile variable to the My Documents
path concatenated with the word orders. The remainder of the file name will be constructed at run
time.

Task 4: Write code to determine whether the Web service is available


1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Check that the Web service is available. Return True if it is, false
otherwise task in the task list.
2. Immediately after the comment, add a private method called CheckWebServiceExists that takes an
OrdersWebServiceClient object as a parameter and returns a Boolean value. This method should try
to access the Orders Web service and return true or false to indicate whether a response was
obtained.

Task 5: Write code to cache the contacts


1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the first TODO: Check whether the Web service is still operational task in the task
list. . This task is located in the getContacts_Click method.
2. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getContactsFromLocalCache method and return from the method.
3. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Cache the contacts task in the task list.
4. Immediately after the comment, add a call to the SaveContactsToLocalCache method, passing the
contacts object.
5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get contact information from the local cache file task in the task list.
6. Delete the existing code in this method, and then add code that performs the following tasks:
a. Instantiate a new instance of the list of contacts and then update the message in the
statusMessage status bar item with the text "Fetching contacts "
b. If the contacts XML file exists, call the LoadContactsFromLocalCache method, display the data
in the contactsGrid grid, display the number of contacts retrieved in the numContactRows
label, and then update the message in the statusMessage status bar item with the text "Ready".
c. If there are no cached contacts, update the message in the statusMessage status bar item with
the text "No cached data available".

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Save contact information to the local cache file task in the task list.
8. Delete the existing code in this method, and then add code that performs the following tasks:
a. Construct an XElement object that contains the data in the contacts object that is passed to the
method.
b. Save the XElement object to the file specified by the contactsFile variable.
6 Lab Instructions: Building Occasionally Connected Solutions

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method. Read contact information from the local cache file
task in the task list.
10. Delete the existing code in this method, and then add code that performs the following tasks:

a. Load the contents of the contacts file into an XDocument object.


b. Convert the XML into a list of Contact objects and return this list.

Task 6: Write code to cache the orders


1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the second TODO: Check whether the Web service is still operational task in the
task list. This task is located in the getOrders_Click method.
2. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getOrdersFromLocalCache method and return from the method.
3. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the first TODO: Cache the orders task in the task list. This task is located in the
getOrders_Click method.
4. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the
orders object and the name of the cache file as parameters. Construct the name of the cache file by
concatenating the text "General.xml" to the end of the value in the ordersFile string variable.
5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the third TODO: Check whether the Web service is still operational task in the
task list. This task is located in the getOrdersForContact_Click method.
6. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getOrdersForContactFromLocalCache method and return from the method.
7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the second TODO: Cache the orders task in the task list. This task is located in the
getOrdersForContact_Click method.
8. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the
orders object and the name of the cache file as parameters. Construct the name of the cache file by
concatenating the text "contactN.xml" to the end of the value in the ordersFile string variable, where
N is the contactID of the contact.
9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the fourth TODO: Check whether the Web service is still operational task in the
task list. This task is located in the getOrdersForProduct_Click method.
10. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getOrdersForProductFromLocalCache method and return from the method.
11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the third TODO: Cache the orders task in the task list. This task is located in the
getOrdersForProduct_Click method.
12. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the
orders object and the name of the cache file as parameters. Construct the name of the cache file by
concatenating the text "productN.xml" to the end of the value in the ordersFile string variable, where
N is the productID of the product.
13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get general order information from a local cache file task in the task
list.
14. Delete the existing code in this method, and then add code that performs the following tasks:
Lab Instructions: Building Occasionally Connected Solutions 7

a. Create a new empty list of orders and update the statusMessage status bar item with the text
"Fetching orders ".
b. If the general orders XML file exists, call the LoadOrdersFromLocalCache method to populate
the list of orders, call the displayOrders method to display the data in the ordersTree TreeView
control in the window, display the number of orders in the numOrderRows label, and then
update the statusMessage status bar item with the text "Ready".

Note: The general orders XML file has the name "xxxxGeneral.xml" where the value of the xxxx prefix is
specified by the ordersFile variable.

c. If there are no cached orders, update the statusMessage status bar item with the text "No
cached data available" and clear the ordersTree TreeView control.

15. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get order information for a specified contact from a local cache file
task in the task list.
16. Delete the existing code in this method, and then add code that performs the following tasks:

a. Create a new empty list of orders and update the statusMessage status bar item with the text
"Fetching orders ".
b. If the XML file containing orders for the specified contact exists, call the
LoadOrdersForContactFromLocalCache method to populate the list of orders, call the
displayOrders method to display the data in the ordersForContractTree TreeView control in
the window, display the number of orders in the numOrderForContactRows label, and then
update the statusMessage status bar item with the text "Ready".

Note: The orders XML file has the name "contactN.xml" located in the folder specified by the ordersFile
variable where N is the contact ID.

c. If there are no cached orders, update the statusMessage status bar item with the text "No
cached data available" and clear the ordersForContractTree TreeView control.
17. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get order information for a specified product from a local cache file
task in the task list.
18. Delete the existing code in this method, and then add code that performs the following tasks:
a. Create a new empty list of orders and update the statusMessage status bar item with the text
"Fetching orders ".
b. If the XML file containing orders for the specified contact exists, call the
LoadOrdersForProductFromLocalCache method to populate the list of orders, call the
displayOrders method to display the data in the ordersForProductTree TreeView control in
the window, display the number of orders in the numOrderForProductRows label, and then
update the statusMessage status bar item with the text "Ready".

Note: The orders XML file has the name "productN.xml" located in the folder specified by the ordersFile
variable where N is the product ID.

c. If there are no cached orders, update the statusMessage status bar item with the text "No
cached data available" and clear the ordersForProductTree TreeView control.
8 Lab Instructions: Building Occasionally Connected Solutions

19. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Save order information to the specified cache file task in the task list.
20. Delete the existing code in this method, and then add code that performs the following tasks:

a. Construct an XElement object that contains the data in the orders object that is passed to the
method.
b. Save the XElement object to the file specified by the fileName variable.
21. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to load general order information from a local cache
file task in the task list.
22. Delete the existing code in this method, and then add code that performs the following tasks:

a. Load the contents of the orders file into an XDocument object. The name of the orders file is
held in the fileName variable.
b. Iterate through the XML content in the XDocument object and convert it into a list of Order
objects, and then return this list.

23. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to load order information for a contact from a local
cache file task in the task list.
24. Delete the existing code in this method, and then add code that performs the following tasks:
a. Load the contents of the orders file into an XDocument object. The name of the orders file is
held in the fileName variable.
b. Iterate through the XML content in the XDocument object and convert it into a list of Order
objects, and then return this list.

Note: Use the getOrderDetailsFromCache method to retrieve the order details for each order from the
local cache.

25. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to load order information for a product from a local
cache file task in the task list.
26. Delete the code in this method, and then add code that performs the following tasks:

a. Load the contents of the contacts file into an XDocument object. The name of the orders file is
held in the fileName variable.
b. Iterate through the XML content in the XDocument object and convert it into a list of Order
objects, and then return this list.

Note: Use the getOrderDetailsForProductFromCache method to retrieve the order details for each
order from the local cache.

27. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to retrieve order details for an order task in the task
list.
28. Delete the existing code in this method, and uncomment the code at end of the method definition so
that the method receives an enumerable list of XElement objects containing order information as a
parameter.
29. Add code to the method that performs the following tasks:
Lab Instructions: Building Occasionally Connected Solutions 9

a. Instantiate a trackable collection of SalesOrderDetail objects.


b. Extract the details of each order from the list of XElement objects and store them in the list of
SalesOrderDetail objects.
c. Add each SalesOrderDetail object to the trackable collection and then return this collection.

30. Locate the next comment in the code file behind the OrderManagementWindow.xaml window file by
double-clicking the TODO: Helper method to retrieve order details for an order for a specified
product task in the task list.
31. Delete the existing code in this method, and uncomment the code at end of the method definition so
that the method receives an enumerable list of XElement objects containing order information and
the productID value as parameters.
32. Add code to the method that performs the following tasks:

a. Instantiate a trackable collection of SalesOrderDetail objects.


b. Extract the order details from the list of XElement objects and store them in the list of
SalesOrderDetail objects.
c. Add each SalesOrderDetail object to the trackable collection and then return it.

Task 7: Build and test the application


1. Build the solution and correct any errors.
2. Start the application.
3. In the Username box, type Fred and in the Password box, type Pa$$w0rd
4. Retrieve contacts from 1 to 412.
5. Open Windows Explorer, move to the C:\Users\Admin\My Documents folder, and then verify that a
new XML file named contacts.xml has been created.
6. Open the contacts.xml file in Windows Internet Explorer to verify that the contact data that is
displayed in the application has been written to the file.
7. In the Order Management application, in the Username box, type Bert and in the Password box,
type Pa$$w0rd
8. On the General Orders tab, retrieve orders from 1 to 43784.
9. In Windows Explorer, verify that a new XML file named ordersGeneral.xml has been created.
10. Open the ordersGeneral.xml file in Internet Explorer to verify that the contact data that is displayed in
the application has been written to the file.
11. On the Orders By Contact tab, retrieve orders for contact 1.
12. In Windows Explorer, verify that a new XML file named orderscontact1.xml has been created.
13. On the Orders By Product tab, retrieve orders for product 776.
14. In Windows Explorer, verify that a new XML file named ordersproduct776.xml has been created.
15. Close Windows Explorer, and then close the Order Management application.
16. Open IIS Manager, and then stop the Orders Web service.
17. In Visual Studio, start the Order Management application.
18. In the Order Management application, in the Username box, type Fred and in the Password box,
type Pa$$w0rd
19. Retrieve contacts from 1 to 412, and then verify that you can access the cached data.
10 Lab Instructions: Building Occasionally Connected Solutions

20. In the Order Management application, in the Username box, type Bert and in the Password box,
type Pa$$w0rd

Note: It is not actually necessary to specify the credentials of a user when retrieving information from the
local cache; these credentials are only required by the Web service.

21. On the General Orders tab, retrieve orders from 1 to 43784, and then verify that you can access the
cached data.
22. On the Orders By Contact tab, retrieve orders for contact 1, and then verify that you can access the
cached data.
23. On the Orders By Product tab, retrieve orders for product 776, and then verify that you can access
the cached data.
24. On the Orders By Product tab, retrieve orders for product 777, and then verify that there is no
cached data available.
25. Close the application.
26. In IIS Manager, start the Orders Web service.
27. In Visual Studio, run all of the tests in the solution.
28. Verify that all of the tests succeed.
29. Save and close the solution, and then close Visual Studio.
Lab Instructions: Building Occasionally Connected Solutions 11

Exercise 2: Modifying the Orders Application to Synchronize Locally


Cached Data
Scenario
In this exercise, you will use the Sync Framework to implement an OCA. Data will be cached locally on the
client computer by using a SQL Server Compact database. You will modify the Orders application to use
this local database and synchronize with the data access tier when a connection is available.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Add local data caching to the Orders.
3. Configure the synchronization.
4. Add synchronization code to the OrdersService project.
5. Add synchronization code to the user interface.
6. Build and test the application.

Task 1: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab11\VB\Ex2\Starter\OrdersDAL (if you
are using Visual Basic) or E:\Labfiles\Lab11\CS\Ex2\Starter\OrdersDAL (if you are using Visual C#)
folder.

Task 2: Add local data caching to the Orders application


1. Add a new local database cache named AWCache to the OrdersDAL project.
2. Configure the server connection to connect to the AdventureWorks database on the local computer.
3. Configure the client connection to connect to the AdventureWorksLocal.sdf SQL Server Compact
3.5 database in the E:\Labfiles\Lab11\VB\Ex2\Starter (if you are using Visual Basic) or
E:\Labfiles\Lab11\CS\Ex2\Starter (if you are using Visual C#) folder.
4. Add the Contact (Person), SalesOrderDetail (Sales), and SalesOrderHeader (Sales) tables to the
synchronized database.
5. Configure synchronization to not use SQL Server Change Tracking, and then initiate the first-time
synchronization.
6. Rebuild the solution.

Task 3: Configure synchronization


1. Add a new class named AWCacheSyncAgent to the OrdersDAL project.
2. Modify the AWCacheSyncAgent class to be a public partial class.
3. Add a private partial method called OnInitialized to the class. This method should take no
parameters and not return a value. In this method, add code that sets the SyncDirection property of
each synchronized table to be Bidirectional. You can access the synchronized tables by using the
_person_ContactSyncTable, _sales_SalesOrderDetailSyncTable, and
_sales_SalesOrderHeaderSyncTable fields in the AWCacheSyncAgent class.

Note: If you are using Visual Basic, do not declare the OnInitialized method as Partial.
12 Lab Instructions: Building Occasionally Connected Solutions

Task 4: Add synchronization code to the OrdersService project


1. Add a reference to the Microsoft.Synchronization.Data version 2.0.0.0 assembly to the
OrdersService project.
2. Review the task list.
3. Open the code file for the IOrdersService interface by double-clicking the first TODO: Synchronize
the AdventureWorksLocal SQL Server CE database with the AdventureWorks database in SQL
Server Express task in the task list. This task is located in the IOrdersService code file.
4. Below the summary and returns comments, declare a method named SyncWithServer that takes no
parameters and returns a Boolean value.
5. Open the code file for the OrdersServiceImpl class by double-clicking the TODO: Namespace
containing types required by Synchronization Services task in the task list.
6. After the comment, add a statement that brings the Microsoft.Synchronization.Data namespace
into scope.
7. Locate the next comment by double-clicking the second TODO: Synchronize the
AdventureWorksLocal SQL Server CE database with the AdventureWorks database in SQL
Server Express task in the task list.
8. Below the summary and returns comments, implement the SyncWithServer method. In this method,
use the AWCacheSyncAgent object to synchronize the local SQL Server Compact data with SQL
Server Express.

Task 5: Add synchronization code to the user interface


1. Locate the SyncDatabase_Click method in the code file behind the OrderManagementWindow.xaml
window by double-clicking the TODO: Add code here to synchronize data between SQL Server
Express and AdventureWorksLocal.sdf task in the task list.
2. Add code to this method to call the SyncWithServer method and display the success of the call to
the user.

Task 6: Build and test the application


1. Build the solution and correct any errors.
2. Start the application without debugging.
3. Synchronize the data with SQL Server Express.
4. Retrieve the orders for the contact with a Contact ID value of 1.
5. Display the edit window for the details of the second order that this contact made.
6. Change the quantity of the order to 4 and the discount to 0.5.
7. Synchronize the data with SQL Server Express.
8. In Visual Studio, using Server Explorer, examine the data in the AdventureWorks SQL Server Express
database and verify that the changes were synchronized.
9. In Server Explorer, locate the details for order 44132, change the OrderQty value to 99, and then
press ENTER.
10. Return to the Order Management application and synchronize the data with SQL Server Express
again.
11. Requery the orders for contact 1 and verify that order 44132 has been updated.
12. Close the Order Management application.
13. Save and close the solution, and then close Visual Studio.
Lab Instructions: Querying Data by Using WCF Data Services 1

Module 12
Lab Instructions: Querying Data by Using WCF Data Services
Contents:
Exercise 1: Exposing Order Data as a WCF Data Service 4
Exercise 2: Consuming a WCF Data Service 6
Exercise 3: Restricting Access to Data That a WCF Data Service Exposes 8
Exercise 4: Implementing a Business Operation in a WCF Data Service 10
2 Lab Instructions: Querying Data by Using WCF Data Services

Lab: Creating and Using WCF Data Services

Objectives
After completing this lab, you will be able to:

Expose data by using WCF Data Services.


Consume a WCF Data Service in a Web application.
Restrict access to the data that a WCF Data Service exposes.
Implement a business operation in a WCF Data Service.

Introduction
In this exercise, you will build a WCF Data Service based on an entity model that provides access to data
that is stored in the AdventureWorks database. You will test this data service by performing queries by
using Internet Explorer, and then you will consume the data service in a client application. You will protect
the data that the data service exposes and restrict access to the data for certain types of users. Finally, you
will add a service operation to the data service and invoke this operation from the client application.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:

Start the 10265A-GEN-DEV-12 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Querying Data by Using WCF Data Services 3

Lab Scenario

Adventure Works Cycles subcontracts the shipping of its products to various delivery companies. These
companies need access to the Adventure Works order data so that they can collect and deliver the goods.
You need to expose this information by using WCF Data Services and building a Web application that the
delivery companies can use to access the data.
4 Lab Instructions: Querying Data by Using WCF Data Services

Exercise 1: Exposing Order Data as a WCF Data Service


Scenario
In this exercise, you will build a simple WCF Data Service that exposes the SalesOrderHeader, Contact,
and Address tables from the AdventureWorks database by using an entity data model. You will see how
to access the service from a Web browser, experiment with operators such as $select, and see how to
navigate through the data.

The data service will include exception handling to trap and log any exceptions that the service throws.

The main tasks for this exercise are as follows:


1. Prepare the environment for the lab.
2. Prepare the AdventureWorks database for the lab.
3. Open the starter project.
4. Add a data service to the ShippingDataServiceSite project.
5. Build and test the data service.

Task 1: Prepare the environment for the lab


1. Log on to the 10265A-GEN-DEV-12 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator.

Task 2: Prepare the AdventureWorks database for the lab


In the E:\Labfiles folder, run AWReset.bat.

Task 3: Open the starter project


1. In the E:\Labfiles\Lab12\VB\Ex1\Starter or E:\Labfiles\Lab12\CS\Ex1\Starter folder, run ExSetup.bat as
an administrator.
2. Open Visual Studio 2010 as an administrator.
3. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex1\Starter or
E:\Labfiles\Lab12\CS\Ex1\Starter folder.

Task 4: Add a data service to the ShippingDataServiceSite project


1. Add a WCF data service named ShippingDataService to the ShippingDataServiceSite project.
2. In the Code Editor, add statements to bring the ShippingDataServiceLibrary and
System.Diagnostics namespaces into scope.
3. Add AdventureWorksEntities as the type of the DataService class:
Locate the comment TODO: replace [[class name]] with your data class name for Visual Basic
or TODO: put your data source class name here for Visual C#, delete the comment, and then
type AdventureWorksEntities
If you are using Visual C#, locate the comment TODO: put your data source class name here,
delete the comment, and then type AdventureWorksEntities
4. In the InitializeService method, write code that performs the following tasks:
a. Set the access permissions for the SalesOrderHeaders, Addresses, and Contacts entity sets to
AllRead.
b. Set the page size for the three entity sets to 10.
Lab Instructions: Querying Data by Using WCF Data Services 5

5. Write code that logs service exceptions to the event log. Your code must perform the following tasks:
a. Declare a private constant string named eventSource (_eventSource in Visual Basic) with a value
of Orders Service.
b. Declare a private constant string named eventLog (_eventLog in Visual Basic) with a value of
Application.
c. Define a private method called logException that takes an exception and a string as parameters,
checks that the eventSource (_eventSource in Visual Basic) object exists, and then writes an
entry to the event log.
d. Override the HandleException method to call the logException method, and then call the
HandleException method of the base class.
6. Save the ShippingDataService.svc file.

Task 5: Build and test the data service


1. Build the solution and correct any errors.
2. Browse to the service by using Internet Explorer.
3. Explore the service by using Internet Explorer:

Note: You must right-click anywhere on the page, and then click View Source to view the data in the
following steps. Internet Explorer cannot display the XML data that the service returns.

a. In Internet Explorer, visit http://localhost/ShippingDataServiceSite/ShippingDataService.svc


/Contacts. Review the contact data.
b. In Internet Explorer, visit http://localhost/ShippingDataServiceSite/ShippingDataService.svc
/Contacts(7). Review the contact data.
c. In Internet Explorer, visit http://localhost/ShippingDataServiceSite/ShippingDataService.svc
/Contacts?$filter=LastName gt 'D'. Review the contact data.
4. Close Internet Explorer.
5. Close all instances of Notepad.
6. Close the solution.
6 Lab Instructions: Querying Data by Using WCF Data Services

Exercise 2: Consuming a WCF Data Service


Scenario
In this exercise, you will modify a simple Web client application to consume the data service and display
the data that is retrieved. This application enables users to query all of the data in the entities that the
WCF Data Service exposes.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Call the data service to retrieve SalesOrderHeader entities from a client Web application.
3. Call the data service to retrieve the details of a specific SalesOrderHeader entity.
4. Call the data service to retrieve the details of a specific Address entity.
5. Call the data service to retrieve the details of a specific Contact entity.
6. Build and test the client Web site.

Task 1: Open the starter project


1. In the E:\Labfiles\Lab12\VB\Ex2\Starter or E:\Labfiles\Lab12\CS\Ex2\Starter folder, run ExSetup.bat as
an administrator.
2. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex2\Starter or
E:\Labfiles\Lab12\CS\Ex2\Starter folder.

Task 2: Call the data service to retrieve SalesOrderHeader entities from a client Web
application
1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Retrieve all
SalesOrderHeaders with the chosen shipMethodID in the task list. This task is located in the
SalesOrders method.
3. In the SalesOrders method, immediately after the comment, write code that performs the following
tasks:
a. Assign a new AdventureWorksEntities object to the context variable.
b. Set the Credentials property of the context object to the current default network credentials.
c. Define a LINQ query named orders that retrieves all of the SalesOrderHeader entities with a
ShipMethodID property equal to the value of the shipMethodID variable.
4. Save the HomeController file.

Task 3: Call the data service to retrieve the details of a specific SalesOrderHeader entity
1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the
SalesOrderHeader with SalesOrderID = id in the task list. This task is located in the Details method.
3. In the Details method, immediately after the comment, write code that uses the AddQueryOption
method of the SalesOrderHeaders entity set to filter the entity set and return the
SalesOrderHeader entity with a SalesOrderID property equal to the value of the id variable.
4. Save the HomeController file.
Lab Instructions: Querying Data by Using WCF Data Services 7

Task 4: Call the data service to retrieve the details of a specific Address entity
1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the Address
with AddressID = id in the task list. This task is located in the AddressDetails method.
3. In the AddressDetails method, immediately after the comment, write code that uses the
AddQueryOption method of the Addresses entity set to filter the entity set and return the Address
entity with an AddressID property equal to the value of the id variable.
4. Save the HomeController file.

Task 5: Call the data service to retrieve the details of a specific Contact entity
1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the Contact
with ContactID = id in the task list. This task is located in the ContactDetails method.
3. In the ContactDetails method, immediately after the comment, write code that uses the
AddQueryOption method of the Contacts entity set to filter the entity set and return the Contact
entity with a ContactID property equal to the value of the id variable.
4. Save the HomeController file.

Task 6: Build and test the client Web site


1. Build the solution and correct any errors.
2. Check that the ShippingDetailsSite project is set as the StartUp project.
3. Start the application in Debug mode.
4. Explore the service by using Internet Explorer:
a. On the Adventure Works Delivery Information page, in the Show Orders by Shipping Method
box, type 1 and then click Submit.
b. On the SalesOrders page, click any of the Details hyperlinks.
c. On the Details page, click one of the Contact Details hyperlinks.
5. Close Internet Explorer.
6. Close the solution.
8 Lab Instructions: Querying Data by Using WCF Data Services

Exercise 3: Restricting Access to Data That a WCF Data Service Exposes


Scenario
In this exercise, you will add a query interceptor so that only users with a specific security role can access
sensitive data. You will also update the service to implement paging to reduce the number of items
fetched by a request, and decrease the possibility of wasting bandwidth by performing unconstrained
queries.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Modify the data service to filter returned data based on the user's role membership.
3. Modify the Web client application to support data paging.
4. Build and test the data service and the client Web site.

Task 1: Open the starter project


1. In the E:\Labfiles\Lab12\VB\Ex3\Starter or E:\Labfiles\Lab12\CS\Ex3\Starter folder, run ExSetup.bat as
an administrator.
2. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex3\Starter or
E:\Labfiles\Lab12\CS\Ex3\Starter folder.

Task 2: Modify the data service to filter returned data based on the user's role
membership
1. Review the task list.
2. Open the ShippingDataService.svc file by double-clicking the comment TODO: Ex3 - Filter the query
results based on localgroup membership in the task list. This task is located in the
OnQuerySalesOrderHeaders method.
3. Notice the QueryInterceptor attribute of the OnQuerySalesOrderHeaders method.
4. In the OnQuerySalesOrderHeaders method, immediately after the comment, write code that
performs the following tasks:
a. If the current user is in the WorldwideShipping role, allow the user to view all SalesOrderHeader
entities with non-null ShipDate properties.
b. If the current user is in the USShipping role, allow the user to view all SalesOrderHeader entities
with non-null ShipDate properties and with the ShipMethodID property equal to 1, 2, or 4.
c. Otherwise, return no SalesOrderHeader entities.
5. Save the ShippingDataService.svc file.

Task 3: Modify the Web client application to support data paging


1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Ex3 - Declare a
QueryOperationResponse object in the task list. This task is located in the SalesOrders method.
3. In the SalesOrders method, immediately after the comment, write code that declares a variable
based on the QueryOperationResponse generic type, called response. Specify SalesOrderHeader as
the type parameter, and initialize it to null.
4. Locate the next comment TODO: Ex3 - Get the first page of SalesOrderHeader records in the
SalesOrders method.
Lab Instructions: Querying Data by Using WCF Data Services 9

5. In the SalesOrders method, immediately after the comment, write code that assigns the result of
calling the Execute method on the SalesOrderHeaders entity set to the response variable.
6. Locate the next comment TODO: Ex3 - Get the next page of SalesOrderHeader records in the
SalesOrders method.
7. In the SalesOrders method, immediately after the comment, write code that assigns the result of
calling the Execute method on the SalesOrderHeader entity set to the response variable. Use the
value of the next (_next in Visual Basic) variable to instantiate a Uri object to pass as a parameter to
the Execute method.
8. Save the HomeController file.

Task 4: Build and test the data service and the client Web site
1. Build the solution and correct any errors.
2. Check that the ShippingDetailsSite project is set as the StartUp project.
3. Start the application in Debug mode.
4. Test the application by using the three sets of credentials in the following table.

User name Password Role

Bill Pa$$w0rd USShipping

James Pa$$w0rd None

Mary Pa$$w0rd WorldwideShipping

5. Close Internet Explorer.


6. Close the solution.
10 Lab Instructions: Querying Data by Using WCF Data Services

Exercise 4: Implementing a Business Operation in a WCF Data Service


Scenario
In this exercise, you will add a business operation to archive orders that were shipped more than a year
ago to another table called ArchivedSalesOrderHeader. You will restrict access to this operation to users
who are in the WorldwideShipping security role.

You will modify the Web client application to invoke this operation asynchronously.
The main tasks for this exercise are as follows:
1. Open the starter project.
2. Add a business operation to archive records in the data service.
3. Call a business operation in the data service from a client Web application.
4. Build and test the client Web site.

Task 1: Open the starter project


1. In the E:\Labfiles\Lab12\VB\Ex4\Starter or E:\Labfiles\Lab12\CS\Ex4\Starter folder, run ExSetup.bat as
an administrator.
2. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex4\Starter or
E:\Labfiles\Lab12\CS\Ex4\Starter folder.

Task 2: Add a business operation to archive records in the data service


1. Review the task list.
2. Open the ShippingDataService.svc file by double-clicking the comment TODO: Ex4 - Configure
Service Operation in the task list. This task is located in the InitializeService method.
3. In the InitializeService method, immediately after the comment, write code that calls the
SetServiceOperationAccessRule method of the config object and gives full permissions to the
ArchiveSalesOrders function.
4. Open the ShippingDataService.svc file by double-clicking the comment TODO: Ex4 - Define the
ArchiveSalesOrders operation in the task list. This task is located in the ArchiveSalesOrders
method.
5. Write code that performs the following tasks:
a. Check the role membership of the current user. If the user is not in the WorldwideShipping role,
throw an UnauthorizedAccessException exception.
b. Calculate the archive date. This should be 365 days ago.
c. Define a LINQ query that selects all of the SalesOrderHeader entities that are older than 365
days and have a Status property equal to 8.
d. For each of the SalesOrderHeader entities that the query selects, set the Status property to 8,
and then create a new ArchivedSalesOrderHeader entity.
e. Save the changes.
6. Save the ShippingDataService.svc file.

Task 3: Call a business operation in the data service from a client Web application
1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Ex4 - Define the
ArchiveOrders ActionResult in the task list. This task is located in the ArchiveOrders method.
Lab Instructions: Querying Data by Using WCF Data Services 11

3. Write code that performs the following tasks:


a. Create a new AsyncCallback object for the ArchiveCompleteCallback method.
b. Create a new AdventureWorksEntities context object and assign the current network
credentials to the Credentials property.
c. Invoke the business operation asynchronously by using the static archiveUrl property of the
Constants class.
d. Handle any DataServiceQueryException exceptions by throwing a new ApplicationException
exception.
e. Return an ActionResult object by calling the Content method with a message Archive Complete
as a parameter.

Note: ASP.NET Model-View-Controller (MVC) 1.0 does not directly support asynchronous action methods.
Therefore, the code should use a blocking call to the EndExecute method instead of using the
ArchiveCompleteCallBack method. ASP.NET MVC 2 will enable you to write real asynchronous action
methods.

4. Save the HomeController file.

Task 4: Build and test the client Web site


1. Build the solution and correct any errors.
2. Check that the ShippingDetailsSite project is set as the StartUp project.
3. Start the application in Debug mode.
4. Explore the service by using Internet Explorer:
a. On the Adventure Works Delivery Information page, click ArchiveShipping Orders.
b. Wait until the Archive Complete message appears in the browser.
5. Close Internet Explorer.
6. Close the solution.
Lab Instructions: Updating Data by Using WCF Data Services 1

Module 13
Lab Instructions: Updating Data by Using WCF Data Services
Contents:
Exercise 1: Updating Entities by Using a WCF Data Service 4
Exercise 2: Creating and Deleting Entities by Using a WCF Data Service 6
Exercise 3: Restricting Create, Update, and Delete Requests 10
2 Lab Instructions: Updating Data by Using WCF Data Services

Lab: Updating Data by Using WCF Data Services

Objectives
After completing this lab, you will be able to:
Update existing entities by using a WCF data service.
Create and delete entities by using a WCF data service.
Implement a change interceptor to protect data and prevent unauthorized insert, update, and delete
operations.

Introduction
In this lab, you will write code to enable a client application to update entities that a WCF data service
exposes. You will modify the WCF data service to allow data to be created or deleted. Finally, you will
write code to protect data by intercepting data service requests and applying business logic.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:
Start the 10265A-GEN-DEV-13 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Updating Data by Using WCF Data Services 3

Lab Scenario

Delivery companies have expressed a need to be able to update delivery information remotely. You
decide to modify the Web application to access the data service. You also decide to add update
functionality to the WCF data service.
4 Lab Instructions: Updating Data by Using WCF Data Services

Exercise 1: Updating Entities by Using a WCF Data Service


Scenario
In this exercise, you will modify the Web client application to enable the user to update the status of
orders. The Web application will handle any exceptions that the WCF data service throws.

The main tasks for this exercise are as follows:


1. Prepare the environment for the lab.
2. Prepare the AdventureWorks database for the lab.
3. Open the starter project.
4. Implement a controller action that performs a status update.
5. Enable modifications on the SalesOrderHeaders entity set.
6. Test the ShippingDetailsSite Web application.

Task 1: Prepare the environment for the lab


1. Log on to the 10265A-GEN-DEV-13 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator.

Task 2: Prepare the AdventureWorks database for the lab


In the E:\Labfiles folder, run AWReset.bat.

Task 3: Open the starter project


1. In the E:\Labfiles\Lab13\VB\Ex1\Starter or E:\Labfiles\Lab13\CS\Ex1\Starter folder, run ExSetup.bat as
an administrator.
2. Open Visual Studio 2010 as an administrator.
3. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab13\VB\Ex1\Starter or
E:\Labfiles\Lab13\CS\Ex1\Starter folder.

Task 4: Implement a controller action that performs a status update


1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Add action method to
handle status update.
3. Immediately after the comment, add a new method named HandleStatusUpdate that returns an
ActionResult object and accepts the following parameters:
a. A byte parameter called StatusList.
b. An integer parameter called id.
4. Make the HandleStatusUpdate method public, and then specify that if an exception is thrown, the
Error view should be displayed.
5. In the HandleStatusUpdate method, write code that performs the following tasks:
a. Assign a new AdventureWorksEntities object to the context variable.
b. Set the Credentials property of the context object to the current default network credentials.
c. Define a LINQ query named salesOrderHeaderToChange that retrieves a single
SalesOrderHeader object with a SalesOrderID property that is equal to the value of the id
variable.
Lab Instructions: Updating Data by Using WCF Data Services 5

6. In the HandleStatusUpdate method, write code that performs the following tasks:
a. Set the Status property of the salesOrderHeaderToChange object to the value of the StatusList
variable.
b. Call the UpdateObject method of the context object, passing the salesOrderHeaderToChange
object as a parameter.
c. Handle any DataServiceRequestException exceptions by throwing a new
ApplicationException exception.
d. Save the changes.
e. Return an ActionResult object by calling the RedirectToAction method with "Details/" + id
("Details/" & id in Visual Basic) values as a parameter.
7. Save the HomeController file.

Task 5: Enable modifications on the SalesOrderHeaders entity set


1. Review the task list.
2. Open the ShippingDataService file by double-clicking the TODO: Allow changes to
SalesOrderHeaders task in the task list. This task is located in the InitializeService method.
3. Immediately after the comment, modify the code that sets the access rule for the SalesOrderHeaders
entity to allow WriteReplace access and WriteMerge access.
4. Build the solution and correct any errors.

Task 6: Test the ShippingDetailsSite Web application


1. Check that the ShippingDetailsSite project is set as the startup project.
2. Browse to the ShippingDetailsSite Web site by using Internet Explorer.
3. Explore the service by using Internet Explorer.
4. Close Internet Explorer.
5. Close the solution:
6 Lab Instructions: Updating Data by Using WCF Data Services

Exercise 2: Creating and Deleting Entities by Using a WCF Data Service


Scenario
In this exercise, you will build a Windows Presentation Foundation (WPF) application to enable the user to
add new contacts, modify contacts, and delete contacts.

You will use batching to optimize network access and batch groups of updates together. You will also
implement concurrency checking.
The main tasks for this exercise are as follows:
1. Open the starter project.
2. Connect to the ShippingDataService data service.
3. Call the data service to retrieve contacts in a specified range.
4. Call the data service to retrieve the next page of contact results.
5. Implement the method that adds, deletes, and updates contacts.
6. Save the changes to the data context.
7. Discard the changes to the data context.
8. Enable write access on the Contacts entity set.
9. Test the ContactManagement application.

Task 1: Open the starter project


1. In the E:\Labfiles\Lab13\CS\Ex2\Starter or E:\Labfiles\Lab13\VB\Ex2\Starter folder, run ExSetup.bat as
an administrator.
2. Open the existing solution, ContactManagement.sln, in the
E:\Labfiles\Lab13\VB\Ex2\Starter\ContactManagement or
E:\Labfiles\Lab13\CS\Ex2\Starter\ContactManagement folder.

Task 2: Connect to the ShippingDataService data service


1. Review the task list.
2. Open the MainWindow file by double-clicking the TODO: Add the namespaces containing the
types required to use a WCF Data Service task in the task list.
3. In the Code Editor window, bring the System.Data.Services.Client and System.Net namespaces into
scope.
4. Locate the next comment in the MainWindow file by double-clicking the TODO: Add the
namespace containing the types for the ShippingDataService task in the task list.
5. Immediately after the comment, add a statement to bring the
ContactManagement.ShippingDataService namespace into scope.
6. Locate the next comment in the MainWindow file by double-clicking the TODO: Define a
DataServiceCollection for holding Contact data task in the task list.
7. Immediately after the comment, write code that declares a private field called contactInfo based on
the DataServiceCollection generic type. Specify Contact as the type parameter for the
DataServiceCollection type, and assign it the value null (Nothing in Visual Basic).
8. Locate the next comment in the MainWindow file by double-clicking the TODO: Define an
AdventureWorksEntities context for accessing the service task in the task list.
Lab Instructions: Updating Data by Using WCF Data Services 7

9. Immediately after the comment, write code that declares a variable called context of type
AdventureWorksEntities and assign it the value null.
10. Locate the next comment in the MainWindow file by double-clicking the TODO: Connect to the
ShippingDataService task in the task list.
11. Immediately after the comment, write code that assigns the value that is returned by calling the
ConnectToContext method to the context variable.
12. Locate the next comment in the MainWindow file by double-clicking the TODO: Connect to the
ShippingDataService task in the task list.
13. Immediately after the comment, write code that creates a new AdventureWorksEntities object
named context.
14. Locate the next comment in the MainWindow file by double-clicking the TODO: Set the credentials
for accessing the service task in the task list.
15. Immediately after the comment, write code to set the Credentials property of the context object to
the current default network credentials.
16. Locate the next comment in the MainWindow file by double-clicking the TODO: Return the
connected context task in the task list.
17. Immediately after the comment, write code to return the context variable.
18. Save the MainWindow file.

Task 3: Call the data service to retrieve contacts in a specified range


1. Review the task list.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Fetch all contacts
in the range specified by the user on the form task in the task list. This task is located in the
getContacts_Click method.
3. In the getContacts_Click method, immediately after the comment, write code that performs the
following tasks:
a. Assign the value of the contactIDFrom.Text property to an integer variable called lowerBound.
b. Assign the value of the contactIDTo.Text property to an integer variable called upperBound.
c. Create a DataServiceCollection object that the current context tracks, and assign it to the
contactInfo variable by defining a LINQ query that retrieves an enumerable collection of Contact
objects with a ContactID property greater than or equal to the value of the lowerBound variable
and less than or equal to the upperBound variable.
d. Define a new ObservableCollection object of type Contact by using the contactInfo property
as a parameter, and assign it to the this.contactsGrid.DataContext property
(Me.contactsGrid.DataContext property in Visual Basic).
e. Handle any DataServiceQueryException exceptions or Exception exceptions by displaying a
message box with the following text: Error Fetching Contacts.
4. Save the MainWindow file.

Task 4: Call the data service to retrieve the next page of contact results
1. Review the task list.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Retrieve the next
page of Contact information and display it task in the task list. This task is located in the
moreContacts_Click method.
8 Lab Instructions: Updating Data by Using WCF Data Services

3. In the moreContacts_Click method, immediately after the comment, write code that performs the
following tasks:
a. If the contactInfo.Continuation property is not null (Nothing in Visual Basic), create an
enumerable collection of Contact objects by calling the Execute method of the context object,
passing the contactInfo.Continuation.NextLinkUri property as a parameter. Pass this collection
to the Load method of the contactInfo class.
b. Define a new property based on the ObservableCollection generic type, specifying Contact as
the type parameter. Pass the contactInfo property as a parameter, and assign this object to the
current contactsGrid.DataContext property.
c. Otherwise display a message box with the following text: No More Contacts.
d. Handle any DataServiceQueryException exceptions or Exception exceptions by displaying a
message box with the following text: Error Fetching Next Page of Contacts.
4. Save the MainWindow file.

Task 5: Implement the method that adds, deletes, and updates contacts
1. Review the task list.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Add the new
Contact to the context task in the task list. This task is located in the
contactsGrid_PreviewKeyDown method.
3. In the contactsGrid_PreviewKeyDown method, immediately after the comment, write code that
performs the following tasks:
a. If the newContact object is not null (Nothing in Visual Basic), add it to the context object by
using the AddToContacts method.
b. Insert the newContact object into the displayedContacts collection at the position that the
contactsGrid.SelectedIndex property specifies.
4. Locate the next comment in the MainWindow file by double-clicking the TODO: Remove the
Contact from the context task in the task list.
5. Immediately after the comment, write code to perform the following tasks:
a. Remove the currentContact object from the context.
b. Remove the currentContact object from the displayedContacts collection.
6. Locate the next comment in the MainWindow file by double-clicking the TODO: Update the
Contact object in the context task in the task list.
7. Immediately after the comment, write code to update the currentContact object by calling the
UpdateObject method of the context object.
8. Build the solution and correct any errors.

Task 6: Save the changes to the data context


1. Review the task list.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Send all changes as
a single batch task in the task list. This task is located in the saveChanges_Click method.
3. Immediately after the comment, write code to perform the following tasks:
a. Define a DataServiceResponse object called result and assign this the value that is returned by
calling the SaveChanges method of the context object, specifying the
SaveChangesOptions.Batch enumeration as a parameter.
Lab Instructions: Updating Data by Using WCF Data Services 9

b. Display a message box specifying Changes Saved as the text.


c. Handle any DataServiceRequestException exceptions by displaying a message box with the
following text: Error Saving Changes.
4. Build the solution and correct any errors.

Task 7: Discard the changes to the data context


1. Review the task list.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Clear the context
and fetch the Contact data from the Data Service again task in the task list. This task is located in
the discardChanges_Click method.
3. Immediately after the comment, write code to perform the following tasks:
a. Assign the value that is returned by calling the ConnectToContext method to the context
parameter.
b. Reload the contacts by raising the ClickEvent event object of the getContacts button.
4. Build the solution and correct any errors.

Task 8: Enable write access on the Contacts entity set


1. Review the task list.
2. Open the ShippingDataService file by double-clicking the TODO: Enable Write access on the
Contacts entity set task in the task list. This task is located in the InitializeService method.
3. Immediately after the comment, modify the code that sets the access rule for the Contacts entity to
allow AllWrite access and AllRead access.
4. Build the solution and correct any errors.

Task 9: Test the ContactManagement application


1. Check that the ContactManagement application is set as the startup project.
2. Start the application in Debug mode.
3. In the Contact Management window, next to the Get Contacts button, in the box, type 1
4. Press the TAB key, type 100 and then click Get Contacts.
5. Click More to load more contacts. Repeat this until the No More Contacts message is displayed.
6. In the No More Contacts dialog box, click OK.
7. Edit an existing contact.
8. Insert a new contact.
9. Commit the changes to the database.
10. Delete a contact.
11. Commit the changes to the database.
12. Discard the changes and reload the contact data from the database.
13. Close the ContactManagement application.
14. Close the solution.
10 Lab Instructions: Updating Data by Using WCF Data Services

Exercise 3: Restricting Create, Update, and Delete Requests


Scenario
In this exercise, you will add change interceptors to the WCF data service to limit the operations that
different users can perform and to apply business logic to update operations. Only users in the
AdventureWorksEmployees roles can add, delete, and modify contacts.

The main tasks for this exercise are as follows:


1. Open the starter project.
2. Set the credentials for accessing the ShippingData service.
3. Define the change interceptors.
4. Test the ContactManagement application.

Task 1: Open the starter project


1. In the E:\Labfiles\Lab13\VB\Ex3\Starter or E:\Labfiles\Lab13\CS\Ex3\Starter folder, run ExSetup.bat as
an administrator.
2. Open the existing solution, ContactManagement.sln, in the
E:\Labfiles\Lab13\VB\Ex3\Starter\ContactManagement or
E:\Labfiles\Lab13\CS\Ex3\Starter\ContactManagement folder.

Task 2: Set the credentials for accessing the ShippingData service


1. Review the task list.
2. Open the MainWindow file by double-clicking the TODO: Set the credentials for accessing the
service using the username and password specified on the form task in the task list. This task is
located in the ConnectToContext method.
3. In the ConnectToContext method, immediately after the comment, create a new
NetworkCredentials object by using the userName.Text and password.Password properties as
parameters. Assign this object to the Credentials property of the context object.
4. Save the MainWindow file.
5. Build the solution and correct any errors.

Task 3: Define the change interceptors


1. Review the task list.
2. Open the ShippingDataService file by double-clicking the TODO: Define the change interceptor
task in the task list.
3. In the ShippingDataService class, immediately after the comment, write code that defines a new
Change Interceptor method for the Contacts entity called OnChangeContacts.
4. In the OnChangeContacts method, write code that throws a DataServiceException exception if the
current user is not a member of the AdventureWorksEmployees group. Return a status code of 403
and a status message of "Cannot add, modify or delete contacts".
5. In the OnChangeContacts method, write code that performs the following tasks only when a Change
update operation is performed:
a. Define a LINQ query named oldemail that queries a new AdventureWorksEntities object and
retrieves the string value from the EmailAddress property of the contact that the contact object
specifies.
Lab Instructions: Updating Data by Using WCF Data Services 11

b. If the EmailAddress property of the contact object is not null and the value of the oldEmail
object is null, set the value of the contact object's EmailPromotion property to 1.
c. If the value of the contact object's EmailAddress property is null, set the contact object's
EmailPromotion property to 0.
6. Build the solution and correct any errors.

Task 4: Test the ContactManagement application


1. Check that the ContactManagement application is set as the startup project.
2. Start the application in Debug mode.
3. In the Contact Management window, next to the Get Contacts button, in the box, type 1
4. Press the TAB key, type 100 and then click Get Contacts.
5. In the Error Fetching Contacts dialog box, click Close.
6. In the Contact Management window, in the User Name box, type Mary
7. In the Password box, type Pa$$w0rd
8. Click Get Contacts.
9. Click More to load more contacts. Repeat this until the No More Contacts message is displayed.
10. In the No More Contacts dialog box, click OK.
11. Edit an existing contact.
12. Insert a new contact.
13. Commit the changes to the database.
14. Delete a contact.
15. Commit the changes to the database.
16. Edit an existing contact.
17. Discard the changes and reload the contact data from the database.
18. Close the ContactManagement application.
19. Close the solution, and then close Visual Studio.
Lab Instructions: Using ADO.NET 1

Module 14
Lab Instructions: Using ADO.NET
Contents:
Exercise 1: Using ADO.NET to Retrieve Read-Only Information Quickly and
Perform Simple Data Modifications 4
Exercise 2: Developing the Product List Web Application 14
Exercise 3: Enabling Data Modifications 17
2 Lab Instructions: Using ADO.NET

Lab: Using ADO.NET

Objectives
After completing this lab, you will be able to:

Use ADO.NET objects to connect to a SQL Server database, fetch data, and perform basic data
modifications.
Display information retrieved by using ADO.NET commands.
Use an ADO.NET DataSet to fetch data and perform updates in a multiuser environment.

Introduction
In this lab, you will connect to a SQL Server database by using an ADO.NET connection object and use
ADO.NET objects to query and update data efficiently. You will also use a DataSet object to fetch data
and cache it locally in an application, modify this data, and send the modifications back to a database.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:

Start the 10265A-GEN-DEV-14 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Using ADO.NET 3

Lab Scenario

You have been asked to implement an application that will benefit from using ADO.NET to provide
efficient access to a database. You will create a data access layer that queries and maintains the product
list for the AdventureWorks database by using the data in the Product table. You will use ASP.NET to
create a simple read-only test Web application that customers can use to browse products. You will then
use a legacy Windows Forms application that enables employees to view, filter, and update product data
to perform further tests.
4 Lab Instructions: Using ADO.NET

Exercise 1: Using ADO.NET to Retrieve Read-Only Information Quickly and


Perform Simple Data Modifications
Scenario
In this exercise, you will build a data access layer that uses an ADO.NET DataReader object to fetch
product data from the AdventureWorks database.

You will perform queries by using a collection of stored procedures rather than by dynamically
constructing SQL SELECT statements in the data access layer. This will reduce the chances of SQL injection
attacks. These stored procedures are:
GetProductByID, which returns the product that matches the ID that is specified as a parameter
GetProductByColor, which returns a list of products that match the color that is specified as a
parameter
GetProductByMaxListPrice, which returns a list of all products that have a list price that is less than
or equal to the value that is specified as a parameter
GetAllProducts, which returns a list of all products in the database
You will use the following stored procedures to modify and remove products:
UpdateProduct, which updates a specified product with data that is provided as parameters
DeleteProduct, which deletes the product with the specified ID

Note: Adding new products will not be not supported by this version of the data access layer.

You have been provided with a script that creates these stored procedures and the code for a class called
ProductDataObject that exposes properties that match the columns returned by the queries in each of
the stored procedures. You have also been provided with the definition of an interface called
IProductDataAccessLayer that you will implement, which defines the functionality for the data access
layer.

The main tasks for this exercise are as follows:


1. Prepare the AdventureWorks database for the lab.
2. Open the starter project for this exercise.
3. Create the ProductDataAccessLayer class.
4. Implement the GetProductList method.
5. Implement the GetProduct method.
6. Implement the overloaded GetProductList methods.
7. Implement the UpdateProduct method.
8. Implement the DeleteProduct method.
9. Create unit tests for the data access layer.
10. Test the data access layer.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-14 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat.
Lab Instructions: Using ADO.NET 5

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the solution, Using ADO.NET.sln, in the E:\Labfiles\Lab14\VB\Ex1\Starter\Using ADO.NET or
E:\Labfiles\Lab14\CS\Ex1\Starter\Using ADO.NET folder.

Task 3: Create the ProductDataAccessLayer class


1. Review the IProductDataAccessLayer interface.
This is the interface that the ProductDataAccess class will implement. It defines the methods shown
in the following code example.

[Visual Basic]

Interface IproductDataAccessLayer

''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <returns>An IEnumerable collection of
''' ProductDataObjects</returns>

Function GetProductList() As List(Of ProductDataObject)

''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <param name="color">Only return products that match this
''' color</param>
''' <returns></returns>
Function GetProductList(ByVal color As String) As List(Of
ProductDataObject)

''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <param name="maxListPrice">Only return products where the
''' ListPrice is equal to or less that maxListPrice</param>
''' <returns></returns>

Function GetProductList(ByVal maxListPrice As Decimal) As List(Of


ProductDataObject)

''' <summary>
''' Get a single ProductDataObject by providing the ProductID
''' </summary>
''' <param name="productID">The Int ProductID of the
''' ProductDataObject to return</param>
''' <returns>A ProductDataObject</returns>

Function GetProduct(ByVal productID As Integer) As


ProductDataObject

''' <summary>
''' Updates a ProductDataObject in the Database
''' </summary>
''' <param name="product">The ProductDataObject to update</param>

Function UpdateProduct(ByVal product As ProductDataObject) As


Boolean

''' <summary>
6 Lab Instructions: Using ADO.NET

''' Deletes a single Product from the Database


''' </summary>
''' <param name="product">The ProjectDataObject to delete</param>

Function DeleteProduct(ByVal product As ProductDataObject) As


Boolean

End Interface

[Visual C#]
interface IProductDataAccessLayer
{
/// <summary>
/// Get an Enumerable collection of ProductDataObjects objects.
/// </summary>
/// <returns>An IEnumerable collection of
/// ProductDataObjects.</returns>
List<ProductDataObject> GetProductList();

/// <summary>
/// Get an Enumerable collection of ProductDataObjects.
/// </summary>
/// <param name="color">Only return products that match this
/// color.</param>
/// <returns></returns>
List<ProductDataObject> GetProductList(string color);

/// <summary>
/// Get an Enumerable collection of ProductDataObjects.
/// </summary>
/// <param name="maxListPrice">Only return products where the
/// ListPrice is equal to or less that maxListPrice.</param>
/// <returns></returns>
List<ProductDataObject> GetProductList(decimal maxListPrice);

/// <summary>
/// Get a single ProductDataObject by providing the ProductID.
/// </summary>
/// <param name="productID">The Int ProductID of the
/// ProductDataObject to return.</param>
/// <returns>A ProductDataObject</returns>
ProductDataObject GetProduct(int productID);

/// <summary>
/// Updates a ProductDataObject in the database.
/// </summary>
/// <param name="product">The ProductDataObject to update.</param>
bool UpdateProduct(ProductDataObject product);

/// <summary>
/// Deletes a single product from the database.
/// </summary>
/// <param name="product">The ProjectDataObject to delete.</param>
bool DeleteProduct(ProductDataObject product);

2. Review the ProductDataObject class.


This is the type returned by the methods in the IProductDataAccess interface. The following code
example shows this class.
Lab Instructions: Using ADO.NET 7

[Visual Basic]
Public Class ProductDataObject

Public Property ProductID As Integer

Public Property Name As String


Public Property ProductNumber As String

Public Property Color As String

Public Property ListPrice As Decimal

Public Property ModifiedDate As DateTime

End Class

[Visual C#]
public class ProductDataObject
{
public int ProductID { get; set; }

public string Name { get; set; }

public string ProductNumber { get; set; }

public string Color { get; set; }

public Decimal ListPrice { get; set; }

public DateTime ModifiedDate { get; set; }


}

3. In the DAL project, create a class named ProductDataAccessLayer.


4. Modify the ProductDataAccessLayer class declaration to implement the IProductDataAccessLayer
interface.
5. If you are using Microsoft Visual Basic, generate the method stubs for each of the methods in the
IProductDataAccessLayer interface by positioning the cursor after IProductDataAccessLayer and
pressing ENTER.
If you are using Visual C#, use the Implement Interface Wizard to generate method stubs for each of
the methods in the IProductDataAccessLayer interface.
6. Bring the System.Collections.Generic, System.Data, and System.Data.SqlClient namespaces into
scope.

Task 4: Implement the GetProductList method


1. Review the Constants and AWDatabase classes.
The Constants class contains a series of strings that specify the names of the stored procedures that
the methods in the ProductDataAccess class will invoke. These stored procedures have already been
created in the AdventureWorks database.
The following code example shows this class.

[Visual Basic]
Friend Class Constants
Friend Const GetAllProducts As String = "productGetAllProducts"
8 Lab Instructions: Using ADO.NET

Friend Const GetProductByID As String = "productGetProductByID"


Friend Const GetProductByColor As String = "productGetProductByColor"
Friend Const GetProductByMaxListPrice As String = "productGetProductByMaxListPrice"
Friend Const UpdateProduct As String = "productUpdateProduct"
Friend Const DeleteProduct As String = "productDeleteProduct"
End Class

[Visual C#]
internal class Constants
{
internal const string GetAllProducts = "productGetAllProducts";
internal const string GetProductByID = "productGetProductByID";
internal const string GetProductByColor = "productGetProductByColor";
internal const string GetProductByMaxListPrice = "productGetProductByMaxListPrice";
internal const string UpdateProduct = "productUpdateProduct";
internal const string DeleteProduct = "productDeleteProduct";
}

The AWDatabase class is a singleton that retrieves the connection string that you will use to connect
to the AdventureWorks database from the configuration file.
The following code example shows this class.

[Visual Basic]
Friend Class AWDatabase

''' <summary>
''' The connection string for the AdventureWorks Database
''' </summary>
Friend Shared DatabaseConnectionString As String =
ConfigurationManager.ConnectionStrings("AdventureWorks").ConnectionString

''' <summary>
''' Prevent object construction
''' </summary>
Private Sub New()
End Sub

End Class

[Visual C#]
internal class AWDatabase
{
/// <summary>
/// The connection string for the AdventureWorks Database.
/// </summary>
internal static string DatabaseConnectionString =
ConfigurationManager.ConnectionStrings["AdventureWorks"].ConnectionString;

/// <summary>
/// Prevent object construction.
/// </summary>
private AWDatabase() { }
}

2. Return to the ProductDataAccessLayer class.


3. Locate the GetProductList method, which accepts no parameters.
Lab Instructions: Using ADO.NET 9

If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
Add code to instantiate a generic list of ProductDataObject objects, named products.
4. In the GetProductList method, add a using code block that instantiates a new SqlConnection object
named connection. The SqlConnection object should take the contents of the static field
AWDatabase.DatabaseConnectionString as a parameter. This is the connection string that contains
the details necessary to connect to the AdventureWorks database.
This using code block ensures that the SqlConnection object is correctly disposed of and the
database connection is closed when it goes out of scope.
5. In the using block, add code to perform the following tasks:
a. Create a SqlCommand object called getAllProducts. This should run the string that is specified
by the GetAllProducts field in the Constants class and use the connection that is defined by the
using statement.
b. Set the CommandType property of the SqlCommand object to specify that the command runs
a stored procedure.
c. Open the connection to the database.
d. Create a SqlDataReader object called reader and use it to invoke the stored procedure. Specify
that the SqlDataReader object should automatically close the connection after it has retrieved
the data returned by the stored procedure.
e. Iterate through the results returned by the SqlDataReader object. For each row returned by the
stored procedure, create a new ProductDataObject object. Populate the fields in the
ProductDataObject object with the data returned by the SqlDataReader object, as shown in the
following table, and add the ProductDataObject object to the products list.

Column number Type of column Can be null? Field to populate

0 Int32 No ProductID

1 String No Name

2 String No ProductNumber

3 String Yes Color

4 Decimal No Price

5 Date Yes ModifiedDate

6. At the end of the GetProductList method, return the list of products.


7. Build the solution and correct any errors.

Task 5: Implement the GetProduct method


1. Locate the GetProduct method in the ProductDataAccessLayer class, which accepts an integer
parameter that specifies a product ID.
If you are using Visual C#. remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
2. In the GetProduct method, add a ProductDataObject object named prod and assign it the value
null.
10 Lab Instructions: Using ADO.NET

3. In the GetProduct method, add a using code block that instantiates a new SqlConnection object
named connection. The SqlConnection object should use the static field
AWDatabase.DatabaseConnectionString as a parameter.
4. In the using block, add code to perform the following tasks:
a. Create a SqlCommand object called getProductsId. This should run the string that is specified
by the GetProductByID field in the Constants class and use the connection that is defined by
the using statement.
b. Set the CommandType property of the SqlCommand object to specify that the command runs
a stored procedure.
c. Create a SqlParameter object for the productID parameter that is passed in. The
GetProductByID method expects the product ID as a parameter. It returns all products that
match this product ID.
d. Open the connection to the database.
e. Create a SqlDataReader object called reader and use it to invoke the stored procedure. Specify
that the SqlDataReader object should automatically close the connection after it has retrieved
the data returned by the stored procedure.
f. Read the first row of the results returned by the SqlDataReader object. Instantiate the prod
object as a new ProductDataObject object. Populate the fields in the ProductDataObject
object with the data returned by the SqlDataReader object, as shown in the following table.

Column number Type of column Can be null? Field to populate

0 Int32 No ProductID

1 String No Name

2 String No ProductNumber

3 String Yes Color

4 Decimal No Price

5 Date Yes ModifiedDate

5. At the end of the GetProductById method, return the prod object.


6. Build the solution and correct any errors.

Task 6: Implement the overloaded GetProductList methods


1. Locate the GetProductList method, which accepts a string parameter that specifies the color to
search for.
If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
2. Add code to instantiate a generic list of ProductDataObject objects, named products.
3. In the GetProductList method, add code that creates a connection to the database by using the
value that is specified by the static field AWDatabase.DatabaseConnectionString and then uses a
SqlDataReader object to execute the stored procedure that is specified by the
Constants.GetProductByColor string. This stored procedure expects a string parameter called color
that indicates the color to match; create a SqlParameter object based on the parameter that is
passed to the GetProductList method. Iterate through the results and fetch each matching product.
Add the products to the products list, and return this list from the GetProductList method.
Lab Instructions: Using ADO.NET 11

The method should be similar to the original GetProductList method that takes no parameters.
4. Locate the GetProductList method, which accepts a decimal parameter that specifies the maximum
price to search for.
If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception..
5. In this version of the GetProductList method, add code to retrieve all products that have a price that
is less than or equal to the price that is specified as the parameter. Use the stored procedure that is
specified by the Constants.GetProductByMaxListPrice field. The stored procedure expects a
parameter named maxListPrice. Create and return a list of all matching ProductDataObject objects.
6. Build the solution and correct any errors.

Task 7: Implement the UpdateProduct method


1. Locate the UpdateProduct method. This method takes a ProductDataObject object as a parameter
and returns a Boolean value. You will implement this method to update the specified product in the
database with the information in this object and return true if the operation is successful or false
otherwise.
2. If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
3. In the body of the method, add code to perform the following tasks:
a. Create a Boolean variable called result and initialize it to false. You will use this variable to
indicate whether the delete was successful.
b. Create a database connection object in a using block. Connect to the database by using the
connection string that is specified in the AWDatabase.DatabaseConnectionString field.
c. Create a SqlCommand object that can be used to execute the stored procedure that is specified
by the Constants.UpdateProduct field.
d. Create SqlParameter objects with the names that are specified in the following table. Populate
these SqlParameter objects with the data from the ProductDataObject object that is passed in
to the method.

Stored procedure parameter


name ProductDataObject field to use

@productID ProductID

@name Name

@productNumber ProductNumber

@color Color

@listPrice ListPrice

e. Add the parameters to the SqlCommand object.


f. Open the connection to the database.
g. Execute the stored procedure by using the ExecuteNonQuery method of the SqlCommand
object and set the value of the result variable to true. Note that if the update fails, a
SqlException exception will be thrown.
h. Catch the SqlException exception and set the value of the result variable to false.
i. Return the result variable.
12 Lab Instructions: Using ADO.NET

4. Build the solution and correct any errors.

Task 8: Implement the DeleteProduct method


1. Locate the DeleteProduct method. This method takes a ProductDataObject object as a parameter
and returns a Boolean value. You will implement this method to delete the specified product from the
database and return true if the operation is successful or false otherwise.
2. In Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
3. In the body of the method, add code to perform the following tasks:
a. Create a Boolean variable called result and initialize it to false. You will use this variable to
indicate whether the delete was successful.
b. Create a database connection object in a using block. Connect to the database by using the
connection string that is specified in the AWDatabase.DatabaseConnectionString field.
c. Create a SqlCommand object that can be used to execute the stored procedure that is specified
by the Constants.DeleteProduct field.
d. Create a SqlParameter object with the details that are specified in the following table.

Stored procedure parameter


name ProductDataObject field to use

@productID ProductID

e. Add the parameter to the SqlCommand object.


f. Open the connection to the database.
g. Execute the stored procedure by using the ExecuteNonQuery method of the SqlCommand
object and set the value of result to true. Note that if the update fails, a SqlException exception
will be thrown.
h. Catch the SqlException exception and set the value of result to false.
i. Return the result variable.
4. Build the solution and correct any errors.

Task 9: Create unit tests for the data access layer


1. In the DAL Unit Tests project, open the IProductDataAccessLayerTest class.
2. Locate the CreateIProductDataAccessLayer method and remove the comment TODO: Instantiate
an appropriate concrete class.
3. Add code to perform the following tasks:
a. Create a new instance of the ProductDataAccessLayer class named target.
b. Return the value of the target variable.
4. Locate the GetProductListTest method.
5. Remove the comment TODO and add code to perform the following tasks:
a. Create a variable of type IProductDataAccessLayer named target. Initialize the value of the
target variable by invoking the CreateIProductDataAccessLayer method.
b. Create a new instance of a generic list of ProductDataType objects, named expected. Initialize
this list by invoking the static GetLocalProductList method.

The GetLocalProductList method is part of the IProductDataAccessLayerTest class. It creates a


list that contains a set of known values for testing purposes.
Lab Instructions: Using ADO.NET 13

c. Create another instance of a generic list of ProductDataType objects, named actual. Initialize
this list by invoking the target.GetProductList method.
d. Iterate through the actual and expected lists, and verify that the items in each list are identical.
Use the AreEqual method of the Assert class.
6. Locate the GetProductByProductIDTest method.
7. Remove the comment TODO and add code to perform the following tasks:
a. Create a variable of type IProductDataAccessLayer named target. Initialize the value of the
target variable by invoking the CreateIProductDataAccessLayer method.
b. Define an integer variable called productID and initialize it with the value 319.
c. Create a variable of type ProductDataObject called expected. Initialize this object with the data
for product 319 (use the productID variable) in the list returned by the GetLocalProductList
method.
d. Create another variable of type ProductDataObject called actual. Initialize this object with the
data returned from the GetProduct method of the target object. Pass the productID variable as
the parameter to the GetProduct method.
e. Verify that the data in the actual and expected variables is the same. Use the AreEqual method
of the Assert class.
8. Locate the GetProductListByMaxPriceTest method.
9. Remove the comment TODO and add code to test the version of the GetProductList method that
expects a decimal parameter, specifying the maximum price to search for. Search for products that
have a maximum price of 100. Follow the same general pattern as the GetProductListTest method.
10. Locate the GetProductListByColorTest method.
11. Remove the comment TODO and add code to test the version of the GetProductList method that
expects a string parameter, specifying the color to search for. Search for products that have a color of
"Silver".
12. Locate the UpdateProductTest method.
13. Remove the comment TODO and add code to test the UpdateProduct method by updating product
1; set the color to "Red" and the list price to 12.99. Verify that the UpdateProduct method returns
true when it has successfully updated the product.
14. Locate the DeleteProductTest method.
15. Remove the comment TODO and add code to test the DeleteProduct method by deleting product
1. Verify that the DeleteProduct method returns true when it has successfully deleted the product.
16. Build the solution and correct any errors.

Task 10: Test the data access layer


1. In the DAL Unit Tests project, run all unit tests and verify that they all pass.
2. Close the solution.
14 Lab Instructions: Using ADO.NET

Exercise 2: Developing the Product List Web Application


Scenario
In this exercise, you will build an ASP.NET test application to consume the ADO.NET data and to verify
that the results are returned as expected.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Implement the BrowseColor controller action.
3. Implement the BrowsePrice controller action.
4. Implement the Products controller action.
5. Implement the Details controller action.
6. Test the data access layer by using the Web application.

Task 1: Open the starter project for this exercise


Open the solution, Using ADO.NET.sln, in the E:\Labfiles\Lab14\VB\Ex2\Starter\Using ADO.NET or
E:\Labfiles\Lab14\CS\Ex2\Starter\Using ADO.NET folder. This solution contains an ASP.NET Model-
View-Controller (MVC) Web application that you will use to test the ADO.NET data access layer that
you built in the previous exercise.

Task 2: Implement the BrowseColor controller action


1. Review the task list.
2. Open the HomeController file by double-clicking the comment TODO: Create a new instance of
the ProductDataAccessLayer. This task is located in the BrowseColor method. The purpose of this
method is to retrieve a list of all products that match the color that is specified by the user.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer. and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
4. Locate the comment TODO: Create a list of products.
5. Remove the comment and replace it with a statement that instantiates a generic list of
ProductDataObject objects named products. Initialize this list with the value returned by the
dal.GetProductList method, passing the value productResponse.Color as a parameter to this
method.
6. Locate the comment TODO: Return the Products view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the products collection to the Products view for display.

[Visual Basic]
Return View("Products", products)

[Visual C#]
return View("Products", products);

Task 3: Implement the BrowsePrice controller action


1. Review the task list.
Lab Instructions: Using ADO.NET 15

2. Locate the BrowsePrice method by double-clicking the first comment TODO: Create a new
instance of the ProductDataAccessLayer in the task list. The purpose of this method is to retrieve a
list of all products that have a list price not greater than the maximum price that is specified by the
user.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
4. Locate the comment TODO: Create a list of products.
5. Remove the comment and replace it with a statement that instantiates a generic list of
ProductDataObject objects named products. Initialize this list with the value returned by the
dal.GetProductList method, passing the value maxprice as a parameter to this method.
6. Locate the comment TODO: Return the Products view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the products collection to the Products view for display.

[Visual Basic]

Return View("Products", products)

[Visual C#]

return View("Products", products);

Task 4: Implement the Products controller action


1. Review the task list.
2. Locate the Products method by double-clicking the first comment TODO: Create a new instance of
the ProductDataAccessLayer in the task list. The purpose of this method is to retrieve a list of all
products.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
4. Locate the comment TODO: Create a list of all products.
5. Remove the comment and replace it with a statement that instantiates a generic list of
ProductDataObject objects named products. Initialize this object with the data returned by the
dal.GetProduct method. Pass the product ID that is passed as the parameter to this method to the
GetProductList method.
6. Locate the comment TODO: Return the Products view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the products collection to the current view for display.

[Visual Basic]
Return View(products)

[Visual C#]
16 Lab Instructions: Using ADO.NET

return View(products);

Task 5: Implement the Details controller action


1. Review the task list.
2. Locate the Details method by double-clicking the first comment TODO: Create a new instance of
the ProductDataAccessLayer in the task list. The purpose of this method is to retrieve the details of
the product that has the product ID that is specified by the user.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
4. Locate the comment TODO: Get the product specified by id.
5. Remove the comment and replace it with a statement that creates a ProductDataObject object
named product. Initialize this object with the value returned by the dal.GetProduct method, passing
the value of id as a parameter to this method.
6. Locate the comment TODO: Return the Details view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the product object to the current view for display.

[Visual Basic]
Return View(product)

[Visual C#]
return View(product);

Task 6: Test the data access layer by using the Web application
1. Start the Test Application application.
Windows Internet Explorer starts and displays the Test Application page.
2. On the Test Application page, click Browse all Products.
Internet Explorer displays a list of the products in the AdventureWorks database.
3. Click Home to return to the first page.
4. In the Browse Products by Color box, type Silver and then click Browse.
Internet Explorer displays a list of silver products.
5. Click Details adjacent to the first product.
Internet Explorer displays the details of the selected product.
6. Click Home.
7. In the Browse Products by Maximum Price box, type 100 and then click Browse.
Internet Explorer displays a list of products where the list price is not greater than 100.
8. Close Internet Explorer, and then return to Visual Studio.
9. Close the solution.
Lab Instructions: Using ADO.NET 17

Exercise 3: Enabling Data Modifications


Scenario
In this exercise, you will modify the data access layer to use an ADO.NET DataSet to maintain product and
list price history data to enable employees to query, update, and filter product information. You will use a
Windows Forms application to consume the data and to verify that the results are returned as expected.
The data access layer will include full concurrency checking and use an appropriate strategy to handle
conflicting updates.

You will be provided with a skeleton version of the data access layer and will re-implement it to use a
typed DataSet class. You will also be provided with a Windows Forms application that provides a simple
user interface (UI). You will add the functionality that invokes the methods in the data access layer. You
will also run multiple instances of this Windows Forms application simultaneously to verify that the
concurrency checking in the data access layer works as expected.
The main tasks for this exercise are as follows:
1. Open the starter project for this exercise.
2. Create the ProductDataSet DataSet.
3. Implement the GetProductList method.
4. Implement the GetProduct method.
5. Implement the overloaded GetProductList methods.
6. Implement the UpdateProduct method.
7. Implement the DeleteProduct method.
8. Modify the test application.
9. Test the data access layer by using the test application.

Task 1: Open the starter project for this exercise


Open the solution, Using ADO.NET.sln, in the E:\Labfiles\Lab14\VB\Ex3\Starter\Using ADO.NET or
E:\Labfiles\Lab14\CS\Ex3\Starter\Using ADO.NET folder. This solution contains a new version of the
data access layer code that you will implement by using DataSets and a Windows Forms application
that you will use to test the data access layer code.

Task 2: Create the ProductDataSet DataSet


1. Add a new DataSet class named ProductDataSet to the DAL project.
2. Add the Product(Production) table in the AdventureWorks database to the ProductDataSet.xsd file.
3. Add the ProductListPriceHistory(Production) table to the ProductDataSet.xsd file.
4. Save the ProductDataSet.xsd file, and then close it.
5. Review the ProductDataProvider class.

This class is a singleton that you can use to construct a ProductDataSet DataSet in a controlled
manner.

Task 3: Implement the GetProductList method


1. Review the task list.
2. Open the ProductDataAccessLayer file by double-clicking the first comment TODO: Add code to
return a list of all products. This task is located in the GetProductList method.
18 Lab Instructions: Using ADO.NET

3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable class.

The ProductDataAccessLayer class provides a table adapter called productDataAdapter that


you can use to connect to the AdventureWorks database and retrieve product information. Use
the Fill method of the productDataAdapter object.

b. Create a new instance of a generic list of ProductDataObject objects, named products.


Iterate through the DataRow collection in the ProductDataProvider.DataSet.Product
DataTable and add the product found in each row to the products collection.
The ProductDataAccessLayer class contains a helper method called
BusinessObjectFromDataRow. You can use this method to construct a ProductDataObject
object from a DataRow object that contains the data for a product. You pass the DataRow
object as the parameter, and the method returns a ProductDataObject object.

c. Return the products collection.

Task 4: Implement the GetProduct method


1. Review the task list.
2. In the Task View window, locate the comment TODO: Add code to return a single product.
Double-click this comment to go to the GetProduct method.
3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable.
b. Retrieve the DataRow object for the product from the ProductDataProvider.DataSet.Product
DataTable object by using the FindByProductID method of this DataTable object, passing the
productID variable as a parameter.

The FindByProductID method was generated by the Dataset Designer.


c. If the DataRow object is not null, construct a ProductDataObject object from the DataRow
object by using the BusinessObjectFromDataRow helper method, and then return this
ProductDataObject object. Otherwise, return a null value.

Task 5: Implement the overloaded GetProductList methods


1. Review the task list.
2. In the Task View window, locate the comment TODO: Add code to return a list of products
matching a given color. Double-click this comment to go to the GetProductList method that
retrieves products that match a specified color.
3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable.
b. Create a new instance of a generic list of ProductDataObject objects, named products.
c. Define a LINQ to DataSet query that retrieves all rows from the
ProductDataProvider.DataSet.Product DataTable where the Color column is not null and
matches the color that is specified as the parameter to this method.
d. Iterate through the results returned by the LINQ to DataSet query and add each product found
to the products collection. Use the BusinessObjectFromDataRow helper method to convert
each DataRow object to a ProductDataObject object.
Lab Instructions: Using ADO.NET 19

e. Return the products collection.


4. In the Task View window, locate the comment TODO: Add code to return a list of products
filtered on list price. Double-click this comment to go to the GetProductList method that retrieves
products that have a list price not exceeding the price that is specified as the parameter to this
method.
5. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable object.
b. Create a new instance of a generic list of ProductDataObject objects, named products.
c. Define a LINQ to DataSet query that retrieves all rows from the
ProductDataProvider.DataSet.Product DataTable object where the list price is not greater
than the value that is specified as the parameter to this method.
d. Iterate through the results returned by the LINQ to DataSet query and add each product found
to the products collection. Use the BusinessObjectFromDataRow helper method to convert
each DataRow object to a ProductDataObject object.
e. Return the products collection.

Task 6: Implement the UpdateProduct method


1. Review the task list.
2. In the Task View window, locate the comment TODO: Add code to update the product in the
database. Double-click this comment to go to the UpdateProduct method.
3. Review the code in the UpdateProduct method.
Most of the code for this method has already been provided for you.
This method updates a product in the Product table. If the product list price has changed, a new row
is added to the ProductListPriceHistory table. This table maintains a list of all changes to a product's
list price. If the update to the Product or ProductListPriceHistory tables fails, the transaction is
rolled back. If a concurrency exception occurs, it is rethrown to the client application.
4. Remove the comment and replace it with code that performs the following tasks:
a. Invoke the productAdapter.Update method. Pass row as the parameter.
b. If the updatePrice variable is true, call the listPriceAdapter.Update method to update the list
price history. You should use the listPriceAdapter.Update method and pass the
ProductDataProvider.DataSet.ProductListPriceHistory DataTable object as the parameter.

Task 7: Implement the DeleteProduct method


1. Review the task list.
2. In the Task View window, locate the comment TODO: Add code to delete a product from the
database. Double-click this comment to go to the DeleteProduct method.
3. Review the code in the DeleteProduct method.
Most of the code for this method has already been provided for you.
This deletes a product from the Product table along with any related rows in the
ProductListPriceHistory table. If the updates to the Product or ProductListPriceHistory tables fail,
the transaction is automatically rolled back. If a concurrency exception occurs, it is rethrown to the
client application.
4. Remove the comment and replace it with code that performs the following tasks:
20 Lab Instructions: Using ADO.NET

a. Create a variable of type DataRelation named relation that refers to the relationship between
the Product DataTable object and the ProductListPriceHistory DataTable object. You can find
this relationship in the ChildRelations collection of the Product DataTable object. It has the
name "FK_ProductListPriceHistory_Product_ProductID".

When the code deletes a product, it will automatically delete all related price history rows to
prevent any referential integrity problems. This strategy is known as a cascading delete.
b. Create a DataRow variable called row that refers to the product being deleted in the Product
DataTable. You can locate this product by using the FindByProductID method of the Product
DataTable.

c. Find all child rows in the ProductListPriceHistory DataTable object for the product identified
by the row table, and store a reference to these child rows in the toRemove DataRow array. You
can find these child rows by using the GetChildRows method of row object; specify relation as
the parameter to this method.

d. Iterate through the rows in the toRemove array and mark them for deletion.

e. If there is at least one row in the toRemove array, delete all marked ProductListPriceHistory
rows from the database. Use the Update method of the listPriceAdapter object and provide the
toRemove array as the parameter to this method.

f. Locate the DataRow object for the product to be deleted in the Product DataTable object and
mark it for deletion.

g. Delete the product from the database. Use the Update method of the productAdapter
TableAdapter and specify the Product DataTable as the parameter.
h. Commit the transaction and set the success variable to true. Note that if either of the Update
methods fail, they will throw an exception, the transaction will be automatically rolled back, and
the success variable will remain at its default value of false.
5. Build the project and correct any errors.

Task 8: Modify the test application


1. Review the task list.
2. Open the Form1 file by double-clicking the comment TODO: Add code to get a product with a
given product ID. This task is located in the btnSearchByProduct_Click method.
3. Remove the comment and replace it with code to create a new ProductDataObject object named
prod. Initialize the prod object with the value returned by the dal.GetProduct method, passing the
prodID variable as a parameter.
4. Locate the comment TODO: Add code to get a list of products matching a given color.
5. Remove the comment and replace it with code to set the value of the products variable by calling the
dal.GetProductList method. Use the txtColor.Text property as the parameter.
6. Locate the comment TODO: Add code to get a list of products filtered by list price.
7. Remove the comment and replace it with code to set the value of the products variable by calling the
dal.GetProductList method and passing the maxPrice variable as a parameter.
8. Locate the comment TODO: Add code to update products. This task is located in the
btnSave_Click method and is run when the user clicks Save Changes on the form.
Lab Instructions: Using ADO.NET 21

9. Remove the comment and replace it with code to call the dal.UpdateProduct method. The
UpdateProduct method expects a ProductDataObject object as a parameter; use the object created
for each iteration of the enclosing loop.
10. Locate the comment TODO: Add code to delete products.
11. Remove the comment and replace it with code to invoke the dal.DeleteProduct method. The
DeleteProduct method expects a ProductDataObject object as a parameter; use the object created
for each iteration of the enclosing loop.
12. Build the project and correct any errors.

Task 9: Test the data access layer by using the test application
1. Start the Test Application application.
2. In the Data Set Test Application window, click Load all Products. This should display a list of all of
the products in the data grid.
3. In the Data Set Test Application window, in the Product ID box, type 316 and then click Search By
Product.
4. In the Data Set Test Application window, in the data grid, in the Color box, type Gold and then click
Save Changes.
5. In the Data Set Test Application window, in the Color box, type Gold and then click Search By Color.
You should see only one product listed in the data grid.
6. Select the product and delete it.
7. Verify that the product has been deleted.
8. Close the solution.
Lab Instructions: Using LINQ to SQL 1

Module 15
Lab Instructions: Using LINQ to SQL
Contents:
Exercise 1: Using LINQ to SQL to Build a Data Access Layer 4
Exercise 2: Updating a Database by Using a Stored Procedure 8
Exercise 3: Building a Custom Entity Class 10
2 Lab Instructions: Using LINQ to SQL

Lab: Using LINQ to SQL

Objectives
After completing this lab, you will be able to:

Use LINQ to SQL to build a data access layer.


Incorporate a stored procedure into a LINQ to SQL model.
Create a LINQ to SQL entity class that implements custom logic for presenting and validating data.

Introduction
In this lab, you will develop a data access layer by using LINQ to SQL to fetch and manage data. You will
use the O/R Designer to implement the object model. You will modify the object model to use a stored
procedure to update data. Finally, you will create a custom entity class that provides additional logic for
displaying and validating data.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:

Start the 10265A-GEN-DEV-15 virtual machine, and then log on by using the following credentials:
User name: Student
Password: Pa$$w0rd
Lab Instructions: Using LINQ to SQL 3

Lab Scenario

You decide that, instead of using ADO.NET for the applications that you developed in the previous lab,
you should use a LINQ to SQL object model to decouple the database structure from the logical data
model that your application uses.
4 Lab Instructions: Using LINQ to SQL

Exercise 1: Using LINQ to SQL to Build a Data Access Layer


Scenario
In this exercise, you will use the O/R Designer to create a LINQ to SQL object model based on the
Product table in the AdventureWorks database. You will modify the data access layer to use this object
model rather than ADO.NET code. You will test the modified version of the data access layer by using the
Web and Windows Forms applications that you used in the previous lab.

The main tasks for this exercise are as follows:


1. Prepare the AdventureWorks database for the lab.
2. Open the starter project for this exercise.
3. Create the LINQ to SQL object model.
4. Implement the methods that retrieve data in the data access layer.
5. Implement the methods that update data in the data access layer.
6. Test the data access layer.

Task 1: Prepare the AdventureWorks database for the lab


1. Log on to the 10265A-GEN-DEV-15 virtual machine as Student with the password Pa$$w0rd.
2. Run AWReset.bat in the E:\Labfiles folder.

Task 2: Open the starter project for this exercise


1. Open Visual Studio 2010.
2. Open the existing solution, Using ADO.NET.sln, in the
E:\Labfiles\Lab15\CS\Ex1\Starter\Using ADO.NET or E:\Labfiles\Lab15\VB\Ex1\Starter\Using ADO.NET
folder.

Task 3: Create the LINQ to SQL object model


1. Add a LINQ to SQL object model called ProductDataModel.dbml to the DAL solution.
2. Use Server Explorer to create a connection to the AdventureWorks database on the 10265A-GEN-
DEV\SQLEXPRESS computer.
3. Add the Product (Production) entity to the LINQ to SQL object model.
4. In the O/R Designer, rename the Product entity as ProductDataObject.
5. Remove all of the columns from the ProductDataObject entity except for the following:
ProductID
Name
ProductNumber
Color
ListPrice
ModifiedDate
6. Examine the code that the O/R Designer generates.

Note: If there is no option to expand ProductDataModel.dbml, ensure that the Show All Files button is
switched on in Solution Explorer.
Lab Instructions: Using LINQ to SQL 5

Task 4: Implement the methods that retrieve data in the data access layer
1. In Solution Explorer, in the DAL project, open the ProductDataAccessLayer code file.
2. In the IProductDataAccessLayer Members region, implement the GetProductList method to use
LINQ to SQL to retrieve all products from the database, populate a list of ProductDataObject
objects, and then return this list.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object. This is a custom


DataContext object that automatically connects to the AdventureWorks database.

Hint: Create the ProductDataModelDataContext object in a using statement and add the remaining
code in this method to the body of the using statement. This will ensure that the
ProductDataModelDataContext object is correctly disposed and its resources released when the
method completes.

b. Retrieve the ProductDataObjects collection from the ProductDataModelDataContext object,


convert it into a list of ProductDataObject objects, and then return this list.
3. Implement the GetProduct method. Use LINQ to SQL to retrieve the product with the product ID
that matches the value that is passed as a parameter to this method, and then return this product.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Using the ProductDataObjects collection in the ProductDataModelDataContext object, find
the product that matches the product ID that is specified as the parameter, and then return it.
4. Implement the GetProductList method that takes a string parameter called color. Use LINQ to SQL to
retrieve a list of products with the specified color.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Define a LINQ query that finds all of the products that have the specified color from the
ProductDataObjects collection in the ProductDataModelDataContext object. The color is
specified as a string, so perform a case-insensitive match.
c. Retrieve all of the matching products into a list, and then return this list.
5. Implement the GetProductList method that takes a decimal parameter called maxListPrice. Use LINQ
to SQL to retrieve a list of products that have a list price that is less than or equal to this value.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Define a LINQ query that finds all of the products that have a price that falls within the specified
range from the ProductDataObjects collection in the ProductDataModelDataContext object.
c. Retrieve all of the matching products into a list, and then return this list.

Task 5: Implement the methods that update data in the data access layer
1. At the top of the ProductDataAccessLayer code file, bring the System.Data.Linq namespace into
scope.
2. Implement the UpdateProduct method. This method updates the database with the product
information passed in as the parameter. The method returns true if the update is successful;
otherwise, it returns false.
6 Lab Instructions: Using LINQ to SQL

The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Retrieve the existing data for the product from the database from the ProductDataObjects
collection of the ProductDataModelDataContext object.
c. If this product still exists in the database, overwrite the data in the product entity object that you
have just retrieved with the data in the product object passed in as the method parameter. This
action causes the changes to be recorded and tracked by the ProductDataObjects collection in
the ProductDataModelDataContext object used to retrieve the product.

If the product has been removed from the database, throw an exception with the message "The
product has already been deleted. Reload the product list."

Hint: Use the Single extension method of the ProductDataObjects collection to find the product in the
collection. This method throws an InvalidOperationException exception if no matching product is
found.

d. Using the ProductDataModelDataContext object, save the updated product back to the
database. Specify that the transaction should roll back the first time that a concurrency conflict is
detected.
e. If a ChangeConflictException exception occurs, iterate through all of the change conflict errors
that are reported, resolve them by overwriting the changes made by the user with the latest data
from the database, and then rethrow the exception.
f. If the update is successful, return the value true. Otherwise, return the value false.
3. Implement the DeleteProduct method. This method removes the product passed in as the parameter
from the database. The method returns true if the deletion is successful; otherwise, it returns false.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Retrieve the existing data for the product from the database from the ProductDataObjects
collection of the ProductDataModelDataContext object.
c. If this product still exists in the database, mark the product for deletion in the
ProductDataObjects collection.

If the product has already been removed from the database, throw an exception with the
message "The product has already been deleted. Reload the product list."

d. Using the ProductDataModelDataContext object, save the updated product back to the
database. Specify that the transaction should roll back the first time that a concurrency conflict is
detected.
e. If a ChangeConflictException exception occurs, iterate through all of the change conflict errors
that are reported, resolve them by forcibly deleting the conflicting rows from the database, and
then rethrow the exception.
f. If the deletion is successful, return the value true; otherwise, return the value false.
4. Build the solution and correct any errors.

Task 6: Test the data access layer


1. Start the test application that is provided with the solution in Debug mode. This application is the
Web application that you used in the previous lab.
2. In the test application, click Browse all Products. Verify that the Products screen appears displaying a
list of products from the database.
Lab Instructions: Using LINQ to SQL 7

This operation uses the GetProductList method in the data access layer.
3. Click the Details link adjacent to a product, and then verify that the details for that product are
displayed.
This operation uses the GetProduct method in the data access layer.
4. Click the Home link.
5. In the Browse Products by Color box, type Black and then click Browse. Verify that a list of black
products is displayed.
This operation uses the overloaded GetProductList method that takes a string parameter in the data
access layer.
6. Click the Home link.
7. In the Browse Products by Maximum Price box, type 25 and then click Browse. Verify that a list of
products that have a list price less than or equal to 25 is displayed.
This operation uses the overloaded GetProductList method that takes a decimal parameter in the
data access layer.
8. Close the test application and return to Visual Studio.
9. Start the Windows Forms test application in Debug mode. This application is the Windows Forms
application that you used in the previous lab.
10. In the Data Set Test Application window, click Load All Products. Verify that the data grid is
populated with the details of products from the database.
This operation uses the GetProductList method in the data access layer.
11. In the Product ID box, type 316 and then click Search By Product. Verify that the details of product
316 are displayed.
This operation uses the GetProduct method in the data access layer.
12. In the Color box, type Black and then click Search By Color. Verify that a list of black products is
displayed. This should be the same list of products that was displayed by the Web application earlier.
This operation uses the overloaded GetProductList method that takes a string parameter in the data
access layer.
13. In the Maximum Price box, type 25 and then click Search By Price. Verify that a list of products with
a price less than or equal to 25 is displayed.
This operation uses the overloaded GetProductList method that takes a decimal parameter in the
data access layer.
14. Click Delete Selected to remove the first item displayed in the data grid.
15. Change the Color field of any other product displayed in the data grid.
16. Click Save Changes. This button saves the changes to the database and then redisplays the entire list
of products. Verify that the changes are saved and no exceptions are thrown.
This operation uses the UpdateProduct and DeleteProduct methods in the data access layer.
17. Close the Data Set Test Application window, and then return to Visual Studio.
18. The DAL Unit Tests project contains the same unit tests that you used in the previous lab. Run all of
the unit tests and verify that they all pass.
19. Close the solution.
8 Lab Instructions: Using LINQ to SQL

Exercise 2: Updating a Database by Using a Stored Procedure


Scenario
In this exercise, you will modify the object model to use a stored procedure to update the Product table
in the database. You will configure the ProductDataObject entity class to invoke this stored procedure
when the DataContext object submits changes to the database.

The main tasks for this exercise are as follows:


1. Create the database objects for this exercise.
2. Open the starter project for this exercise.
3. Use a stored procedure to update data.
4. Test the data access layer.

Task 1: Create the database objects for this exercise


1. Open the CustomUpdateProcedure.sql file in the
E:\Labfiles\Lab15\CS\Ex2\SQL or E:\Labfiles\Lab15\VB\Ex2\SQL folder.
This script adds a new table called ProductChangeHistory to the AdventureWorks database, and
creates a stored procedure called productUpdateProduct. This stored procedure takes parameters
that correspond to the columns in the Product entity class and uses them to update the Product
table in the database. The stored procedure also adds a row to the ProductChangeHistory table,
recording an audit trail of changes made to products.

2. Run the script. Connect to the 10265A-GEN-DEV\SQLExpress SQL Server instance when prompted.
3. Close the CustomUpdateProcedure.sql file.

Task 2: Open the starter project for this exercise


Open the existing solution, Using ADO.NET.sln, in the
E:\Labfiles\Lab15\CS\Ex2\Starter\Using ADO.NET or E:\Labfiles\Lab15\VB\Ex2\Starter\Using ADO.NET
folder.
This solution is a copy of the completed solution from Exercise 1.

Task 3: Use a stored procedure to update data


1. Add the productUpdateProduct stored procedure in the AdventureWorks database to the LINQ to
SQL object model.
This action adds a method called productUpdateProduct to the DataContext object for the LINQ to
SQL object model that an application can use to run the stored procedure.
2. Modify the ProductDataObject entity class to use the productUpdateProduct stored procedure to
update data instead of using a SQL UPDATE statement by performing the following tasks:
Configure the Update property of the ProductDataObject entity class and customize the update
behavior to use the stored procedure.
Map the current values of the fields in the ProductDataObject entity class to the parameters
passed to the stored procedure.
3. Build the solution and correct any errors.
Lab Instructions: Using LINQ to SQL 9

Task 4: Test the data access layer


1. Open the Transact-SQL editor, and then connect to the 10265A-GEN-DEV\SQLExpress SQL Server
instance.
2. Query the Production.ProductChangeHistory table in the AdventureWorks database and verify that
it contains no rows.
3. Start the Windows Forms test application in Debug mode.
4. In the Data Set Test Application window, click Load All Products.
5. Change the Color field of any other product displayed in the data grid.
6. Click Save Changes. Verify that the changes are saved and no exceptions are thrown.
7. Close the Data Set Test Application window, and then return to Visual Studio.
8. In the Transact-SQL Editor window, requery the Production.ProductChangeHistory table in the
AdventureWorks database. The data displayed should include the original data for the product that
you changed (the value in the Color column should be the original color).
9. Close the solution.
10 Lab Instructions: Using LINQ to SQL

Exercise 3: Building a Custom Entity Class


Scenario
In this exercise, you will replace the LINQ to SQL object model with a custom version of the
ProductDataObject entity class that incorporates additional functionality for displaying error information
when it is used as part of a user interface. The ProductDataObject class will implement the
IDataErrorInfo interface, which provides custom error information that a user interface can bind to.

The main tasks for this exercise are as follows:


1. Open the starter project for this exercise.
2. Create the entity class.
3. Test the entity class.

Task 1: Open the starter project for this exercise


Open the existing solution, Using ADO.NET.sln, in the
E:\Labfiles\Lab15\CS\Ex3\Starter\Using ADO.NET or E:\Labfiles\Lab15\VB\Ex3\Starter\Using ADO.NET
folder.
This solution contains a version of the data access layer that was not built by using an entity class or
DataContext object that the O/R Designer generated. The methods in the data access layer use an
ordinary DataContext object to manage collections of ProductDataObject entity objects. The
ProductDataObject entity class has not yet been defined. You will implement this class in this
exercise.

Task 2: Create the entity class


1. Add a new class called ProductDataObject to the DAL project.
2. At the top of the ProductDataObject code file, bring the System.Data.Linq.Mapping and
System.ComponentModel namespaces into scope.
3. Mark the ProductDataObject class as an entity class that is associated with the
AdventureWorks.Production.Product table.
4. Make the ProductDataObject class public, and then specify that it implements the IDataErrorInfo
interface.
5. In the ProductDataObject class, add the following private fields:
a. An integer field called productID.
b. A string field called name.
c. A string field called productNumber.
d. A string field called color.
e. A decimal field called listPrice.
f. A DateTime field called modifiedDate.
6. Add a property called ProductID that provides read and write access to the productID field. Mark
this property as a primary-key column that does not allow null values. The name of the column in the
database is ProductID.
7. Add a property called Name that provides read and write access to the name field. In the get
accessor, return the value of name in uppercase. Mark this property as a column that does not allow
null values. The name of the column in the database is Name.
Lab Instructions: Using LINQ to SQL 11

8. Add a property called ProductNumber that provides read and write access to the productNumber
field. Mark this property as a column that does not allow null values. The name of the column in the
database is ProductNumber.
9. Add a property called Color that provides read and write access to the color field. In the get
accessor, return the value of color in uppercase if it is not null, but return a null value otherwise. In the
set accessor, if the value specified is not null, convert it to uppercase before assigning it to the color
field; otherwise, assign an empty string to the color field. Mark the property as a column that allows
null values. The name of the column in the database is Color.
10. Add a property called ListPrice that provides read and write access to the listPrice field. Mark the
property as a column that does not allow null values. The name of the column in the database is
ListPrice.
11. Add a property called ModifiedDate that provides read and write access to the modifiedDate field.
Mark this property as a column that does not allow null values. The name of the column in the
database is ModifiedDate.
12. Add the indexer in the following code example to the ProductDataObject class.
This indexer is part of the IDataErrorInfo interface. The get accessor takes the name of a column as a
parameter, and returns a string that contains an error message if the specified column contains
invalid data.
The indexer returns an error message under the following circumstances:
If the Name column is null or empty.
If the ProductNumber column is null or empty.
If the ListPrice column is less than 10.

[Visual Basic]

<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

...

#Region "IDataErrorInfo Members"

Public Default ReadOnly Property Item(


ByVal columnName As String) As String _
Implements IDataErrorInfo.Item

Get

If (columnName = "Name") AndAlso


[String].IsNullOrEmpty(Name) Then

Return "Name cannot be null"

End If

If (columnName = "ProductNumber") AndAlso


[String].IsNullOrEmpty(ProductNumber) Then

Return "Product Number cannot be null"

End If

If (columnName = "ListPrice") AndAlso listPrice < 10 Then


12 Lab Instructions: Using LINQ to SQL

Return "List Price must be at least 10.00"

End If

Return Nothing

End Get

End Property

#End Region

End Class

[Visual C#]

[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...

#region IDataErrorInfo Members

public string this[string columnName]


{

get
{

if ((columnName == "Name") && String.IsNullOrEmpty(Name))


{
return "Name cannot be null";
}

if ((columnName == "ProductNumber") &&


String.IsNullOrEmpty(ProductNumber))
{
return "Product Number cannot be null";
}

if ((columnName == "ListPrice") && listPrice < 10)


{
return "List Price must be at least 10.00";
}

return null;
}

#endregion
}

13. Add the Error property in the following code example to the ProductDataObject class.
This property is also part of the IDataErrorInfo interface. This property is used to return an error
message for the object. The ProductDataObject class does not require this data, so the property
simply returns a null value.
Lab Instructions: Using LINQ to SQL 13

[Visual Basic]

[Table(Name = "AdventureWorks.Production.Product")]

public class ProductDataObject : IDataErrorInfo


{
...
#region IDataErrorInfo Members
...

Public ReadOnly Property [Error]() As String _


Implements IDataErrorInfo.Error
Get
Return Nothing ' Not required
End Get
End Property
#endregion
}

[Visual C#]

[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
#region IDataErrorInfo Members

...
public string Error
{
get { return null; } // Not required
}

#endregion
}

14. Build the solution and correct any errors.

Task 3: Test the entity class


1. Start the Windows Forms test application in Debug mode.
2. In the Data Set Test Application window, click Load All Products.
Verify that the Name and Color columns are converted to uppercase and that there are errors
because the List Price of several products is less than 10.
3. Modify a List Price so that it is above 10.00, and then click Save Changes. Verify that the error
disappears.
4. Change the value that you just modified back to 0.00, and then click Save Changes. Notice that the
error returns.
5. Close the Data Set Test Application window.
6. Close Visual Studio.
Lab Answer Key: Introduction to Data Access Technologies 1

Module 1
Lab Answer Key: Introduction to Data Access Technologies
Contents:
Exercise 1: Identifying Data Access Technologies 2
2 Lab Answer Key: Introduction to Data Access Technologies

Lab 1: Analyzing Data Access Scenarios


Exercise 1: Identifying Data Access Technologies
Task 1: Identify the appropriate data access technology for a customer management
application

Scenario
Adventure Works Cycles has a corporate database that contains customer information. Employees can
browse and maintain customer data, but customers only have read access to the data. Employees use a
Windows Presentation Foundation (WPF) application to access their required data and the corporate
network has no bandwidth issues. The corporate database is several years old and changes are made to
the database structure twice a year.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem:
The data that is used in this application may be required in other applications in the future, so
you decide to create the data access layer as an independent assembly that the WPF application
consumes. You will use the Entity Framework to build the data access layer to insulate the logic
and the user interface against future changes in the database structure. The entities that the data
access layer uses will be initially created in the ADO.NET Entity Data Model Designer (Entity
Designer) by importing the relevant database tables into a new model. You can then customize
and extend the model for additional business requirements. You will use Language-Integrated
Query (LINQ) to Entities, Entity SQL, or Entity Client for data access.

Task 2: Identify the appropriate data access technology for an order management
application

Scenario
Adventure Works Cycles has a requirement to enable salespeople to view and create orders during offsite
meetings and add them to the database at a later time. Therefore, the data access layer needs to copy
database content on the remote device to the server.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem:
In this scenario, there will be many users who need to access a central database from various
locations and devices. Therefore, the data access layer will again reside in a separate tier on a
server machine, enabling concurrent access by multiple client applications. The data access tier
should be modeled by using an Entity Data Model (EDM) to insulate the design from the
underlying database. You could use the mapping features of the Entity Framework to create the
database, using LINQ to Entities to access it. If salespeople require read-only data, you could
cache the data as local XML files to be read by using LINQ to XML. If salespeople need to create
orders while they are disconnected from the data access tier, you could cache data in a local
Microsoft SQL Server database and synchronize this with the server by using the Microsoft
Sync Framework when the salespeople are reconnected later.
Lab Answer Key: Introduction to Data Access Technologies 3

Task 3: Identify the appropriate data access technology for a delivery management
application

Scenario
Adventure Works Cycles has agreed to provide an ASP.NET Model-View-Controller (MVC) Web
application to delivery companies to query and maintain the delivery status of orders as they ship them.
The application has to provide fast and responsive access to the database in a potentially low-bandwidth
environment. Adventure Works Cycles has to produce a highly robust data access layer for this application
in a very compressed time scale.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem:
There will potentially be many users who need to access a central database from different
locations. Therefore, you decide to develop a data access layer that resides in a separate tier on a
server machine, allowing concurrent access by multiple client applications. The data access tier is
best implemented by using WCF Data Services. In this way, the data service can be quickly
developed and easily consumed by the Web application and any future Windows applications
that may be developed.

Task 4: Identify the appropriate data access technology for a product management
application

Scenario
Adventure Works Cycles previously developed what are now legacy applications that enable employees to
browse and maintain a list of products, and enable customers to browse a list of products. The employee
application is a Windows Forms application, and the customer application is a Web application. Both
applications were built by using the Microsoft .NET Framework 2.0 or earlier. The underlying database
structure is stable and has not been changed since it was first designed. Employees have no bandwidth
limitations, although customers may do.

1. Given the scenario above, on a piece of paper, write down what you think is the most appropriate
data access technology to solve the business problem:
Two different applications will be using the data access layer, so you decide to implement the
data access layer as a separate assembly that is distributed to both client applications. You also
decide to implement the data access layer by using ADO.NET so that it supports the existing .NET
Framework 2.0 applications and enables the data access layer to operate on the .NET Framework
4 with minimum changes. Using ADO.NET directly is also useful for building a solution that
requires the fastest possible data access to a stable database model.
2. Discuss the benefits and drawbacks of each of your solutions with one of the other students.
Lab Answer Key: Building Entity Data Models 1

Module 2
Lab Answer Key: Building Entity Data Models
Contents:
Exercise 1: Generating an EDM from AdventureWorks 2
Exercise 2: Adding Entities and Associations 4
Exercise 3: Using the Generate Database Wizard 6
Exercise 4: Mapping Entities to Multiple Tables 11
Exercise 5: Implementing an Inheritance Hierarchy 13
Exercise 6: Using Stored Procedures 16
Exercise 7: Creating a Complex Type 17
2 Lab Answer Key: Building Entity Data Models

Lab 2: Using Entity Data Models


Exercise 1: Generating an EDM from AdventureWorks
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-02 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat:

a. Click Start, and then click Computer.


b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex1\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex1\Starter\DAL folder:

a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex1\Starter\DAL folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex1\Starter\DAL folder, click DAL.sln, and then click Open.

Task 3: Create the AdventureWorks Entity Data Model


Add a new ADO.NET Entity Data Model (EDM) to the DAL project. Generate the EDM from the
AdventureWorks Microsoft SQL Server database and create entities for the Contact,
SalesOrderHeader, SalesTerritory, and StoreContact tables:
a. In Solution Explorer, right-click DAL, point to Add, and then click New Item.
b. In the Add New Item - DAL dialog box, in the templates list, click ADO.NET Entity Data Model,
in the Name box, type AdventureWorksEDM and then click Add.
c. In the Entity Data Model Wizard, on the Choose Model Contents page, click Generate from
database, and then click Next.
d. On the Choose Your Data Connection page, click New Connection.
e. In the Choose Data Source dialog box, in the Data source list, click Microsoft SQL Server, and
then click Continue.
f. In the Connection Properties dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress
g. In the Select or enter a database name box, enter AdventureWorks and then click OK.
h. On the Choose Your Data Connection page, click Next.
i. On the Choose Your Database Objects page, expand Tables, select the Contact (Person),
SalesOrderHeader (Sales), SalesTerritory (Sales), and StoreContact (Sales) check boxes, and
then click Finish.

Task 4: Review the AdventureWorks model


1. Review the four entities shown in the Entity Designer pane and the associations between them:
Lab Answer Key: Building Entity Data Models 3

a. In the Entity Designer pane, click the Contact entity, and then review the properties of the
entity.
b. Click the StoreContact entity, and then review the properties of the entity.
c. Double-click the dotted line between the Contact and StoreContact entities.
d. In the Referential Constraint dialog box, review the information, and then click Cancel.
e. Review the other entities and relationships in a similar way.

2. Open the Mapping Details pane and review the mappings for each entity in the model:
a. On the View menu, point to Other Windows, and then click Entity Data Model Mapping
Details.
b. In the Entity Designer pane, click an entity, and in the Mapping Details pane, review the
mappings for the entity.

3. Close the Entity Designer pane:


On the File menu, click Close.

Task 5: Modify the SalesTerritory entity by using XML


1. Open the AdventureWorksEDM EDM in the XML Editor:
a. In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open With.
b. In the Open With - AdventureWorksEDM.edmx dialog box, click XML (Text) Editor, and then
click OK.

2. Locate the conceptual schema definition language (CSDL) section of the model:

a. On the Edit menu, point to Find and Replace, and then click Quick Find.
b. In the Find and Replace dialog box, in the Find what box, type CSDL and then click Find Next.
c. Close the Find and Replace dialog box.

3. Locate the SalesTerritory entity in the CSDL section of the model:


Scroll through the CSDL section until you find the following line of code.

<EntityType Name="SalesTerritory">

4. Find the Name property, and then change its Name attribute to TerritoryName:
a. Scroll through the CSDL section until you find the following line of code.

<Property Name="Name" Type="String" ...>

b. Change the Name attribute to TerritoryName. Your code should resemble the following code
example.

<Property Name="TerritoryName" Type="String" ...>

5. Save the model and close the XML Editor window:

a. On the File menu, click Save AdventureWorksEDM.edmx.


b. On the File menu, click Close.
6. Open AdventureWorksEDM.edmx in the Entity Designer pane, verify that the change has been
made, and then change the property back to Name:

a. In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.


4 Lab Answer Key: Building Entity Data Models

b. Examine the SalesTerritory entity and verify that the second property in the list is now called
TerritoryName.
c. Right-click TerritoryName, and then click Rename.
d. Type Name and then press ENTER.

7. Save and close the solution:

a. On the File menu, click Save All.


b. On the File menu, click Close Solution.

Exercise 2: Adding Entities and Associations


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex2\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex2\Starter\DAL folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex2\Starter\DAL folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex2\Starter\DAL folder, click DAL.sln, and then click Open.

Task 2: Add the Reward entity


1. Open the AdventureWorksEDM model in the Entity Designer pane:
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. Add a new entity named Reward with a Key Property name of RewardID and an EntitySet name of
Reward to the model:
a. In the Entity Designer pane, right-click anywhere in the white space, point to Add, and then
click Entity.
b. In the Add Entity dialog box, in the Entity name box, type Reward
c. In the Entity Set box, type Reward
d. In the Property name box, type RewardID and then click OK.

3. Add the scalar properties described in the following table to the entity.

Property name Type Scale Nullable Default Value

RewardType String not available True AW

RewardName String not available False (None)

NumberOfAirMilesRequired Int32 not available True (None)

PointsPerAirMile Int32 not available True (None)

Destination String not available True (None)

MoneyBackPerPoint Decimal 2 True (None)

NumberOfPointsRequired Int32 not available True (None)

Product String not available True (None)


Lab Answer Key: Building Entity Data Models 5

For each property, perform the following steps:


i. In the Reward entity, right-click the entity heading, point to Add, and then click Scalar
Property.
ii. Type the property name, and then press ENTER.
iii. Use the Properties pane to set the Type, Scale, Nullable, and Default Value properties.

Task 3: Add the RewardsClaimed entity


1. Add a new entity named RewardsClaimed with a Key Property name of ClaimID and an Entity Set
name of RewardsClaimed to the model:

a. In the Entity Designer pane, right-click anywhere in the white space, point to Add, and then
click Entity.
b. In the Add Entity dialog box, in the Entity name box, type RewardsClaimed
c. In the Entity Set box, type RewardsClaimed
d. In the Property name box, type ClaimID and then click OK.

2. Add the scalar property described in the following table to the entity.

Property name Type

PointsUsed Int32

For the PointsUsed property, perform the following steps:


i. In the RewardsClaimed entity, right-click the entity heading, point to Add, and then click
Scalar Property.
ii. Type the property name, and then press ENTER.
iii. Use the Properties pane to set the Type property.

Task 4: Add the Reward entity to the RewardsClaimed association


1. Add a one-to-many association between the Reward and RewardsClaimed entities:

a. In the Reward entity, right-click the entity heading, point to Add, and then click Association.
b. In the Add Association dialog box, review the default values, and then click OK.

2. In the RewardsClaimed entity, rename the new RewardRewardID property to RewardID:

a. In the RewardsClaimed entity, right-click RewardRewardID, and then click Rename.


b. Type RewardID and then press ENTER.

3. In the Reward entity, rename the RewardsClaimeds navigation property to RewardsClaimed:

a. In the Reward entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.

Task 5: Add the Contact entity to the RewardsClaimed association


1. Add a one-to-many association between the Contact and RewardsClaimed entities:

a. In the Contact entity, right-click the entity heading, point to Add, and then click Association.
b. In the Add Association dialog box, change the right End Entity to RewardsClaimed, and then
click OK.
2. Rename the new ContactContactID property in the RewardsClaimed entity to ContactID:
6 Lab Answer Key: Building Entity Data Models

a. In the RewardsClaimed entity, right-click ContactContactID, and then click Rename.


b. Type ContactID and then press ENTER.

3. Rename the Navigation Property in the Contact entity to RewardsClaimed:


a. In the Contact entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.

4. Save and close the solution:

a. On the File menu, click Save All.


b. On the File menu, click Close Solution.

Exercise 3: Using the Generate Database Wizard


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex3\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex3\Starter\DAL folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex3\Starter\DAL folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex3\Starter\DAL folder, click DAL.sln, and then click Open.

Task 2: Modify the Database Schema Name property


1. Open the AdventureWorksEDM model in the Entity Designer:
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.

Note: The Error List shows that the Reward and RewardsClaimed entities are not mapped. This
is because you created the entities in the last exercise and have not yet mapped them to any
database objects. You will resolve this error later in this exercise.

2. Change the Database Schema Name property to Sales:

a. In the Entity Designer pane, click anywhere in the white space.


b. In the Properties pane, click the Database Schema Name property, type Sales and then press
ENTER.

Task 3: Generate Transact-SQL script of the model


Run the Generate Database Wizard to script the AdventureWorksEDM model:
a. In the Entity Designer pane, right-click anywhere in the white space, and then click Generate
Database from Model.
b. In the Generate Database Wizard window, review the generated script, and then click Finish.
c. In the SSDL/MSL Overwrite Warning dialog box, click Yes.

Task 4: Modify the generated script


1. Remove the Dropping existing FOREIGN KEY constraints section from the script:
Lab Answer Key: Building Entity Data Models 7

a. In the script window, locate the comment that reads Dropping existing FOREIGN KEY
constraints.
b. Select all of the code from the beginning of the comment to the final GO in the section, and then
press DELETE.

2. Remove the Dropping existing tables section from the script:

a. In the script window, locate the comment that reads Dropping existing tables.
b. Select all of the code from the beginning of the comment to the final GO in the section, and then
press DELETE.

3. Remove the code that creates the following existing tables in the database: Contacts,
SalesOrderHeaders, SalesTerritories, and StoreContacts:

a. In the script window, locate the comment that reads Creating table 'Contacts'.
b. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
c. In the script window, locate the comment that reads Creating table 'SalesOrderHeaders'.
d. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
e. In the script window, locate the comment that reads Creating table 'SalesTerritories'.
f. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
g. In the script window, locate the comment that reads Creating table 'StoreContacts'.
h. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
4. Remove the code that creates the existing primary keys on the following tables in the database:
Contacts, SalesOrderHeaders, SalesTerritories, and StoreContacts:

a. In the script window, locate the comment that reads Creating primary key on [ContactID] in
table 'Contacts'.
b. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
c. In the script window, locate the comment that reads Creating primary key on [SalesOrderID]
in table 'SalesOrderHeaders'.
d. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
e. In the script window, locate the comment that reads Creating primary key on [TerritoryID] in
table 'SalesTerritories'.
f. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
g. In the script window, locate the comment that reads Creating primary key on [CustomerID],
[ContactID] in table 'StoreContacts'.
h. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.

5. Remove the code that creates the existing foreign keys on the following tables in the database:
SalesOrderHeaders and StoreContacts:

a. In the script window, locate the comment that reads Creating foreign key on [ContactID] in
table 'SalesOrderHeaders'.
b. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
c. In the script window, locate the comment that reads Creating foreign key on [ContactID] in
table 'StoreContacts'.
d. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
e. In the script window, locate the comment that reads Creating foreign key on [TerritoryID] in
table 'SalesOrderHeaders'.
f. Select all of the code from the beginning of the comment to the next GO, and then press DELETE.
6. Modify the code that creates the foreign key on [ContactID] in the RewardsClaimed table to
change the schema for the Contact table to Person and to singularize the table name to Contact:
8 Lab Answer Key: Building Entity Data Models

a. In the script window, locate the comment that reads Creating foreign key on [ContactID] in
table 'RewardsClaimed'.
b. Modify the line that reads REFERENCES [Sales].[Contacts] to read REFERENCES
[Person].[Contact].

At this point, your code should resemble the following code example.

[Visual Basic]
-- --------------------------------------------------
-- Date Created: 01/19/2010 08:38:09
-- Generated from EDMX file:
E:\Labfiles\Lab02\VB\Ex3\Starter\DAL\DAL\AdventureWorksEDM.edmx
-- --------------------------------------------------

SET QUOTED_IDENTIFIER OFF;


GO
USE [AdventureWorks]
GO
IF SCHEMA_ID(N'Sales') IS NULL EXECUTE(N'CREATE SCHEMA [Sales]')
GO

-- --------------------------------------------------
-- Creating all tables
-- --------------------------------------------------

-- Creating table 'Reward'


CREATE TABLE [Sales].[Reward] (
[RewardID] int NOT NULL,
[RewardType] nvarchar(max) NULL,
[RewardName] nvarchar(max) NOT NULL,
[NumberOfAirMilesRequired] int NULL,
[PointsPerAirMile] int NULL,
[Destination] nvarchar(max) NULL,
[MoneyBackPerPoint] decimal(18,2) NULL,
[NumberOfPointsRequired] int NULL,
[Product] nvarchar(max) NULL
);
GO
-- Creating table 'RewardsClaimed'
CREATE TABLE [Sales].[RewardsClaimed] (
[ClaimID] int NOT NULL,
[PointsUsed] int NOT NULL,
[RewardID] int NOT NULL,
[ContactID] int NOT NULL
);
GO

-- --------------------------------------------------
-- Creating all PRIMARY KEY Constraints
-- --------------------------------------------------

-- Creating primary key on [RewardID] in table 'Reward'


ALTER TABLE [Sales].[Reward]
ADD CONSTRAINT [PK_Reward]
PRIMARY KEY CLUSTERED ([RewardID] ASC);
GO
-- Creating primary key on [ClaimID] in table 'RewardsClaimed'
ALTER TABLE [Sales].[RewardsClaimed]
ADD CONSTRAINT [PK_RewardsClaimed]
PRIMARY KEY CLUSTERED ([ClaimID] ASC);
GO

-- --------------------------------------------------
Lab Answer Key: Building Entity Data Models 9

-- Creating all FOREIGN KEY Constraints


-- --------------------------------------------------

-- Creating foreign key on [RewardID] in table 'RewardsClaimed'


ALTER TABLE [Sales].[RewardsClaimed]
ADD CONSTRAINT [FK_RewardRewardsClaimed]
FOREIGN KEY ([RewardID])
REFERENCES [Sales].[Reward]
([RewardID])
ON DELETE NO ACTION ON UPDATE NO ACTION;
-- Creating non-clustered index for FOREIGN KEY 'FK_RewardRewardsClaimed'
CREATE INDEX [IX_FK_RewardRewardsClaimed]
ON [Sales].[RewardsClaimed]
([RewardID]);
GO
-- Creating foreign key on [ContactID] in table 'RewardsClaimed'
ALTER TABLE [Sales].[RewardsClaimed]
ADD CONSTRAINT [FK_ContactRewardsClaimed]
FOREIGN KEY ([ContactID])
REFERENCES [Person].[Contact]
([ContactID])
ON DELETE NO ACTION ON UPDATE NO ACTION;
-- Creating non-clustered index for FOREIGN KEY 'FK_ContactRewardsClaimed'
CREATE INDEX [IX_FK_ContactRewardsClaimed]
ON [Sales].[RewardsClaimed]
([ContactID]);
GO

-- --------------------------------------------------
-- Script has ended
-- --------------------------------------------------

[Visual C#]
-- --------------------------------------------------
-- Date Created: 01/19/2010 08:38:09
-- Generated from EDMX file:
E:\Labfiles\Lab02\CS\Ex3\Starter\DAL\DAL\AdventureWorksEDM.edmx
-- --------------------------------------------------

SET QUOTED_IDENTIFIER OFF;


GO
USE [AdventureWorks]
GO
IF SCHEMA_ID(N'Sales') IS NULL EXECUTE(N'CREATE SCHEMA [Sales]')
GO

-- --------------------------------------------------
-- Creating all tables
-- --------------------------------------------------

-- Creating table 'Reward'


CREATE TABLE [Sales].[Reward] (
[RewardID] int NOT NULL,
[RewardType] nvarchar(max) NULL,
[RewardName] nvarchar(max) NOT NULL,
[NumberOfAirMilesRequired] int NULL,
[PointsPerAirMile] int NULL,
[Destination] nvarchar(max) NULL,
[MoneyBackPerPoint] decimal(18,2) NULL,
[NumberOfPointsRequired] int NULL,
[Product] nvarchar(max) NULL
10 Lab Answer Key: Building Entity Data Models

);
GO
-- Creating table 'RewardsClaimed'
CREATE TABLE [Sales].[RewardsClaimed] (
[ClaimID] int NOT NULL,
[PointsUsed] int NOT NULL,
[RewardID] int NOT NULL,
[ContactID] int NOT NULL
);
GO

-- --------------------------------------------------
-- Creating all PRIMARY KEY Constraints
-- --------------------------------------------------

-- Creating primary key on [RewardID] in table 'Reward'


ALTER TABLE [Sales].[Reward]
ADD CONSTRAINT [PK_Reward]
PRIMARY KEY CLUSTERED ([RewardID] ASC);
GO
-- Creating primary key on [ClaimID] in table 'RewardsClaimed'
ALTER TABLE [Sales].[RewardsClaimed]
ADD CONSTRAINT [PK_RewardsClaimed]
PRIMARY KEY CLUSTERED ([ClaimID] ASC);
GO

-- --------------------------------------------------
-- Creating all FOREIGN KEY Constraints
-- --------------------------------------------------

-- Creating foreign key on [RewardID] in table 'RewardsClaimed'


ALTER TABLE [Sales].[RewardsClaimed]
ADD CONSTRAINT [FK_RewardRewardsClaimed]
FOREIGN KEY ([RewardID])
REFERENCES [Sales].[Reward]
([RewardID])
ON DELETE NO ACTION ON UPDATE NO ACTION;
-- Creating non-clustered index for FOREIGN KEY 'FK_RewardRewardsClaimed'
CREATE INDEX [IX_FK_RewardRewardsClaimed]
ON [Sales].[RewardsClaimed]
([RewardID]);
GO
-- Creating foreign key on [ContactID] in table 'RewardsClaimed'
ALTER TABLE [Sales].[RewardsClaimed]
ADD CONSTRAINT [FK_ContactRewardsClaimed]
FOREIGN KEY ([ContactID])
REFERENCES [Person].[Contact]
([ContactID])
ON DELETE NO ACTION ON UPDATE NO ACTION;
-- Creating non-clustered index for FOREIGN KEY 'FK_ContactRewardsClaimed'
CREATE INDEX [IX_FK_ContactRewardsClaimed]
ON [Sales].[RewardsClaimed]
([ContactID]);
GO

-- --------------------------------------------------
-- Script has ended
-- --------------------------------------------------

7. Validate and then execute the script against the 10265A-GEN-DEV


\SQLExpress database engine:

a. On the Data menu, point to Transact-SQL Editor, and then click Validate SQL Syntax.
Lab Answer Key: Building Entity Data Models 11

b. In the Connect to Database Engine dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress and then click Connect.
c. Verify that the validation succeeded.
d. On the Data menu, point to Transact-SQL Editor, and then click Execute SQL.
e. Verify that the script executed successfully.

8. Build the solution:


On the Build menu, click Build Solution.

Note: The errors in the Error List have been cleared and the solution builds successfully because the
Reward and RewardsClaimed entities are now mapped to the tables that you have just created.

9. Save and close the solution:

a. On the File menu, click Save All.


b. On the File menu, click Close Solution.

Exercise 4: Mapping Entities to Multiple Tables


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex4\Starter\DAL or
E:\Labfiles\Lab02\CS\Ex4\Starter\DAL folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex4\Starter\DAL folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex4\Starter\DAL folder, click DAL.sln, and then click Open.

Task 2: Create the InactiveStoreContact table


1. Open the InactiveStoreContact.sql script, in the E:\Labfiles\Lab02\VB\Ex4\Starter or
E:\Labfiles\Lab02\CS\Ex4\Starter folder:
a. On the File menu, point to Open, and then click File.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex4\Starter folder, click InactiveStoreContact.sql, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex4\Starter folder, click InactiveStoreContact.sql, and then click Open.
2. Validate and then execute the script against the 10265A-GEN-DEV
\SQLExpress database engine:

a. On the Data menu, point to Transact-SQL Editor, and then click Validate SQL Syntax.
b. In the Connect to Database Engine dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress and then click Connect.
c. Verify that the validation succeeded.
d. On the Data menu, point to Transact-SQL Editor, and then click Execute SQL.
e. Verify that the script executed successfully.
f. On the File menu, click Close.

Task 3: Update the model from the database


1. Open the AdventureWorksEDM model in the Entity Designer:
12 Lab Answer Key: Building Entity Data Models

In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.


2. Update the model from the database to add the InactiveStoreContacts table to the model:

a. In the Entity Designer pane, right-click anywhere in the white space, and then click Update
Model from Database.
b. In the Update Wizard window, expand Tables, select the InactiveStoreContact (Sales) check
box, and then click Finish.

Note: Due to an issue with the prerelease version of the Entity Designer, the mappings for the original
table have been lost. To resolve this issue, it is necessary to delete the model from the project, re-create
the model, and re-create any default values.

3. Delete the model from the project:


a. In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Delete.
b. In the Microsoft Visual Studio dialog box, click OK.

4. Add a new ADO.NET EDM named AdventureWorksEDM.edmx to the DAL project. Generate the
data model from the AdventureWorks database, and create entities for the Contact,
InactiveStoreContact, Reward, RewardsClaimed, SalesOrderHeader, SalesTerritory, and
StoreContact tables:

a. In Solution Explorer, right-click DAL, point to Add, and then click New Item.
b. In the Add New Item - DAL dialog box, in the templates list, click ADO.NET Entity Data Model,
in the Name box, type AdventureWorksEDM and then click Add.
c. In the Entity Data Model Wizard, on the Choose Model Contents page, click Generate from
database, and then click Next.
d. On the Choose Your Data Connection page, click Next.
e. On the Choose Your Database Objects page, expand Tables, select the Contact (Person),
InactiveStoreContact (Sales), Reward (Sales), RewardsClaimed (Sales), SalesOrderHeader
(Sales), SalesTerritory (Sales), and StoreContact (Sales) check boxes, and then click Finish.

5. Change the Database Schema Name property to Sales:


a. In the Entity Designer pane, click anywhere in the white space.
b. In the Properties pane, click the Database Schema Name property, type Sales and then press
ENTER.
6. Rename the Navigation Property in the Reward entity to RewardsClaimed:

a. In the Reward entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.

7. Rename the Navigation Property in the Contact entity to RewardsClaimed:

a. In the Contact entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.

Task 4: Map the StoreContact entity to the InactiveStoreContact table


1. Open the Mapping Details pane, and then map the StoreContact entity to the
InactiveStoreContact table:
Lab Answer Key: Building Entity Data Models 13

a. In the Entity Designer pane, click the StoreContact entity.


b. On the View menu, point to Other Windows, and then click Entity Data Model Mapping
Details.
c. In the Mapping Details pane, review the mappings for the entity.
d. In the Mapping Details pane, click <Add a Table or View>, and then in the drop-down list,
click InactiveStoreContact.
e. Review the automatic column mappings that have occurred because the column names in the
entity and the table are the same.

2. In the Entity Designer pane, delete the InactiveStoreContact entity:


In the Entity Designer pane, in the InactiveStoreContact entity, right-click the entity heading,
and then click Delete.
3. Build the solution:
On the Build menu, click Build Solution.
4. Save and close the solution:

a. On the File menu, click Save All.


b. On the File menu, click Close Solution.

Exercise 5: Implementing an Inheritance Hierarchy


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the
E:\Labfiles\Lab02\VB\Ex5\Starter\ DAL or E:\Labfiles\Lab02\CS\Ex5\Starter\DAL folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex5\Starter\DAL folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex5\Starter\DAL folder, click DAL.sln, and then click Open.

Task 2: Add the AirMilesReward entity


1. Open AdventureWorksEDM in the Entity Designer pane:
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. Add a new entity named AirMilesReward based on the Reward entity:

a. In the Entity Designer pane, right-click anywhere in the white space, point to Add, and then
click Entity.
b. In the Add Entity dialog box, in the Entity name box, type AirMilesReward
c. In the Base type list, click Reward, and then click OK.

Task 3: Add the SupermarketReward entity


Add a new entity named SupermarketReward based on the Reward entity:
a. In the Entity Designer pane, right-click anywhere in the white space, point to Add, and then
click Entity.
b. In the Add Entity dialog box, in the Entity name box, type SupermarketReward
c. In the Base type list, click Reward, and then click OK.
14 Lab Answer Key: Building Entity Data Models

Task 4: Add the AdventureWorksReward entity


Add a new entity named AdventureWorksReward based on the Reward entity:
a. In the Entity Designer pane, right-click anywhere in the white space, point to Add, and then
click Entity.
b. In the Add Entity dialog box, in the Entity name box, type AdventureWorksReward
c. In the Base type list, click Reward, and then click OK.

Task 5: Map the new entities to the Reward table


1. Open the Mapping Details pane, and then map the AirMilesReward entity to the Reward table:

a. In the Entity Designer pane, click the AirMilesReward entity.


b. On the View menu, point to Other Windows, and then click Entity Data Model Mapping
Details.
c. In the Mapping Details pane, click <Add a Table or View>, and then in the drop-down list,
click Reward.
2. In the Mapping Details pane, map the SupermarketReward entity to the Reward table:

a. In the Entity Designer pane, click the SupermarketReward entity.


b. In the Mapping Details pane, click <Add a Table or View>, and then in the drop-down list,
click Reward.

3. In the Mapping Details pane, map the AdventureWorksReward entity to the Reward table:
a. In the Entity Designer pane, click the AdventureWorksReward entity.
b. In the Mapping Details pane, click <Add a Table or View>, and then in the drop-down list,
click Reward.

Task 6: Add conditions to the mappings


1. Add a condition to the AirMilesReward mapping to specify that this entity should contain rewards
only if the RewardType field is AM:

a. In the Entity Designer pane, click the AirMilesReward entity.


b. In the Mapping Details pane, click <Add a Condition>, and then in the drop-down list, click
RewardType.
c. In the Value / Property column, click <Empty String>, type AM and then press ENTER.

2. Add a condition to the SupermarketReward mapping to specify that this entity should contain
rewards only if the RewardType field is SM:

a. In the Entity Designer pane, click the SupermarketReward entity.


b. In the Mapping Details pane, click <Add a Condition>, and then in the drop-down list, click
RewardType.
c. In the Value / Property column, click <Empty String>, type SM and then press ENTER.

3. Add a condition to the AdventureWorksReward mapping to specify that this entity should contain
rewards only if the RewardType field is AW:

a. In the Entity Designer pane, click the AdventureWorksReward entity.


b. In the Mapping Details pane, click <Add a Condition>, and then in the drop-down list, click
RewardType.
c. In the Value / Property column, click <Empty String>, type AW and then press ENTER.

4. Remove the RewardType property from the Reward entity:


Lab Answer Key: Building Entity Data Models 15

a. In the Entity Designer pane, click the Reward entity.


b. In the Properties list, right-click RewardType, and then click Delete.

5. Make the Reward entity abstract:


a. In the Entity Designer pane, click the Reward entity.
b. In the Properties pane, click the Abstract property, and then in the drop-down list, click True.

Task 7: Assign properties to the new entities


1. Move the NumberOfAirMilesRequired property to the AirMilesReward entity:

a. In the Entity Designer pane, click the Reward entity.


b. In the Properties list, right-click the NumberOfAirMilesRequired property, and then click Cut.
c. Right-click the AirMilesReward entity, and then click Paste.
2. Move the PointsPerAirMile property to the AirMilesReward entity:

a. In the Entity Designer pane, click the Reward entity.


b. In the Properties list, right-click the PointsPerAirMile property, and then click Cut.
c. Right-click the AirMilesReward entity, and then click Paste.
3. Move the Destination property to the AirMilesReward entity:

a. In the Entity Designer pane, click the Reward entity.


b. In the Properties list, right-click the Destination property, and then click Cut.
c. Right-click the AirMilesReward entity, and then click Paste.

4. Move the MoneyBackPerPoint property to the SupermarketReward entity:


a. In the Entity Designer pane, click the Reward entity.
b. In the Properties list, right-click the MoneyBackPerPoint property, and then click Cut.
c. Right-click the SupermarketReward entity, and then click Paste.
5. Move the NumberOfPointsRequired property to the AdventureWorksReward entity:

a. In the Entity Designer pane, click the Reward entity.


b. In the Properties list, right-click the NumberOfPointsRequired property, and then click Cut.
c. Right-click the AdventureWorksReward entity, and then click Paste.
6. Move the Product property to the AdventureWorksReward entity:

a. In the Entity Designer pane, click the Reward entity.


b. In the Properties list, right-click the Product property, and then click Cut.
c. Right-click the AdventureWorksReward entity, and then click Paste.

7. Map the AirMilesReward properties to the appropriate columns in the Reward table:
a. In the Entity Designer pane, click the AirMilesReward entity.
b. In the Mapping Details pane, map the NumberOfAirMilesRequired, PointsPerAirMile, and
Destination properties to the matching columns in the database.

8. Map the SupermarketReward properties to the appropriate columns in the Reward table:

a. In the Entity Designer pane, click the SupermarketReward entity.


b. In the Mapping Details pane, map the MoneyBackPerPoint property to the matching column
in the database.
9. Map the AdventureWorksReward properties to the appropriate columns in the Reward table:

a. In the Entity Designer pane, click the AdventureWorksReward entity.


16 Lab Answer Key: Building Entity Data Models

b. In the Mapping Details pane, map the NumberOfPointsRequired and the Product properties
to the matching columns in the database.

10. Build the solution:


On the Build menu, click Build Solution.
11. Save and close the solution:
a. On the File menu, click Save All.
b. On the File menu, click Close Solution.

Exercise 6: Using Stored Procedures


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex6\Starter or
E:\Labfiles\Lab02\CS\Ex6\Starter folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex6\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex6\Starter folder, click DAL.sln, and then click Open.

Task 2: Add the uspCountOrders stored procedure to the model


1. Open AdventureWorksEDM in the Entity Designer pane:
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. Run the Update Wizard to add the uspCountOrders stored procedure to the model:

a. In the Entity Designer pane, right-click anywhere in the white space, and then click Update
Model from Database.
b. In the Update Wizard window, on the Choose Your Data Connection page, click Next.
c. In the Update Wizard window, on the Choose Your Database Objects page, expand Stored
Procedures, select the uspCountOrders (Sales) check box, and then click Finish.

Note: Four errors will appear in the Error List because you have updated the model from the database
and the mappings for the inheritance hierarchy that you created in Exercise 5 have been lost. These errors
will not impact this exercise, so you can ignore the errors and continue with the next task.

Task 3: Add a function import to the model


1. Add a function import named CountOrders to the model. Map it to the uspCountOrders stored
procedure and configure it to return a collection of scalar Int32 data types:

a. In the Entity Designer pane, right-click anywhere in the white space, point to Add, and then
click Function Import.
b. In the Add Function Import dialog box, in the Function Import Name box, type CountOrders
c. In the Stored Procedure Name list, click uspCountOrders.
d. Click Get Column Information.
e. Under Returns a Collection Of, click Scalars, in the type list, click Int32, and then click OK.

2. Build the solution:


On the Build menu, click Build Solution.
Lab Answer Key: Building Entity Data Models 17

3. Save and close the solution:

a. On the File menu, click Save All.


b. On the File menu, click Close Solution.

Exercise 7: Creating a Complex Type


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex7\Starter or
E:\Labfiles\Lab02\CS\Ex7\Starter folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\VB\Ex7\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab02\CS\Ex7\Starter folder, click DAL.sln, and then click Open.

Task 2: Create the complex type


1. Open AdventureWorksEDM in the Entity Designer pane:
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. In the SalesTerritory entity, create a complex type named LocationType from the Name,
CountryRegionCode, and Group properties:

a. In the Entity Designer pane, click the SalesTerritory entity, and then select all of the following
properties: Name, CountryRegionCode, and Group.
b. Right-click the selected properties, and then click Refactor into New Complex Type.
c. In the Model Browser pane, under Complex Types, type LocationType and then press ENTER.
3. Change the name of the new complex property in the SalesTerritory entity to Location:
a. In the SalesTerritory entity, in the Properties list, right-click ComplexProperty, and then click
Rename.
b. Type Location and then press ENTER.

4. Build the solution:


On the Build menu, click Build Solution.
5. Save and close the solution, and then close Visual Studio:
a. On the File menu, click Save All.
b. On the File menu, click Exit.
Lab Answer Key: Querying Entity Data 1

Module 3
Lab Answer Key: Querying Entity Data
Contents:
Exercise 1: Retrieving All Contact Entities 2
Exercise 2: Retrieving Contact Entities by Using a Filter 5
Exercise 3: Retrieving RewardsClaimed Entities 9
Exercise 4: Querying the Rewards Family of Entities 13
Exercise 5: Executing a Stored Procedure 17
2 Lab Answer Key: Querying Entity Data

Lab 3: Querying Entity Data


Exercise 1: Retrieving All Contact Entities
Task 1: Prepare the Adventure Works database for the lab
1. Log on to the 10265A-GEN-DEV-03 virtual machine as Student with the password Pa$$w0rd.
2. Run AWReset.bat in the E:\Labfiles folder:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\VB\Ex1\Starter or
E:\Labfiles\Lab03\CS\Ex1\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\VB\Ex1\Starter folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\CS\Ex1\Starter folder, click DAL.sln, and then click Open.

Task 3: Add code to retrieve all of the contacts


1. In Visual Studio, review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Retrieve all
contacts item in the task list. This task is located in the first GetContactList method:
In the task list, double-click the TODO: Ex1 - Retrieve all contacts item.
3. Delete the existing code in the GetContactList method:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should
perform all data access operations by using this context object.

b. Create and define a Language-Integrated Query (LINQ) to Entities query to select all Contact
entities.
c. Execute the query with MergeOption set to NoTracking.
d. Return the results.
Lab Answer Key: Querying Entity Data 3

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Returns a list of all the Contact entities from the database.
''' </summary>
''' <returns></returns>
Public Function GetContactList() As List(Of Contact) _
Implements IDataAccessLayer.GetContactList

' Check we have an ObjectContext


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

' Create a query from the entityset


Dim contacts As ObjectQuery(Of Contact) = entities.Contacts

' Create detached objects


contacts.MergeOption = MergeOption.NoTracking

' Define the query


Dim query = From c In contacts
Select c

' Execute the query


Dim results As List(Of Contact) = query.ToList()

' Return the results in a List


Return results

End Function

[Visual C#]
/// <summary>
/// Returns a list of all the Contact entities from the database.
/// </summary>
/// <returns></returns>
public List<Contact> GetContactList()
{
// Check we have an ObjectContext
if (entities == null) entities = new AdventureWorksEntities();

// Create a query from the entityset


ObjectQuery<Contact> contacts = entities.Contacts;

// Create detached objects


contacts.MergeOption = MergeOption.NoTracking;

// Define the query


var query = from c in contacts
select c;

// Execute the query


List<Contact> results = query.ToList();

// Return the results in a List


return results;
}

5. Save the DataAccessLayer code file:


4 Lab Answer Key: Querying Entity Data

a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 4: Add a unit test to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex1 - Create a unit
test for GetContactList item in the task list. This task is located in the GetContactListTest method:
In the task list, double-click the TODO: Ex1 - Create a unit test for GetContactList item.
3. Delete the existing code in the GetContactListTest method:
Select the code in the method, and then press DELETE.
4. Write a unit test to compare the first 10 contacts returned by your query with the 10 contacts
returned by the GetLocalCustomerListFirstTen method. Be sure to release all resources at the end
of the test.
Your code should resemble the following code example.

[Visual Basic]

'''<summary>
'''A test for GetContactList
'''</summary>

<TestMethod()> _
Public Sub GetContactListTest()

Dim dal As New DataAccessLayer()


Dim expected As List(Of Contact) = _
Me.GetLocalCustomerListFirstTen()
Dim actual As List(Of Contact) = dal.GetContactList()

For i As Integer = 0 To 9

Assert.AreEqual(expected(i).ContactID, actual(i).ContactID)
Assert.AreEqual(expected(i).Title, actual(i).Title)
Assert.AreEqual(expected(i).FirstName, actual(i).FirstName)
Assert.AreEqual(expected(i).MiddleName, actual(i).MiddleName)
Assert.AreEqual(expected(i).LastName, actual(i).LastName)
Assert.AreEqual(expected(i).EmailAddress,
actual(i).EmailAddress)
Assert.AreEqual(expected(i).Phone, actual(i).Phone)
Assert.AreEqual(expected(i).CurrentPoints,
actual(i).CurrentPoints)

Next i

dal.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for GetContactList
Lab Answer Key: Querying Entity Data 5

///</summary>
[TestMethod()]
public void GetContactListTest()
{
DataAccessLayer dal = new DataAccessLayer();
List<Contact> expected = this.GetLocalCustomerListFirstTen();
List<Contact> actual = dal.GetContactList();

for (int i = 0; i < 10; i++)


{
Assert.AreEqual(expected[i].ContactID, actual[i].ContactID);
Assert.AreEqual(expected[i].Title, actual[i].Title);
Assert.AreEqual(expected[i].FirstName, actual[i].FirstName);
Assert.AreEqual(expected[i].MiddleName, actual[i].MiddleName);
Assert.AreEqual(expected[i].LastName, actual[i].LastName);
Assert.AreEqual(expected[i].EmailAddress,
actual[i].EmailAddress);
Assert.AreEqual(expected[i].Phone, actual[i].Phone);
Assert.AreEqual(expected[i].CurrentPoints,
actual[i].CurrentPoints);
}

dal.Dispose();
}

5. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 5: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that the application functions as expected.
4. Close the application.
5. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed, including GetContactListTest.
7. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Retrieving Contact Entities by Using a Filter


Task 1: Open the starter project for this exercise
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\VB\Ex2\Starter or
E:\Labfiles\Lab03\CS\Ex2\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
6 Lab Answer Key: Querying Entity Data

b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\VB\Ex2\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\CS\Ex2\Starter folder, click DAL.sln, and then click Open.

Task 2: Add code to retrieve contacts by last name


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - Retrieve contacts
by last name item in the task list. This task is located in the second GetContactList method:
In the task list, double-click the TODO: Ex2 - Retrieve contacts by last name item.
3. Delete the existing code in the GetContactList method:
Select the code in the method, and then press DELETE.
4. Add code to the method that performs the following tasks:
a. Instantiate the entities context object if it is currently null.
b. Create and define a LINQ to Entities query to retrieve Contact entities by last name.
c. Execute the query with MergeOption set to NoTracking.
d. Return the results.

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Retrieve all contacts with a specified last name
''' </summary>
''' <param name="name">Name to search for</param>
''' <returns>List of contacts</returns>
Public Function GetContactList(ByVal name As String) As List(Of Contact) _
Implements IDataAccessLayer.GetContactList

' Check we have an ObjectContext


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

' Create a query from the entityset


Dim contacts As ObjectQuery(Of Contact) = entities.Contacts

' Create detached objects


contacts.MergeOption = MergeOption.NoTracking

' Define the query


Dim query = From c In contacts
Where c.LastName = name
Select c
' Execute the query
Dim results As List(Of Contact) = query.ToList()

' Return the results in a List


Return results

End Function
Lab Answer Key: Querying Entity Data 7

[Visual C#]
/// <summary>
/// Retrieve all contacts with a specified last name
/// </summary>
/// <param name="name">Name to search for</param>
/// <returns>List of contacts</returns>
public List<Contact> GetContactList(string name)
{
// Check we have an ObjectContext
if (entities == null) entities = new AdventureWorksEntities();

// Create a query from the entityset


ObjectQuery<Contact> contacts = entities.Contacts;

// Create detached objects


contacts.MergeOption = MergeOption.NoTracking;

// Define the query


var query = from c in contacts
where c.LastName == name
select c;

// Execute the query


List<Contact> results = query.ToList();

// Return the results in a List


return results;
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 3: Add a unit test to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex2 - Create a unit
test for GetContactList by last name item in the task list. This task is located in the
GetAContactListTest method:
In the task list, double-click the TODO: Ex2 - Create a unit test for GetContactList by last
name item.
3. Delete the existing code in the GetAContactListTest method:
Select the code in the method, and then press DELETE.
4. Write a unit test to compare the first contacts returned by your query, using Adina as the last name
parameter, with the contacts returned by the GetLocalCustomerList method. Be sure to release all
resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

'''<summary>
'''A test for GetContactList
'''</summary>
8 Lab Answer Key: Querying Entity Data

<TestMethod()> _
Public Sub GetAContactListTest()

Dim dal As New DataAccessLayer()


Dim name As String = "Adina"
Dim expected As List(Of Contact) = Me.GetLocalCustomerList()
Dim actual As List(Of Contact) = dal.GetContactList(name)

For i As Integer = 0 To expected.Count - 1

Assert.AreEqual(expected(i).ContactID, actual(i).ContactID)
Assert.AreEqual(expected(i).Title, actual(i).Title)
Assert.AreEqual(expected(i).FirstName, actual(i).FirstName)
Assert.AreEqual(expected(i).MiddleName, actual(i).MiddleName)
Assert.AreEqual(expected(i).LastName, actual(i).LastName)
Assert.AreEqual(expected(i).EmailAddress,
actual(i).EmailAddress)
Assert.AreEqual(expected(i).Phone, actual(i).Phone)
Assert.AreEqual(expected(i).CurrentPoints,
actual(i).CurrentPoints)

Next i
dal.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for GetContactList
///</summary>
[TestMethod()]
public void GetAContactListTest()
{
DataAccessLayer dal = new DataAccessLayer();
string name = "Adina";
List<Contact> expected = this.GetLocalCustomerList();
List<Contact> actual = dal.GetContactList(name);

for (int i = 0; i < expected.Count; i++)


{
Assert.AreEqual(expected[i].ContactID, actual[i].ContactID);
Assert.AreEqual(expected[i].Title, actual[i].Title);
Assert.AreEqual(expected[i].FirstName, actual[i].FirstName);
Assert.AreEqual(expected[i].MiddleName, actual[i].MiddleName);
Assert.AreEqual(expected[i].LastName, actual[i].LastName);
Assert.AreEqual(expected[i].EmailAddress,
actual[i].EmailAddress);
Assert.AreEqual(expected[i].Phone, actual[i].Phone);
Assert.AreEqual(expected[i].CurrentPoints,
actual[i].CurrentPoints);
}
dal.Dispose();
}

5. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.
Lab Answer Key: Querying Entity Data 9

Task 4: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. In the AdventureWorks Rewards window, in the Name box, type Ward and then click Search. Verify
that the application functions as expected and loads the correct contacts into the data grid.
4. Close the application.
5. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed, including GetAContactListTest.
7. Close the solution:
On the File menu, click Close Solution.

Exercise 3: Retrieving RewardsClaimed Entities


Task 1: Open the starter project for this exercise
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\VB\Ex3\Starter or
E:\Labfiles\Lab03\CS\Ex3\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\VB\Ex3\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\CS\Ex3\Starter folder, click DAL.sln, and then click Open.

Task 2: Add code to retrieve rewards claimed by contact ID


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex3 - Retrieve rewards
claimed by contact ID item in the task list. This task is located in the GetRewardsClaimedList
method:
In the task list, double-click the TODO: Ex3 - Retrieve rewards claimed by contact ID item.
3. Delete the existing code in the GetRewardsClaimedList method:
Select the code in the method, and then press DELETE.
4. Add code that performs the following tasks:
a. Instantiate the entities context object if it is currently null.
b. Create and define an Entity Structured Query Language (Entity SQL) query to retrieve
RewardsClaimed entities by contact ID.
c. Execute the query with MergeOption set to NoTracking.
d. Return the results.

Your code should resemble the following code example.


10 Lab Answer Key: Querying Entity Data

[Visual Basic]

''' <summary>
''' Get a list of RewardsClaimed for a contact.
''' Use Entity SQL syntax.
''' </summary>
''' <param name="contactID">ContactID to search on</param>
''' <returns>List of RewardsClaimed</returns>

Public Function GetRewardsClaimedList(ByVal contactID As Integer) As List(Of


RewardsClaimed) _
Implements IDataAccessLayer.GetRewardsClaimedList

' Check we have an ObjectContext


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

' Define the Entity SQL query


Dim queryString As String = "SELECT VALUE r from " &
"AdventureWorksEntities.RewardsClaimed as r " &
"WHERE r.ContactID=@contactID"

' Create the query


Dim query = New ObjectQuery(Of RewardsClaimed)(queryString, entities)

' Create detached objects


query.MergeOption = MergeOption.NoTracking

' Add the parameters


query.Parameters.Add(New ObjectParameter("contactID", contactID))

' Execute the query


Dim results As List(Of RewardsClaimed) = query.ToList()

' Return the results in a list


Return results

End Function

[Visual C#]

/// <summary>
/// Get a list of RewardsClaimed for a contact.
/// Use Entity SQL syntax.
/// </summary>
/// <param name="contactID">ContactID to search on</param>
/// <returns>List of RewardsClaimed</returns>

public List<RewardsClaimed> GetRewardsClaimedList(int contactID)


{

// Check we have an ObjectContext


if (entities == null) entities = new AdventureWorksEntities();

// Define the Entity SQL query


var queryString = @"SELECT VALUE r from
AdventureWorksEntities.RewardsClaimed as r
WHERE r.ContactID=@contactID";

// Create the query


Lab Answer Key: Querying Entity Data 11

var query = new ObjectQuery<RewardsClaimed>(queryString,


entities);

// Create detached objects


query.MergeOption = MergeOption.NoTracking;

// Add the parameters


query.Parameters.Add(new ObjectParameter("contactID", contactID));

// Execute the query


List<RewardsClaimed> results = query.ToList();

// Return the results in a list


return results;
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 3: Add a unit test to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex3 - Create a unit
test for GetRewardsClaimedList item in the task list. This task is located in the
GetRewardsClaimedListTest method:
In the task list, double-click the TODO: Ex3 - Create a unit test for GetRewardsClaimedList
item.
3. Delete the existing code in the GetRewardsClaimedListTest method:
Select the code in the method, and then press DELETE.
4. Write a unit test to compare the RewardsClaimed entities returned by your query, using a contactID
of 2 as the parameter value, with the RewardsClaimed entities returned by the
GetLocalRewardsClaimedList method. Be sure to release all resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

'''<summary>
'''A test for GetRewardsClaimedList
'''</summary>

<TestMethod()> _
Public Sub GetRewardsClaimedListTest()

Dim dal As New DataAccessLayer()


Dim contactID As Integer = 2
Dim expected As List(Of RewardsClaimed) = _
Me.GetLocalRewardsClaimedList()
Dim actual As List(Of RewardsClaimed) = _
dal.GetRewardsClaimedList(contactID)

For i As Integer = 0 To expected.Count - 1


12 Lab Answer Key: Querying Entity Data

Assert.AreEqual(expected(i).ClaimID, actual(i).ClaimID)
Assert.AreEqual(expected(i).PointsUsed, actual(i).PointsUsed)
Assert.AreEqual(expected(i).RewardID, actual(i).RewardID)
Assert.AreEqual(expected(i).ContactID, actual(i).ContactID)

Next i

dal.Dispose()

End Sub

[Visual C#]

/// <summary>
///A test for GetRewardsClaimedList
///</summary>

[TestMethod()]
public void GetRewardsClaimedListTest()
{

DataAccessLayer dal = new DataAccessLayer();


int contactID = 2;

List<RewardsClaimed> expected = this.GetLocalRewardsClaimedList();


List<RewardsClaimed> actual =
dal.GetRewardsClaimedList(contactID);

for (int i = 0; i < expected.Count; i++)


{
Assert.AreEqual(expected[i].ClaimID, actual[i].ClaimID);
Assert.AreEqual(expected[i].PointsUsed, actual[i].PointsUsed);
Assert.AreEqual(expected[i].RewardID, actual[i].RewardID);
Assert.AreEqual(expected[i].ContactID, actual[i].ContactID);
}

dal.Dispose();

5. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 4: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.
Then, click a contact in the data grid to display rewards claimed by that contact in the second data
grid.
4. Close the application.
Lab Answer Key: Querying Entity Data 13

5. Run all of the tests in the solution:


On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed, including GetRewardsClaimedListTest.
7. Close the solution:
On the File menu, click Close Solution.

Exercise 4: Querying the Rewards Family of Entities


Task 1: Open the starter project for this exercise
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\VB\Ex4\Starter or
E:\Labfiles\Lab03\CS\Ex4\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\VB\Ex4\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\CS\Ex4\Starter folder, click DAL.sln, and then click Open.

Task 2: Add code to retrieve reward details by reward ID


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex4 - Get a reward by
reward ID item in the task list. This task is located in the GetReward method:
In the task list, double-click the TODO: Ex4 - Get a reward by reward ID item.
3. Delete the existing code in the GetReward method:
Select the code in the method, and then press DELETE.
4. Use the EntityConnection, EntityCommand, and EntityDataReader classes to connect to the entity
model, retrieve the details of a reward by rewardID, and then return the result as a Reward entity.
Set the MergeOption of the query to NoTracking.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' Find Reward details given a RewardID.
''' Implemented using an EntityCommand.
''' </summary>
''' <param name="rewardID">RewardID to search for</param>
''' <returns>Single matching reward</returns>

Public Function GetReward(ByVal rewardID As Integer) As AdventureWorksReward _


Implements IDataAccessLayer.GetReward

' An empty AdventureWorksReward object


Dim reward As New AdventureWorksReward()

' Create the EntityConnection


Using connection As _
New EntityConnection("name=AdventureWorksEntities")
14 Lab Answer Key: Querying Entity Data

' Open the connection


connection.Open()

' Define the query


Dim queryString As String = "SELECT VALUE r FROM " &
"OfType(AdventureWorksEntities.Rewards, " &
"AdventureWorksModel.AdventureWorksReward) AS r WHERE " &
"r.RewardID=@rewardID"

' Create the EntityCommand


Using cmd = New EntityCommand(queryString, connection)

' Define the parameter


Dim param = New EntityParameter()
param.ParameterName = "rewardID"
param.Value = rewardID
cmd.Parameters.Add(param)

' Execute using an EntityReader


Using reader = _
cmd.ExecuteReader(CommandBehavior.SequentialAccess)

' Process the results - there will only


' be a single Reward
' for any RewardID
While reader.Read()

reward.RewardID = reader.GetInt32(0)
reward.RewardName = reader.GetString(1)
reward.NumberOfPointsRequired = reader.GetInt32(2)
reward.Product = reader.GetString(3)

End While

End Using

' Be sure to close the connection


connection.Close()
End Using
End Using

' Return the reward.


Return reward

End Function

[Visual C#]

/// <summary>
/// Find Reward details given a RewardID.
/// Implemented using an EntityCommand.
/// </summary>
/// <param name="rewardID">RewardID to search for</param>
/// <returns>Single matching reward</returns>

public AdventureWorksReward GetReward(int rewardID)


{
// An empty AdventureWorksReward object
AdventureWorksReward reward = new AdventureWorksReward();

// Create the EntityConnection


Lab Answer Key: Querying Entity Data 15

using (EntityConnection connection = new


EntityConnection("name=AdventureWorksEntities"))
{

// Open the connection


connection.Open();

// Define the query


var queryString = @"SELECT VALUE r FROM
OfType(AdventureWorksEntities.Rewards,
AdventureWorksModel.AdventureWorksReward) AS r WHERE
r.RewardID=@rewardID";

// Create the EntityCommand


using (EntityCommand cmd = new EntityCommand(queryString,
connection))
{
// Define the parameter
EntityParameter param = new EntityParameter();
param.ParameterName = "rewardID";
param.Value = rewardID;
cmd.Parameters.Add(param);

// Execute using an EntityReader


using (EntityDataReader reader = cmd.ExecuteReader(
CommandBehavior.SequentialAccess))
{

// Process the results - there will only


// be a single Reward
// for any RewardID

while (reader.Read())
{
reward.RewardID = reader.GetInt32(0);
reward.RewardName = reader.GetString(1);
reward.NumberOfPointsRequired =
reader.GetInt32(2);
reward.Product = reader.GetString(3);
}
}

// Be sure to close the connection


connection.Close();
}

// Return the reward.


return reward;

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 3: Add a unit test to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
16 Lab Answer Key: Querying Entity Data

b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex4 - Create a unit
test for GetReward item in the task list. This task is located in the GetRewardTest method:
In the task list, double-click the TODO: Ex4 - Create a unit test for GetReward item.
3. Delete the existing code in the GetRewardTest method:
Select the code in the method, and then press DELETE.
4. Write a unit test to compare the Reward entity returned by your query, using a rewardID of 21 as
the parameter value, with the Reward entity returned by the GetLocalRewardData method. Be sure
to release all resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]
'''<summary>
'''A test for GetReward
'''</summary>
<TestMethod()> _
Public Sub GetRewardTest()

Dim dal As New DataAccessLayer()


Dim rewardID As Integer = 21
Dim expected As AdventureWorksReward = Me.GetLocalRewardData()
Dim actual As AdventureWorksReward = dal.GetReward(rewardID)

Assert.AreEqual(expected.RewardID, actual.RewardID)
Assert.AreEqual(expected.RewardName, actual.RewardName)
Assert.AreEqual(expected.NumberOfPointsRequired,
actual.NumberOfPointsRequired)
Assert.AreEqual(expected.Product, actual.Product)

dal.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for GetReward
///</summary>
[TestMethod()]
public void GetRewardTest()
{
DataAccessLayer dal = new DataAccessLayer();
int rewardID = 21;
AdventureWorksReward expected = this.GetLocalRewardData();
AdventureWorksReward actual = dal.GetReward(rewardID);

Assert.AreEqual(expected.RewardID, actual.RewardID);
Assert.AreEqual(expected.RewardName, actual.RewardName);
Assert.AreEqual(expected.NumberOfPointsRequired, actual.NumberOfPointsRequired);
Assert.AreEqual(expected.Product, actual.Product);

dal.Dispose();
}

5. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
Lab Answer Key: Querying Entity Data 17

b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 4: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.
Click a contact in the data grid to display reward claims in the second data grid. Then, click a rewards
claim in the second data grid to display the reward details on the form.
4. Close the application.
5. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed, including GetRewardTest.
7. Close the solution:
On the File menu, click Close Solution.

Exercise 5: Executing a Stored Procedure


Task 1: Open the starter project for this exercise
Open the existing solution, in the E:\Labfiles\Lab03\VB\Ex5\Starter or E:\Labfiles\Lab03\CS\Ex5\Starter
folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\VB\Ex5\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab03\CS\Ex5\Starter folder, click DAL.sln, and then click Open.

Task 2: Add code to retrieve the number of orders that a contact has placed
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex5 - Call the
CountOrders stored procedure item in the task list. This task is located in the CountOrders method:
In the task list, double-click the TODO: Ex5 - Call the CountOrders stored procedure item.
3. Delete the existing code in the CountOrders method:
Select the code in the method, and then press DELETE.
4. Add code that performs the following tasks:
a. Instantiate the entities context object if it is currently null.
b. Invoke the CountOrders method on the context object.
c. Return the results.

Your code should resemble the following code example.


18 Lab Answer Key: Querying Entity Data

[Visual Basic]
''' <summary>
''' Find the number of orders placed by a contact.
''' Invokes a function in the entity model, which in turn
''' invokes a stored procedure.
''' </summary>
''' <param name="contactID">ContactID to get count for</param>
''' <returns>Number of orders</returns>
Public Function CountOrders(ByVal contactID As Integer) As Integer _
Implements IDataAccessLayer.CountOrders

' Check we have an ObjectContext


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

' Invoke the stored procedure via the Entity model


Return CType(entities.CountOrders(contactID).First(), Integer)

End Function

[Visual C#]
/// <summary>
/// Find the number of orders placed by a contact.
/// Invokes a function in the entity model, which in turn
/// invokes a stored procedure.
/// </summary>
/// <param name="contactID">ContactID to get count for</param>
/// <returns>Number of orders</returns>
public int CountOrders(int contactID)
{
// Check we have an ObjectContext
if (entities == null) entities = new AdventureWorksEntities();

// Invoke the stored procedure via the Entity model


return (int)entities.CountOrders(contactID).First();
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 3: Add a unit test to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex5 - Create a unit
test for CountOrders item in the task list. This task is located in the CountOrdersTest method:
In the task list, double-click the TODO: Ex5 - Create a unit test for CountOrders item.
3. Delete the existing code in the CountOrdersTest method:
Select the code in the method, and then press DELETE.
4. Write a unit test to verify that the contact with a contact ID of 2 has placed four orders.
Your code should resemble the following code example.
Lab Answer Key: Querying Entity Data 19

[Visual Basic]
'''<summary>
'''A test for CountOrders
'''</summary>
<TestMethod()> _
Public Sub CountOrdersTest()

Dim dal As New DataAccessLayer()


Dim contactID As Integer = 2
Dim expected As Integer = 4
Dim actual As Integer = dal.CountOrders(contactID)

Assert.AreEqual(expected, actual)

dal.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for CountOrders
///</summary>
[TestMethod()]
public void CountOrdersTest()
{
DataAccessLayer dal = new DataAccessLayer();
int contactID = 2;
int expected = 4;
int actual = dal.CountOrders(contactID);

Assert.AreEqual(expected, actual);
dal.Dispose();
}

5. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 4: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.
Then, click a contact in the data grid to display the number of orders placed by that contact on the
form.
4. Close the application.
5. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed, including CountOrdersTest.
7. Close Visual Studio:
20 Lab Answer Key: Querying Entity Data

On the File menu, click Exit.


Lab Answer Key: Creating, Updating, and Deleting Entity Data 1

Module 4
Lab Answer Key: Creating, Updating, and Deleting Entity
Data
Contents:
Exercise 1: Maintaining Contact and Reward Data 2
Exercise 2: Maintaining RewardsClaim Data 24
2 Lab Answer Key: Creating, Updating, and Deleting Entity Data

Lab 4: Creating, Updating, and Deleting Entity


Data
Exercise 1: Maintaining Contact and Reward Data
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-04 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\VB\Ex1\Starter or
E:\Labfiles\Lab04\CS\Ex1\Starter folder:

a. On the File menu, point to Open, and then click Project/Solution.


b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab04\VB\Ex1\Starter folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab04\CS\Ex1\Starter folder, click DAL.sln, and then click Open.

Task 3: Add code to add a new contact


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Add a new Contact in
the task list. This task is located in the AddContact method:
In the task list, double-click the comment TODO: Ex1 - Add a new Contact.
3. In the AddContact method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should
perform all data access operations by using this context object.

b. Encrypt the password in the contact passed as a parameter by calling the EncryptPassword
method.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 3

c. Set the ModifiedDate property of the contact to the current date and time.
d. Assign a globally unique identifier (GUID) to the rowguid property of the contact.
e. Add the contact to the Contacts entity set.
f. Save the changes to the database and return the new ContactID value.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.

Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' Add a new Contact.
''' </summary>
''' <param name="contact">The new Contact entity object to add.</param>
''' <returns>The ContactID of the added contact.</returns>
Public Function AddContact(ByVal contact As Contact) As Integer _
Implements IDataAccessLayer.AddContact

' Check you have an ObjectContext object.


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

Try
' Encrypt the password in the detached object.
EncryptPassword(contact)

' Set the time modified.


contact.ModifiedDate = DateTime.Now

' Create a new GUID.


contact.rowguid = Guid.NewGuid()

' Add the new contact to the entity set.


entities.AddToContacts(contact)

' Save the changes to the database.


entities.SaveChanges()

' ContactID is marked as an identity column in the model


' - so it will contain the ID of the added record.
Return contact.ContactID

Catch ex As InvalidOperationException

Throw New DALException(


"There was a problem adding the new Contact", ex)

Catch ex As UpdateException

Throw New DALException(


"There was a problem saving the new Contact to the database", ex)

End Try

End Function
[Visual C#]

/// <summary>
/// Add a new Contact.
/// </summary>
/// <param name="contact">The new Contact entity object to
/// add.</param>
4 Lab Answer Key: Creating, Updating, and Deleting Entity Data

/// <returns>The ContactID of the added contact.</returns>

public int AddContact(Contact contact)


{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try
{
// Encrypt the password in the detached object.
EncryptPassword(contact);

// Set the time modified.


contact.ModifiedDate = DateTime.Now;

// Create a new GUID.


contact.rowguid = Guid.NewGuid();

// Add the new contact to the entity set.


entities.AddToContacts(contact);

// Save the changes to the database.


entities.SaveChanges();

// ContactID is marked as an identity column in the model


// - so it will contain the ID of the added record.
return contact.ContactID;
}

catch (InvalidOperationException ex)


{
throw new DALException(
"There was a problem adding the new Contact", ex);
}
catch (UpdateException ex)
{
throw new DALException(
"There was a problem saving the new Contact to the database", ex);
}
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 4: Add code to update a contact


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Modify an existing
Contact in the task list. This task is located in the UpdateContact method:
In the task list, double-click the comment TODO: Ex1 - Modify an existing Contact.
3. In the UpdateContact method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
Lab Answer Key: Creating, Updating, and Deleting Entity Data 5

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Get the EntityKey property of the detached contact passed to the method. This detached
contact contains the modified properties.
c. Use the TryGetObjectByKey method to load the correct contact into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a
parameter.
e. Save the changes to the database and return true.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Modify an existing contact.
''' </summary>
''' <param name="contact">A detached entity object containing the details to
modify.</param>
Public Function UpdateContact(ByVal contact As Contact) As Boolean _
Implements IDataAccessLayer.UpdateContact

' Check you have an ObjectContext object.


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

Try

' Get the key of the entity you are modifying.


Dim key As EntityKey = entities.CreateEntityKey("Contacts", contact)

Dim contactToModify As Object = Nothing

' Ensure that the entity to modify is loaded.


If entities.TryGetObjectByKey(key, contactToModify) Then

' Copy all of the changes over from the


' detached contact entity.
entities.ApplyCurrentValues(key.EntitySetName, contact)

End If

' Save the changes to the database.


entities.SaveChanges()

Return True

Catch ex As InvalidOperationException

Throw New DALException("There was a problem changing the Contact", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem saving the Contact to the database",
ex)

End Try

End Function

[Visual C#]
6 Lab Answer Key: Creating, Updating, and Deleting Entity Data

/// <summary>
/// Modify an existing contact.
/// </summary>
/// <param name="contact">A detached entity object containing the details to
modify.</param>

public bool UpdateContact(Contact contact)


{

// Check you have an ObjectContext object.


if (entities == null) entities = new AdventureWorksEntities();

try
{
// Get the key of the entity you are modifying.
EntityKey key = entities.CreateEntityKey("Contacts", contact);

object contactToModify;

// Ensure that the entity to modify is loaded.

if (entities.TryGetObjectByKey(key, out contactToModify))


{
// Copy all of the changes over from the
// detached contact entity.
entities.ApplyCurrentValues(key.EntitySetName, contact);
}

// Save the changes to the database.


entities.SaveChanges();

return true;
}

catch (InvalidOperationException ex)


{
throw new DALException("There was a problem changing the Contact", ex);
}

catch (UpdateException ex)


{
throw new DALException("There was a problem saving the Contact to the database",
ex);
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 5: Add code to delete a contact


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Delete an existing
Contact in the task list. This task is located in the DeleteContact method:
In the task list, double-click the comment TODO: Ex1 - Delete an existing Contact.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 7

3. In the DeleteContact method, delete the existing code:


Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Use the contactID variable passed as a parameter to create the EntityKey of the contact to
delete.
c. Use the TryGetObjectByKey method to load the correct contact into the context.
d. Delete all of the related RewardsClaimed entities belonging to the contact from the context.
e. Delete the contact from the context.
f. Save all of the changes to the database and return true.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Delete a contact.
''' </summary>
''' <param name="contactID">The ContactID of the contact to delete.</param>
''' <returns>True if it succeeds.</returns>
Public Function DeleteContact(ByVal contactID As Integer) As Boolean _
Implements IDataAccessLayer.DeleteContact

' Check you have an ObjectContext object.


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

Try

' Get the key of the entity you are deleting.


Dim key As New EntityKey(
"AdventureWorksEntities.Contacts", "ContactID", contactID)

Dim contactToDelete As Object = Nothing

' Ensure that the entity to delete is loaded.


If entities.TryGetObjectByKey(key, contactToDelete) Then

' Delete all of the RewardsClaimed entities belonging


' to the contact.
' NOTE - an alternative would be to configure
' Cascade Delete in the EDM.
For Each rewardClaimed In
(DirectCast(contactToDelete, Contact).RewardsClaimed)

entities.DeleteObject(rewardClaimed)

Next

' Delete the contact.


entities.DeleteObject(contactToDelete)
End If

' Persist the deletions to the database.


entities.SaveChanges()

Return True
8 Lab Answer Key: Creating, Updating, and Deleting Entity Data

Catch ex As InvalidOperationException

Throw New DALException("There was a problem deleting the Contact", ex)


Catch ex As UpdateException

Throw New DALException("There was a problem deleting the Contact from the
database", ex)

End Try

End Function

[Visual C#]
/// <summary>
/// Delete a contact.
/// </summary>
/// <param name="contactID">The ContactID of the contact to
/// delete.</param>
/// <returns>True if it succeeds.</returns>
public bool DeleteContact(int contactID)
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try
{
// Get the key of the entity you are deleting.
EntityKey key = new EntityKey(
"AdventureWorksEntities.Contacts", "ContactID", contactID);

object contactToDelete;

// Ensure that the entity to delete is loaded.

if (entities.TryGetObjectByKey(key, out contactToDelete))


{
// Delete all of the RewardsClaimed entities belonging
// to the contact.
// NOTE - an alternative would be to configure
// Cascade Delete in the EDM.
foreach (var rewardClaimed in
((Contact)contactToDelete).RewardsClaimed)
{
entities.DeleteObject(rewardClaimed);
}

// Delete the contact.


entities.DeleteObject(contactToDelete);
}

// Persist the deletions to the database.


entities.SaveChanges();

return true;
}

catch (InvalidOperationException ex)


{
throw new DALException("There was a problem deleting the Contact", ex);
}
Lab Answer Key: Creating, Updating, and Deleting Entity Data 9

catch (UpdateException ex)


{
throw new DALException("There was a problem deleting the Contact from the
database", ex);
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 6: Add unit tests to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Add a test for
AddContact in the task list. This task is located in the AddContactTest method:
In the task list, double-click the comment TODO: Ex1 - Add a test for AddContact.
3. In the AddContactTest method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Add a unit test to verify the behavior of the AddContact method. Use the CreateTestContact
method to create a contact to add to the database, and use the GetContactById method to retrieve
the contact from the database. Ensure that you remove the contact and release any resources at the
end of the test.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
'''A test for AddContact
'''</summary>
<TestMethod()> _
Public Sub AddContactTest()

Dim target As New DataAccessLayer()

' Create a new contact and add it to the database.


Dim contact As Contact = CreateTestContact()
Dim contactId As Integer
contactId = target.AddContact(contact)

Dim expected As Contact = CreateTestContact()

' Get the saved contact back from the database.


Dim actual As Contact = GetContactById(contactId)

' Check the saved data


Assert.IsTrue(actual.ContactID > 0)
Assert.AreEqual(expected.Title, actual.Title)
Assert.AreEqual(expected.FirstName, actual.FirstName)
Assert.AreEqual(expected.MiddleName, actual.MiddleName)
Assert.AreEqual(expected.LastName, actual.LastName)
Assert.AreEqual(expected.EmailAddress, actual.EmailAddress)
10 Lab Answer Key: Creating, Updating, and Deleting Entity Data

Assert.AreEqual(expected.Phone, actual.Phone)
Assert.AreEqual(expected.CurrentPoints, actual.CurrentPoints)
Assert.AreNotEqual(expected.PasswordHash, actual.PasswordHash)
Assert.AreNotEqual(expected.PasswordSalt, actual.PasswordSalt)

' Tidy up.


If contactId > 0 Then
target.DeleteContact(contactId)
End If

target.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for AddContact.
///</summary>

[TestMethod()]
public void AddContactTest()
{
DataAccessLayer target = new DataAccessLayer();

// Create a new contact and add it to the database.

Contact contact = CreateTestContact();


int contactId;
contactId = target.AddContact(contact);
Contact expected = CreateTestContact();

// Get the saved contact back from the database.


Contact actual = GetContactById(contactId);

// Check the saved data

Assert.IsTrue(actual.ContactID > 0);


Assert.AreEqual(expected.Title, actual.Title);
Assert.AreEqual(expected.FirstName, actual.FirstName);
Assert.AreEqual(expected.MiddleName, actual.MiddleName);
Assert.AreEqual(expected.LastName, actual.LastName);
Assert.AreEqual(expected.EmailAddress, actual.EmailAddress);
Assert.AreEqual(expected.Phone, actual.Phone);
Assert.AreEqual(expected.CurrentPoints, actual.CurrentPoints);
Assert.AreNotEqual(expected.PasswordHash, actual.PasswordHash);
Assert.AreNotEqual(expected.PasswordSalt, actual.PasswordSalt);

// Tidy up.
if (contactId > 0)
target.DeleteContact(contactId);
target.Dispose();

5. Locate the ModifyContactTest method by double-clicking the comment TODO: Ex1 - Add a test
for ModifyContact in the task list. This task is located in the ModifyContactTest method:
In the task list, double-click the comment TODO: Ex1 - Add a test for ModifyContact.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 11

6. In the ModifyContactTest method, delete the existing code:


Select the code in the method, and then press DELETE.
7. Add a unit test to verify the behavior of the ModifyContact method. Use the CreateTestContact
method to create a contact to add to the database, which you can then modify, and use the
GetContactById method to retrieve the contact from the database. Ensure that you remove the
contact and release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
'''A test for ModifyContact
'''</summary>

<TestMethod()> _
Public Sub ModifyContactTest()

Dim target As New DataAccessLayer()


Dim contact As Contact = CreateTestContact()
Dim testId As Integer
testId = target.AddContact(contact)

' Get a detached version of this contact.


Dim expected As Contact = GetContactById(testId)

' Make some changes.


expected.ContactID = testId
expected.Title = "Mr."
expected.FirstName = "Rupert"
expected.MiddleName = "M."
expected.LastName = "Beddoes"
expected.EmailAddress = "beddoes@adventure-works.com"
expected.Phone = "453-555-0166"
expected.CurrentPoints = 1000

' Save the changes.


target.UpdateContact(expected)

' Check the results


Dim actual As Contact = GetContactById(testId)
Assert.AreEqual(expected.Title, actual.Title)
Assert.AreEqual(expected.FirstName, actual.FirstName)
Assert.AreEqual(expected.MiddleName, actual.MiddleName)
Assert.AreEqual(expected.LastName, actual.LastName)
Assert.AreEqual(expected.EmailAddress, actual.EmailAddress)
Assert.AreEqual(expected.Phone, actual.Phone)
Assert.AreEqual(expected.CurrentPoints, actual.CurrentPoints)

' Tidy up.


target.DeleteContact(testId)

target.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for ModifyContact.
///</summary>
12 Lab Answer Key: Creating, Updating, and Deleting Entity Data

[TestMethod()]

public void ModifyContactTest()


{
DataAccessLayer target = new DataAccessLayer();
Contact contact = CreateTestContact();
int testId;
testId = target.AddContact(contact);

// Get a detached version of this contact.


Contact expected = GetContactById(testId);

// Make some changes.


expected.ContactID = testId;
expected.Title = "Mr.";
expected.FirstName = "Rupert";
expected.MiddleName = "M.";
expected.LastName = "Beddoes";
expected.EmailAddress = "beddoes@adventure-works.com";
expected.Phone = "453-555-0166";
expected.CurrentPoints = 1000;

// Save the changes.


target.UpdateContact(expected);

// Check the results


Contact actual = GetContactById(testId);
Assert.AreEqual(expected.Title, actual.Title);
Assert.AreEqual(expected.FirstName, actual.FirstName);
Assert.AreEqual(expected.MiddleName, actual.MiddleName);
Assert.AreEqual(expected.LastName, actual.LastName);
Assert.AreEqual(expected.EmailAddress, actual.EmailAddress);
Assert.AreEqual(expected.Phone, actual.Phone);
Assert.AreEqual(expected.CurrentPoints, actual.CurrentPoints);

// Tidy up.
target.DeleteContact(testId);

target.Dispose();
}

8. Locate the DeleteContactTest method by double-clicking the comment TODO: Ex1 - Add a test for
DeleteContact in the task list. This task is located in the DeleteContactTest method:
In the task list, double-click the comment TODO: Ex1 - Add a test for DeleteContact.
9. In the DeleteContactTest method, delete the existing code:
Select the code in the method, and then press DELETE.
10. Add a unit test to verify the behavior of the DeleteContact method. Use the CreateTestContact
method to create a contact to add to the database, which you can then delete, and use the
GetContactById method to try to retrieve the contact from the database. Ensure that you release any
resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
'''A test for DeleteContact
'''</summary>
Lab Answer Key: Creating, Updating, and Deleting Entity Data 13

<TestMethod()> _
Public Sub DeleteContactTest()

Dim target As New DataAccessLayer()

' Add a test contact to the database.


Dim testContact As Contact = CreateTestContact()

Dim contactID As Integer = target.AddContact(testContact)

' Delete the contact.


target.DeleteContact(contactID)

' Check that it has gone.


Dim deleted As Contact = GetContactById(contactID)
Assert.IsNull(deleted)

' Tidy up.


target.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for DeleteContact.
///</summary>
[TestMethod()]
public void DeleteContactTest()
{
DataAccessLayer target = new DataAccessLayer();

// Add a test contact to the database.


Contact testContact = CreateTestContact();
int contactID = target.AddContact(testContact);

// Delete the contact.


target.DeleteContact(contactID);

// Check that it has gone.


Contact deleted = GetContactById(contactID);
Assert.IsNull(deleted);

// Tidy up.
target.Dispose();
}

11. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 7: Add code to add a new reward


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
14 Lab Answer Key: Creating, Updating, and Deleting Entity Data

2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Add a new Reward in
the task list. This task is located in the AddReward method:
In the task list, double-click the comment TODO: Ex1 - Add a new Reward.
3. In the AddReward method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

b. Get the next available RewardID value by calling the GetNextRewardID method.
c. Add the reward to the Rewards entity set.
d. Save the changes to the database and return the RewardID value.
e. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Adds a new reward.
''' </summary>
''' <param name="reward">A new entity object containing the details of the Reward to
add.</param>
''' <returns>The RewardID of the added Reward.</returns>
Public Function AddReward(ByVal reward As Reward) As Integer _
Implements IDataAccessLayer.AddReward

' Check you have an ObjectContext object.


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

Try

' Get the next available rewardID.


reward.RewardID = GetNextRewardID()
' Add the reward to the entity set.
entities.AddToRewards(reward)

' Save the changes to the database.


entities.SaveChanges()

Return reward.RewardID

Catch ex As InvalidOperationException

Throw New DALException("There was a problem adding the new Reward", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem saving the new Reward to the
database", ex)

End Try
Lab Answer Key: Creating, Updating, and Deleting Entity Data 15

End Function

[Visual C#]
/// <summary>
/// Adds a new reward.
/// </summary>
/// <param name="reward">A new entity object containing the details of the Reward to
add.</param>
/// <returns>The RewardID of the added Reward.</returns>
public int AddReward(Reward reward)
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try
{
// Get the next available rewardID.
reward.RewardID = GetNextRewardID();

// Add the reward to the entity set.


entities.AddToRewards(reward);

// Save the changes to the database.


entities.SaveChanges();

return reward.RewardID;
}
catch (InvalidOperationException ex)
{
throw new DALException("There was a problem adding the new Reward", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem saving the new Reward to the
database", ex);
}
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 8: Add code to update a reward


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Modify an existing
Reward in the task list. This task is located in the UpdateReward method:
In the task list, double-click the comment TODO: Ex1 - Modify an existing Reward.
3. In the UpdateReward method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
16 Lab Answer Key: Creating, Updating, and Deleting Entity Data

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Get the EntityKey value of the detached contact passed to the method. This detached contact
contains the modified properties.
c. Use the TryGetObjectByKey method to load the correct contact into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a
parameter.
e. Save the changes to the database and return true.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Modify an existing reward.
''' </summary>
''' <param name="reward">A detached entity object containing the details to
modify.</param>
Public Function UpdateReward(ByVal reward As Reward) As Integer _
Implements IDataAccessLayer.UpdateReward

' Check you have an ObjectContext object.


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

Try

' Get the key of the entity you are modifying.


Dim key As EntityKey = _
entities.CreateEntityKey("Rewards", reward)

Dim rewardToModify As Object = Nothing

' Ensure that the entity to modify is loaded.


If entities.TryGetObjectByKey(key, rewardToModify) Then

' Copy all of the changes over from the detached


' Reward entity.
entities.ApplyCurrentValues(key.EntitySetName, reward)

End If

' Save the changes to the database.


entities.SaveChanges()

Return True

Catch ex As InvalidOperationException

Throw New DALException("There was a problem changing the Reward", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem saving the Reward to the database",
ex)

End Try

End Function
[Visual C#]
Lab Answer Key: Creating, Updating, and Deleting Entity Data 17

/// <summary>
/// Modify an existing reward.
/// </summary>

/// <param name="reward">A detached entity object containing the details to


modify.</param>

public bool UpdateReward(Reward reward)


{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try

{
// Get the key of the entity you are modifying.
EntityKey key = entities.CreateEntityKey("Rewards", reward);

object rewardToModify;

// Ensure that the entity to modify is loaded.


if (entities.TryGetObjectByKey(key, out rewardToModify))
{
// Copy all of the changes over from the detached
// Reward entity.
entities.ApplyCurrentValues(key.EntitySetName, reward);
}

// Save the changes to the database.


entities.SaveChanges();

return true;
}
catch (InvalidOperationException ex)
{
throw new DALException("There was a problem changing the Reward", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem saving the Reward to the database",
ex);
}
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 9: Add code to delete a reward


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Delete an existing
Reward in the task list. This task is located in the DeleteReward method:
In the task list, double-click the comment TODO: Ex1 - Delete an existing Reward.
3. In the DeleteReward method, delete the existing code:
18 Lab Answer Key: Creating, Updating, and Deleting Entity Data

Select the code in the method, and then press DELETE.


4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Use the rewardID value passed as a parameter to create the EntityKey object of the reward to
delete.
c. Use the TryGetObjectByKey method to load the correct reward into the context.
d. Delete the reward from the context.
e. Save the change to the database and return true.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.

Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' Delete a reward.
''' </summary>

''' <param name="rewardID">The RewardID of the reward to delete.</param>

Public Function DeleteReward(ByVal rewardID As Integer) As Boolean _


Implements IDataAccessLayer.DeleteReward

' Check you have an ObjectContext object.


If (entities Is Nothing) Then entities = _
New AdventureWorksEntities()

Try

Dim rewardToDelete As Object = Nothing

Dim key As EntityKey = New EntityKey( _


"AdventureWorksEntities.Rewards", "RewardID", rewardID)

' Ensure that the entity to modify is loaded.


If entities.TryGetObjectByKey(key, rewardToDelete) Then

' Delete the object.


' This could fail because of referential
' integrity violations
entities.DeleteObject(rewardToDelete)

End If

Return True

Catch ex As InvalidOperationException

Throw New DALException("There was a problem deleting the Reward", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem deleting the Reward from the
database", ex)

End Try
Lab Answer Key: Creating, Updating, and Deleting Entity Data 19

End Function

[Visual C#]

/// <summary>
/// Delete a reward.
/// </summary>

/// <param name="rewardID">The RewardID of the reward to delete.</param>

public bool DeleteReward(int rewardID)


{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try
{
object rewardToDelete = null;

EntityKey key = new


EntityKey("AdventureWorksEntities.Rewards",
"RewardID", rewardID);

// Ensure that the entity to modify is loaded.


if (entities.TryGetObjectByKey(key, out rewardToDelete))
{
// Delete the object.
// This could fail because of referential
// integrity violations
entities.DeleteObject(rewardToDelete);
}
return true;
}
catch (InvalidOperationException ex)
{
throw new DALException("There was a problem deleting the Reward", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem deleting the Reward from the
database", ex);
}
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 10: Add unit tests to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Add a test for
AddReward in the task list. This task is located in the AddRewardTest method:
In the task list, double-click the comment TODO: Ex1 - Add a test for AddReward.
20 Lab Answer Key: Creating, Updating, and Deleting Entity Data

3. In the AddRewardTest method, delete the existing code:


Select the code in the method, and then press DELETE.
4. Add a unit test to verify the behavior of the AddReward method. Use the
CreateAdventureWorksRewardData and CreateSupermarketRewardData methods to create
rewards to add to the database, and use the GetRewardById method to retrieve the rewards from
the database. Ensure that you remove the rewards and release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
'''A test for AddReward
'''</summary>

<TestMethod()> _
Public Sub AddRewardTest()

Dim target As New DataAccessLayer()

' Create some test rewards


Dim reward1 As AdventureWorksReward = _
CreateAdventureWorksRewardData()
Dim reward2 As SupermarketReward = CreateSupermarketRewardData()
Dim expected1 As AdventureWorksReward = _
CreateAdventureWorksRewardData()
Dim expected2 As SupermarketReward = CreateSupermarketRewardData()

' Add the first one to the database.


Dim id As Integer
id = target.AddReward(reward1)

' Do some checks.


Assert.IsTrue(id > 0)
Dim actual As Reward = GetRewardById(id)
Assert.IsInstanceOfType(actual, GetType(AdventureWorksReward))

' Tidy up.


target.DeleteReward(id)

' Add the second one to the database.


id = target.AddReward(reward2)

' Do some tests.


Assert.IsTrue(id > 0)
actual = GetRewardById(id)
Assert.IsInstanceOfType(actual, GetType(SupermarketReward))

' Tidy up.


target.DeleteReward(id)

target.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for AddReward.
///</summary>
Lab Answer Key: Creating, Updating, and Deleting Entity Data 21

[TestMethod()]
public void AddRewardTest()
{
DataAccessLayer target = new DataAccessLayer();

// Create some test rewards


AdventureWorksReward reward1 = CreateAdventureWorksRewardData();
SupermarketReward reward2 = CreateSupermarketRewardData();
AdventureWorksReward expected1 = CreateAdventureWorksRewardData();
SupermarketReward expected2 = CreateSupermarketRewardData();

// Add the first one to the database.


int id;
id = target.AddReward(reward1);

// Do some checks.
Assert.IsTrue(id > 0);
Reward actual = GetRewardById(id);
Assert.IsInstanceOfType(actual, typeof(AdventureWorksReward));

// Tidy up.
target.DeleteReward(id);

// Add the second one to the database.


id = target.AddReward(reward2);

// Do some tests.
Assert.IsTrue(id > 0);
actual = GetRewardById(id);
Assert.IsInstanceOfType(actual, typeof(SupermarketReward));

// Tidy up.
target.DeleteReward(id);

target.Dispose();
}

5. Locate the ModifyRewardTest method by double-clicking the comment TODO: Ex1 - Add a test
for ModifyReward in the task list. This task is located in the ModifyRewardTest method:
In the task list, double-click the comment TODO: Ex1 - Add a test for ModifyReward.
6. In the ModifyRewardTest method, delete the existing code:
Select the code in the method, and then press DELETE.
7. Add a unit test to verify the behavior of the ModifyReward method. Use the
CreateSupermarketRewardData method to create a reward to add to the database, which you can
then modify, and use the GetRewardById method to retrieve the reward from the database. Ensure
that you remove the reward and release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
'''A test for ModifyReward
'''</summary>
<TestMethod()> _
Public Sub ModifyRewardTest()

Dim target As New DataAccessLayer()

' Create some test data.


22 Lab Answer Key: Creating, Updating, and Deleting Entity Data

Dim testdata As Reward = CreateSupermarketRewardData()


Dim testid As Integer = target.AddReward(testdata)

' Get a detached copy of the reward and modify.


Dim expected As SupermarketReward = _
DirectCast(GetRewardById(testid), SupermarketReward)
expected.MoneyBackPerPoint = 0.15
expected.RewardName = "Changed"

target.UpdateReward(expected)

' Get the copy back from the database.


Dim actual As SupermarketReward = _
DirectCast(GetRewardById(testid), SupermarketReward)

' Do some checks.


Assert.AreEqual(expected.MoneyBackPerPoint, _
actual.MoneyBackPerPoint)
Assert.AreEqual(expected.RewardName, actual.RewardName)

' Tidy up.


target.DeleteReward(testid)

target.Dispose()

End Sub

[Visual C#]
/// <summary>
///A test for ModifyReward.
///</summary>
[TestMethod()]
public void ModifyRewardTest()
{
DataAccessLayer target = new DataAccessLayer();

// Create some test data.


Reward testdata = CreateSupermarketRewardData();
int testid = target.AddReward(testdata);

// Get a detached copy of the reward and modify.


SupermarketReward expected =
(SupermarketReward)GetRewardById(testid);
expected.MoneyBackPerPoint = 0.15m;
expected.RewardName = "Changed";

target.UpdateReward(expected);

// Get the copy back from the database.


SupermarketReward actual =
(SupermarketReward)GetRewardById(testid);

// Do some checks.
Assert.AreEqual(expected.MoneyBackPerPoint,
actual.MoneyBackPerPoint);
Assert.AreEqual(expected.RewardName, actual.RewardName);

// Tidy up.
target.DeleteReward(testid);

target.Dispose();
Lab Answer Key: Creating, Updating, and Deleting Entity Data 23

8. Locate the DeleteRewardTest method by double-clicking the comment TODO: Ex1 - Add a test for
DeleteReward in the task list. This task is located in the DeleteRewardTest method:
In the task list, double-click the comment TODO: Ex1 - Add a test for DeleteReward.
9. In the DeleteRewardTest method, delete the existing code:
Select the code in the method, and then press DELETE.
10. Add a unit test to verify the behavior of the DeleteReward method. Use the
CreateAdventureWorksRewardData method to create a reward to add to the database, which you
can then delete. Ensure that you release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
'''A test for DeleteReward
'''</summary>

<TestMethod()> _
Public Sub DeleteRewardTest()

Dim target As New DataAccessLayer()

' Add a test reward to the database.


Dim reward As AdventureWorksReward = _
CreateAdventureWorksRewardData()
Dim rewardID As Integer = target.AddReward(reward)

' Delete the contact.


Dim actual As Boolean = target.DeleteReward(rewardID)

' Check that the delete was successful.


Assert.IsTrue(actual)

' Tidy up.


target.Dispose()

End Sub

[Visual C#]

/// <summary>
///A test for DeleteReward.
///</summary>

[TestMethod()]
public void DeleteRewardTest()
{
DataAccessLayer target = new DataAccessLayer();

// Add a test reward to the database.


AdventureWorksReward reward = CreateAdventureWorksRewardData();
int rewardID = target.AddReward(reward);

// Delete the contact.


bool actual = target.DeleteReward(rewardID);
24 Lab Answer Key: Creating, Updating, and Deleting Entity Data

// Check that the delete was successful.


Assert.IsTrue(actual);

// Tidy up.
target.Dispose();
}

11. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 11: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model
into the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add
and modify Adventure Works reward data.
6. Close the application.
7. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Maintaining RewardsClaim Data


Task 1: Open the starter project
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\Ex2\VB\Starter or
E:\Labfiles\Lab04\Ex2\CS\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab04\VB\Ex2\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab04\CS\Ex2\Starter folder, click DAL.sln, and then click Open.

Task 2: Add data modification stored procedures to your model


1. Open the AdventureWorksEDM model in the Entity Designer.
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. Run the Update Wizard to add the uspInsertRewardsClaim, uspUpdateRewardsClaim, and
uspDeleteRewardsClaim stored procedures to the model:
a. In the Entity Designer pane, right-click anywhere in the white space, and then click Update
Model from Database.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 25

b. In the Update Wizard window, on the Choose Your Database Objects page, on the Add tab,
expand Stored Procedures, select the uspInsertRewardsClaim (Sales),
uspUpdateRewardsClaim (Sales), and uspDeleteRewardsClaim (Sales) check boxes, and then
click Finish.
3. Map the RewardsClaimed entity to the stored procedures in the following table.

Function Stored Procedure

Insert uspInsertRewardsClaim

Update uspUpdateRewardsClaim

Delete uspDeleteRewardsClaim

a. In the Entity Designer pane, right-click the RewardsClaimed entity, and then click Stored
Procedure Mapping.
b. Click <Select Insert Function>, and then in the drop-down list, click uspInsertRewardsClaim.
c. Click <Select Update Function>, and then in the drop-down list, click
uspUpdateRewardsClaim.
d. Click <Select Delete Function>, and then in the drop-down list, click uspDeleteRewardsClaim.
4. Map the stored procedure parameters to the entity properties as shown in the following table.

Stored Procedure Parameter Property

uspInsertRewardsClaim claimID : int ClaimID : Int32

uspInsertRewardsClaim pointsUsed : int PointsUsed : Int32

uspInsertRewardsClaim rewardID : int RewardID : Int32

uspInsertRewardsClaim contactID : int ContactID : Int32

uspUpdateRewardsClaim claimID : int ClaimID : Int32

uspUpdateRewardsClaim pointsUsed : int PointsUsed : Int32

uspUpdateRewardsClaim rewardID : int RewardID : Int32

uspUpdateRewardsClaim contactID : int ContactID : Int32

uspDeleteRewardsClaim claimID : int ClaimID : Int32

For each parameter, perform the following steps:


i. Next to the parameter name, click the empty Property.
ii. Click the correct property value.
5. Save the AdventureWorksEDM model:
On the File menu, click Save AdventureWorksEDM.edmx.

Task 3: Add code to add a new RewardsClaim record


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
26 Lab Answer Key: Creating, Updating, and Deleting Entity Data

b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - Add a new
RewardsClaimed entity in the task list. This task is located in the CreateRewardsClaim method:
In the task list, double-click the comment TODO: Ex2 - Add a new RewardsClaimed entity.
3. In the CreateRewardsClaim method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.

b. Set the ClaimID by calling the GetNextClaimID method.


c. Add the claim to the RewardsClaimed entity set.
d. Decrement the associated contacts points by the points used for the claim.
e. Save all of the changes to the database and return the new ClaimID.
f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Add a new RewardsClaimed entity
''' </summary>
''' <param name="claim">The detached claim to add.</param>
''' <returns>True if success</returns>
Public Function CreateRewardsClaim(ByVal claim As RewardsClaimed) As Integer _
Implements IDataAccessLayer.CreateRewardsClaim

' Check you have an ObjectContext object.


If entities Is Nothing Then entities = New _
AdventureWorksEntities()

Try

' Get the next valid claim id.


claim.ClaimID = GetNextClaimID()

' Add the RewardsClaimed to the entity set.


entities.RewardsClaimed.AddObject(claim)

' Get the contact and decrement the points.


Dim Contact As Contact = claim.Contact
Contact.CurrentPoints = Contact.CurrentPoints
claim.PointsUsed

' Save all of the changes to the database.


entities.SaveChanges()

Return claim.ClaimID
Lab Answer Key: Creating, Updating, and Deleting Entity Data 27

Catch ex As InvalidOperationException

Throw New DALException("There was a problem creating the RewardClaim", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem saving the RewardClaim to the


database", ex)

End Try

End Function

[Visual C#]
/// <summary>
/// Add a new RewardsClaimed entity.
/// </summary>
/// <param name="claim">The detached claim to add.</param>
/// <returns>True if success</returns>
public int CreateRewardsClaim(RewardsClaimed claim)
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try
{
// Get the next valid claim id.
claim.ClaimID = GetNextClaimID();

// Add the RewardsClaimed to the entity set.


entities.RewardsClaimed.AddObject(claim);

// Get the contact and decrement the points.


Contact contact = claim.Contact;
contact.CurrentPoints -= claim.PointsUsed;

// Save all of the changes to the database.


entities.SaveChanges();

return claim.ClaimID;
}
catch (InvalidOperationException ex)
{
throw new DALException("There was a problem creating the RewardClaim", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem saving the RewardClaim to the
database", ex);
}
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 4: Add code to update a RewardsClaim record


1. Review the task list:
28 Lab Answer Key: Creating, Updating, and Deleting Entity Data

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - Update a
RewardsClaimed entity in the task list. This task is located in the UpdateRewardsClaim method:
In the task list, double-click the comment TODO: Ex2 - Update a RewardsClaimed entity.
3. In the UpdateRewardsClaim method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Get the EntityKey property of the detached claim passed to the method. This detached claim
contains the modified properties.
c. Use the TryGetObjectByKey method to load the correct claim into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a
parameter.
e. Adjust the points for the associated contact by the difference between the old and the new
points for the claim.
f. Save all of the changes to the database and return true.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' Update a RewardsClaimed entity
''' </summary>

''' <param name="rewardClaim">The detached RewardsClaimed entity with the new


values</param>

''' <returns>True on success</returns>


Public Function UpdateRewardsClaim(ByVal rewardClaim As RewardsClaimed) As Boolean _
Implements IDataAccessLayer.UpdateRewardsClaim

' Check you have an ObjectContext object.


If entities Is Nothing Then entities = _
New AdventureWorksEntities()

Try

' Get the entity key you require.


Dim key As EntityKey = rewardClaim.EntityKey

Dim rewardClaimToModify As Object = Nothing

' Ensure that the entity to modify is loaded.


If entities.TryGetObjectByKey(key, rewardClaimToModify) Then

' Get the old and new points ready to update the contact.
Dim originalPoints As Integer =
DirectCast(rewardClaimToModify, _
RewardsClaimed).PointsUsed
Lab Answer Key: Creating, Updating, and Deleting Entity Data 29

Dim newPoints As Integer = rewardClaim.PointsUsed

' Copy all of the changes over from the


' detached RewardClaim entity.
entities.ApplyCurrentValues(key.EntitySetName,
rewardClaim)

If originalPoints <> newPoints Then

' Update contacts points.


DirectCast(rewardClaimToModify, _
RewardsClaimed).Contact.CurrentPoints = _
DirectCast(rewardClaimToModify, _
RewardsClaimed).Contact.CurrentPoints + originalPoints

DirectCast(rewardClaimToModify, _
RewardsClaimed).Contact.CurrentPoints = _
DirectCast(rewardClaimToModify, _
RewardsClaimed).Contact.CurrentPoints + newPoints

End If

End If

' Save the changes to the database.


entities.SaveChanges()
Return True

Catch ex As InvalidOperationException

Throw New DALException("There was a problem modifying the RewardClaim", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem saving the RewardClaim changes to


the database", ex)

End Try

End Function

[Visual C#]
/// <summary>
/// Update a RewardsClaimed entity.
/// </summary>
/// <param name="rewardClaim">The detached RewardsClaimed entity with the new
values</param>
/// <returns>True on success</returns>
public bool UpdateRewardsClaim(RewardsClaimed rewardClaim)
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

try
{
// Get the entity key you require.
EntityKey key = rewardClaim.EntityKey;

object rewardClaimToModify;
30 Lab Answer Key: Creating, Updating, and Deleting Entity Data

// Ensure that the entity to modify is loaded.


if (entities.TryGetObjectByKey(key, out rewardClaimToModify))
{
// Get the old and new points ready to update the contact.
int originalPoints =
((RewardsClaimed)rewardClaimToModify).PointsUsed;
int newPoints = rewardClaim.PointsUsed;

// Copy all of the changes over from the


// detached RewardClaim entity.
entities.ApplyCurrentValues(key.EntitySetName,
rewardClaim);

if (originalPoints != newPoints)
{
// Update contacts points.
((RewardsClaimed)rewardClaimToModify)
.Contact.CurrentPoints += originalPoints;
((RewardsClaimed)rewardClaimToModify)
.Contact.CurrentPoints -= newPoints;
}
}

// Save the changes to the database.


entities.SaveChanges();
return true;
}
catch (InvalidOperationException ex)
{
throw new DALException("There was a problem modifying the RewardClaim", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem saving the RewardClaim changes to
the database", ex);
}
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 5: Add code to delete a RewardsClaim record


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - Delete a
RewardsClaimed entity in the task list. This task is located in the DeleteRewardsClaim method:
In the task list, double-click the comment TODO: Ex2 - Delete a RewardsClaimed entity.
3. In the DeleteRewardsClaim method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 31

b. Use the claimID value passed as a parameter to create the EntityKey object of the claim to
delete.
c. Use the TryGetObjectByKey method to load the correct claim into the context.
d. Give the points of the claim back to the associated contact.
e. Delete the claim from the context.
f. Save all of the changes to the database and return true.
g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new
DALException exception.

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' Delete a RewardsClaimed entity
''' </summary>
''' <param name="claimId">The ClaimID of the entity to delete</param>
''' <returns>True if success</returns>
Public Function DeleteRewardsClaim(ByVal claimId As Integer) As Boolean _
Implements IDataAccessLayer.DeleteRewardsClaim

' Check you have an ObjectContext object.


If entities Is Nothing Then entities = _
New AdventureWorksEntities()
Try

Dim rewardClaimToDelete As Object = Nothing

' Get the entity key of the claim to delete.


Dim key As EntityKey = New EntityKey( _
"AdventureWorksEntities.RewardsClaimed", "ClaimID", _
claimId)
' Ensure that the entity to modify is loaded.
If entities.TryGetObjectByKey(key, rewardClaimToDelete) Then

' Give the points back to the contact.


DirectCast(rewardClaimToDelete, _
RewardsClaimed).Contact.CurrentPoints =
DirectCast(rewardClaimToDelete, _
RewardsClaimed).Contact.CurrentPoints +
DirectCast(rewardClaimToDelete, _
RewardsClaimed).PointsUsed

' Delete the object.


entities.DeleteObject(rewardClaimToDelete)
End If

' Save the changes to the database.


entities.SaveChanges()
Return True

Catch ex As InvalidOperationException

Throw New DALException("There was a problem deleting the RewardsClaim", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem deleting the RewardClaim from the
database", ex)

End Try
32 Lab Answer Key: Creating, Updating, and Deleting Entity Data

End Function

[Visual C#]

/// <summary>
/// Delete a RewardsClaimed entity.
/// </summary>

/// <param name="claimId">The ClaimID of the entity to delete</param>


/// <returns>True if success</returns>

public bool DeleteRewardsClaim(int claimId)


{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();
try
{
object rewardClaimToDelete = null;

// Get the entity key of the claim to delete.

EntityKey key = new


EntityKey("AdventureWorksEntities.RewardsClaimed",
"ClaimID", claimId);

// Ensure that the entity to modify is loaded.

if (entities.TryGetObjectByKey(key, out rewardClaimToDelete))


{
// Give the points back to the contact.
((RewardsClaimed)rewardClaimToDelete)
.Contact.CurrentPoints +=
((RewardsClaimed)rewardClaimToDelete).PointsUsed;

// Delete the object.


entities.DeleteObject(rewardClaimToDelete);
}

// Save the changes to the database.


entities.SaveChanges();
return true;
}

catch (InvalidOperationException ex)


{
throw new DALException("There was a problem deleting the RewardsClaim", ex);
}

catch (UpdateException ex)


{
throw new DALException("There was a problem deleting the RewardClaim from the
database", ex);
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 33

Task 6: Add unit tests to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex2 - Add a test for
CreateRewardsClaim in the task list. This task is located in the CreateRewardsClaimTest method:
In the task list, double-click the comment TODO: Ex2 - Add a test for CreateRewardsClaim.
3. In the CreateRewardsClaimTest method, delete the existing code:
Select the code in the method, and then press DELETE.
4. Add a unit test to verify the behavior of the CreateRewardsClaim method. Use the
CreateLocalClaim method to create a claim to add to the database, and use the
GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you remove
the rewards and release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' A test for CreateRewardsClaim
''' </summary>

<TestMethod()> _
Public Sub CreateRewardsClaimTest()

Dim dal As New DataAccessLayer()

' Create a claim entity to insert.


Dim claim As RewardsClaimed = Me.CreateLocalClaim()

' Insert the claim entity.


Dim claimID As Integer = dal.CreateRewardsClaim(claim)

' Retrieve the claim entity.


Dim actual As RewardsClaimed = GetRewardsClaimedByID(claimID)
' Do the test.
Dim expected As RewardsClaimed = CreateLocalClaim()
Assert.IsTrue(claimID > 0)
Assert.AreEqual(expected.PointsUsed, actual.PointsUsed)
Assert.AreEqual(expected.RewardID, actual.RewardID)
Assert.AreEqual(expected.ContactID, actual.ContactID)

' Tidy up.


Dim deleteResult As Boolean = _
dal.DeleteRewardsClaim(actual.ClaimID)

dal.Dispose()

End Sub

[Visual C#]
/// <summary>
/// A test for CreateRewardsClaim.
/// </summary>
34 Lab Answer Key: Creating, Updating, and Deleting Entity Data

[TestMethod()]
public void CreateRewardsClaimTest()
{
DataAccessLayer dal = new DataAccessLayer();

// Create a claim entity to insert.


RewardsClaimed claim = this.CreateLocalClaim();

// Insert the claim entity.


int claimID = dal.CreateRewardsClaim(claim);

// Retrieve the claim entity.


RewardsClaimed actual = GetRewardsClaimedByID(claimID);

// Do the test.
RewardsClaimed expected = CreateLocalClaim();
Assert.IsTrue(claimID > 0);
Assert.AreEqual(expected.PointsUsed, actual.PointsUsed);
Assert.AreEqual(expected.RewardID, actual.RewardID);
Assert.AreEqual(expected.ContactID, actual.ContactID);

// Tidy up.
bool deleteResult = dal.DeleteRewardsClaim(actual.ClaimID);

dal.Dispose();
}

5. Locate the UpdateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a
test for UpdateRewardsClaim in the task list. This task is located in the UpdateRewardsClaimTest
method:
In the task list, double-click the comment TODO: Ex2 - Add a test for UpdateRewardsClaim.
6. In the UpdateRewardsClaimTest method, delete the existing code:
Select the code in the method, then and press DELETE.
7. Add a unit test to verify the behavior of the UpdateRewardsClaim method. Use the
CreateLocalClaim method to create a claim to add to the database, which you can then modify, and
use the GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you
remove the claim and release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' A test for UpdateRewardsClaim
''' </summary>

<TestMethod()> _
Public Sub UpdateRewardsClaimTest()

Dim dal As New DataAccessLayer()

' Create a new claim entity to test.


Dim claim As RewardsClaimed = Me.CreateLocalClaim()
Dim claimID As Integer = dal.CreateRewardsClaim(claim)

' Retrieve detached version.


Dim expected As RewardsClaimed = GetRewardsClaimedByID(claimID)

' Make some changes.


Lab Answer Key: Creating, Updating, and Deleting Entity Data 35

expected.PointsUsed = 2000
expected.RewardID = 21
Dim updateResult As Boolean = dal.UpdateRewardsClaim(expected)

' Retrieve updated version.


Dim actual As RewardsClaimed = GetRewardsClaimedByID(claimID)

Assert.AreEqual(updateResult, True)
Assert.AreEqual(expected.PointsUsed, actual.PointsUsed)
Assert.AreEqual(expected.RewardID, actual.RewardID)
Assert.AreEqual(expected.ContactID, actual.ContactID)
' Tidy up.
Dim deleteResult As Boolean = dal.DeleteRewardsClaim(claimID)
dal.Dispose()

End Sub

[Visual C#]
/// <summary>
/// A test for UpdateRewardsClaim.
/// </summary>

[TestMethod()]
public void UpdateRewardsClaimTest()
{
DataAccessLayer dal = new DataAccessLayer();

// Create a new claim entity to test.


RewardsClaimed claim = this.CreateLocalClaim();
int claimID = dal.CreateRewardsClaim(claim);

// Retrieve detached version.


RewardsClaimed expected = GetRewardsClaimedByID(claimID);

// Make some changes.


expected.PointsUsed = 2000;
expected.RewardID = 21;
bool updateResult = dal.UpdateRewardsClaim(expected);

// Retrieve updated version.


RewardsClaimed actual = GetRewardsClaimedByID(claimID);

Assert.AreEqual(updateResult, true);
Assert.AreEqual(expected.PointsUsed, actual.PointsUsed);
Assert.AreEqual(expected.RewardID, actual.RewardID);
Assert.AreEqual(expected.ContactID, actual.ContactID);

// Tidy up.
bool deleteResult = dal.DeleteRewardsClaim(claimID);
dal.Dispose();

8. Locate the DeleteRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a
test for DeleteRewardsClaim in the task list. This task is located in the DeleteRewardsClaimTest
method:
In the task list, double-click the comment TODO: Ex2 - Add a test for DeleteRewardsClaim.
9. In the DeleteRewardsClaimTest method, delete the existing code:
36 Lab Answer Key: Creating, Updating, and Deleting Entity Data

Select the code in the method, and then press DELETE.


10. Add a unit test to verify the behavior of the DeleteRewardsClaim method. Use the
CreateLocalClaim method to create a claim to add to the database, which you can then delete.
Ensure that you release any resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' A test for DeleteRewardsClaim
''' </summary>
<TestMethod()> _
Public Sub DeleteRewardsClaimTest()

Dim dal As New DataAccessLayer()

' Create a test record.


Dim claim As RewardsClaimed = Me.CreateLocalClaim()
Dim claimID As Integer = dal.CreateRewardsClaim(claim)

' Perform the delete.


Dim deleteResult As Boolean = dal.DeleteRewardsClaim(claimID)

' Check the result.


Assert.AreEqual(deleteResult, True)

' Tidy up.


dal.Dispose()

End Sub

[Visual C#]
/// <summary>
/// A test for DeleteRewardsClaim
/// </summary>
[TestMethod()]
public void DeleteRewardsClaimTest()
{
DataAccessLayer dal = new DataAccessLayer();

// Create a test record.


RewardsClaimed claim = this.CreateLocalClaim();
int claimID = dal.CreateRewardsClaim(claim);

// Perform the delete.


bool deleteResult = dal.DeleteRewardsClaim(claimID);

// Check the result.


Assert.AreEqual(deleteResult, true);

// Tidy up.
dal.Dispose();
}

11. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.
Lab Answer Key: Creating, Updating, and Deleting Entity Data 37

Task 7: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed, including the GetContactListTest test.
4. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model
into the data grid. Verify that you can add, modify, and delete claims and that the points for the
contact are adjusted correctly.
6. Close the application.
7. Close the solution, and then close Visual Studio:
a. On the File menu, click Close Solution.
b. On the File menu, click Exit.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 1

Module 5
Lab Answer Key: Handling Multi-User Scenarios by Using
Object Services
Contents:
Exercise 1: Handling Concurrency of Rewards Claimed Data 2
Exercise 2: Updating the RewardsClaimed and ArchivedRewardsClaimed
Information by Using a Transaction 13
2 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

Lab 5: Handling Multi-User Scenarios by Using


Object Services
Exercise 1: Handling Concurrency of Rewards Claimed Data
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-05 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex1\Starter or
E:\Labfiles\Lab05\CS\Ex1\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab05\VB\Ex1\Starter folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab05\CS\Ex1\Starter folder, click DAL.sln, and then click Open.

Task 3: Set the concurrency behavior of the Contact and RewardsClaimed entities
1. Open the AdventureWorksEDM model in the Entity Designer:
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. In the AdventureWorksEDM model, set the Concurrency Mode property of the ModifiedDate
property of the Contact entity to Fixed:
a. In the Entity Designer pane, in the Contact entity, click ModifiedDate.
b. In the Properties pane, click Concurrency Mode, and then select Fixed.
3. In the AdventureWorksEDM model, set the Concurrency Mode property of the TimeStamp property
of the RewardsClaimed entity to Fixed:
a. In the Entity Designer pane, in the RewardsClaimed entity, click TimeStamp.
b. In the Properties pane, click Concurrency Mode, and then select Fixed.
4. Save the AdventureWorks EDM model:
On the File menu, click Save AdventureWorksEDM.edmx.

Task 4: Add code to set the ModifiedDate property of the contact


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 3

2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Update the contact's
Modified Date property item in the task list. This task is located in the UpdateContact method:
In the task list, double-click the TODO: Ex1 - Update the contact's Modified Date property
item.
3. Immediately after the comment, add code that sets the ModifiedDate property of the contact being
saved to the current date and time.
Your code should resemble the following code example.

[Visual Basic]

' TODO: Ex1 - Update the contact's Modified Date property


DirectCast(contactToModify, Contact).ModifiedDate = DateTime.Now

[Visual C#]

// TODO: Ex1 - Update the contact's Modified Date property


((Contact)contactToModify).ModifiedDate = DateTime.Now;

4. Save the DataAccessLayer file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 5: Add code to handle OptimisticConcurrencyException exceptions in the


CreateRewardsClaim method
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Handle the
OptimisticConcurrencyException in CreateRewardsClaim item in the task list. This task is located
in the CreateRewardsClaim method:
In the task list, double-click the TODO: Ex1 - Handle the OptimisticConcurrencyException in
CreateRewardsClaim item.
3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException
exception. In the catch block, add code that performs the following tasks:
a. Refresh the contact with the current values from the database.
b. Deduct the points used for the claim from the contact.
c. Set the ModifiedDate property of the contact to the current date and time.
d. Save the changes to the database.
e. Refresh the contact and the claim with the current values from the database.
f. Return the new claimID value.

Your code should resemble the following code example.

[Visual Basic]

' TODO: Ex1 - Handle the OptimisticConcurrencyException in


CreateRewardsClaim
4 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

Catch ex As OptimisticConcurrencyException

' The contact could have been modified so


' get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, claim.Contact)

' Deduct points from customer.


claim.Contact.CurrentPoints = claim.Contact.CurrentPoints
claim.PointsUsed
claim.Contact.ModifiedDate = DateTime.Now

' Try to save all the changes again.


entities.SaveChanges()

' Make sure the correct datetime is in the context


entities.Refresh(RefreshMode.StoreWins, claim.Contact)
entities.Refresh(RefreshMode.StoreWins, claim)

Return claim.ClaimID

[Visual C#]

// TODO: Ex1 - Handle the OptimisticConcurrencyException in


// CreateRewardsClaim
catch (OptimisticConcurrencyException)
{
// The contact may have been modified, so
// get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, claim.Contact);

// Deduct points from customer.


claim.Contact.CurrentPoints -= claim.PointsUsed;
claim.Contact.ModifiedDate = DateTime.Now;

// Try to save all of the changes again.


entities.SaveChanges();

// Ensure that the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, claim.Contact);
entities.Refresh(RefreshMode.StoreWins, claim);

return claim.ClaimID;
}

4. Save the DataAccessLayer file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 6: Add code to handle OptimisticConcurrencyException exceptions in the


UpdateRewardsClaim method
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 5

2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Handle the
OptimisticConcurrencyException in UpdateRewardsClaim item in the task list. This task is located
in the UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex1 - Handle the OptimisticConcurrencyException in
UpdateRewardsClaim item.
3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException
exception. In the catch block, add code that performs the following tasks:
a. Refresh the contact with the current values from the database.
b. Refresh the claim, keeping any changes made in the context.
c. Deduct the points used for the original claim from the contact, and add the points used for the
modified claim to the contact.
d. Set the ModifiedDate property of the contact to the current date and time.
e. Save the changes to the database.
f. Refresh the contact and the claim with the current values from the database.
g. Return true.

Your code should resemble the following code example.

[Visual Basic]

' TODO: Ex1 - Handle the OptimisticConcurrencyException in UpdateRewardsClaim

Catch ex As OptimisticConcurrencyException

' The contact could have been modified


' so get the latest version of the contact from the database.
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)

' It's very unlikely the claim has been modified as we've
' only just refreshed it.
entities.Refresh(RefreshMode.ClientWins, rewardClaimToModify)

' Adjust points for customer.


rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints + originalPoints
rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints - newPoints

rewardClaimToModify.Contact.ModifiedDate = DateTime.Now

' Try to save all the changes again.


entities.SaveChanges()

' Make sure the correct datetime is in the context


entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)
entities.Refresh(RefreshMode.StoreWins, rewardClaimToModify)

Return True

[Visual C#]

// TODO: Ex1 - Handle the OptimisticConcurrencyException in


// UpdateRewardsClaim
6 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

catch (OptimisticConcurrencyException)

{
// The contact may have been modified,
// so get the latest version of the contact from the database.
entities.Refresh(RefreshMode.StoreWins, rewardClaimToModify.Contact);

// It's very unlikely the claim has been modified, because you
// have only just refreshed it.
entities.Refresh(RefreshMode.ClientWins, rewardClaimToModify);

// Adjust points for customer.


rewardClaimToModify.Contact.CurrentPoints
+= originalPoints;
rewardClaimToModify.Contact.CurrentPoints
-= newPoints;
rewardClaimToModify.Contact.ModifiedDate = DateTime.Now;

// Try to save all of the changes again.


entities.SaveChanges();

// Ensure that the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact);
entities.Refresh(RefreshMode.StoreWins, rewardClaimToModify);

return true;

4. Save the DataAccessLayer file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 7: Add code to handle OptimisticConcurrencyException exceptions in the


DeleteRewardsClaim method
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 - Handle the
OptimisticConcurrencyException in DeleteRewardsClaim item in the task list. This task is located
in the DeleteRewardsClaim method:
In the task list, double-click the TODO: Ex1 - Handle the OptimisticConcurrencyException in
DeleteRewardsClaim item.
3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException
exception. In the catch block, add code that performs the following tasks:
a. Refresh the contact with the current values from the database.
b. Refresh the claim, keeping any changes made in the context.
c. Add the points used for the deleted claim to the contact.
d. Set the ModifiedDate property of the contact to the current date and time.
e. Save the changes to the database.
f. Refresh the contact with the current values from the database.
g. Return true.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 7

Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex1 - Handle the OptimisticConcurrencyException in DeleteRewardsClaim
Catch ex As OptimisticConcurrencyException

' The contact could have been modified so


' get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, relatedContact)

' Give the points back to the contact


relatedContact.CurrentPoints = relatedContact.CurrentPoints +
rewardClaimToDelete.PointsUsed
relatedContact.ModifiedDate = DateTime.Now

' Try to save all the changes again.


entities.SaveChanges()

' Make sure the correct datetime is in the context


entities.Refresh(RefreshMode.StoreWins, relatedContact)

Return True

[Visual C#]
// TODO: Ex1 - Handle the OptimisticConcurrencyException in
// DeleteRewardsClaim
catch (OptimisticConcurrencyException)
{
// The contact may have been modified, so
// get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, relatedContact);

// Give the points back to the contact.


relatedContact.CurrentPoints +=
rewardClaimToDelete.PointsUsed;
relatedContact.ModifiedDate = DateTime.Now;

// Try to save all of the changes again.


entities.SaveChanges();

// Ensure that the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, relatedContact);

return true;
}

4. Save the DataAccessLayer file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 8: Add unit tests to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
8 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Create a test to
verify that CreateRewardsClaim handles concurrency issues item in the task list. This task is
located in the CreateRewardsClaimConcurrencyTest method:
In the task list, double-click the TODO: Ex1 - Create a test to verify that CreateRewardsClaim
handles concurrency issues item.
3. Using the comments in the CreateRewardsClaimConcurrencyTest method for guidance, write code
that verifies the behavior of the CreateRewardsClaim method when two users modify the same
contact while they are adding new claims to the database.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' A test for CreateRewardsClaim
''' when there is an OptimisticConcurrencyException
''' </summary>

<TestMethod()> _
Public Sub CreateRewardsClaimConcurrencyTest()

' Create two instances of the DataAccessLayer


' to represent two users
Dim user1 As New DataAccessLayer()
Dim user2 As New DataAccessLayer()

' User1 creates a contact - this loads the contact


' into the context
Dim contactID As Integer = user1.AddContact(CreateTestContact())

' User2 adds a new claim - modifying the contact


Dim claim As RewardsClaimed = CreateLocalClaim()
claim.ContactID = contactID
Dim claimID As Integer = user2.CreateRewardsClaim(claim)
Dim actual1 As Contact = GetContactById(contactID)
Assert.AreEqual(9000, actual1.CurrentPoints)

' User1 adds a new claim for the same contact


' - getting an OptimisticConcurrencyException
Dim newClaim As RewardsClaimed = CreateLocalClaim()
newClaim.ClaimID = claimID
newClaim.ContactID = contactID
newClaim.PointsUsed = 500
Dim newClaimID As Integer = user1.CreateRewardsClaim(newClaim)
Dim actual2 As Contact = GetContactById(contactID)
Assert.AreEqual(8500, actual2.CurrentPoints)

' Tidy up - delete the contact


user1.DeleteContact(contactID)
user1.Dispose()
user2.Dispose()

End Sub

[Visual C#]

/// <summary>
/// A test for the CreateRewardsClaim method
/// when there is an OptimisticConcurrencyException exception.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 9

/// </summary>

[TestMethod()]
public void CreateRewardsClaimConcurrencyTest()
{
// Create two instances of the DataAccessLayer
// to represent two users.
DataAccessLayer user1 = new DataAccessLayer();
DataAccessLayer user2 = new DataAccessLayer();

// User1 creates a contactthis loads the contact


// into the context.
int contactID = user1.AddContact(CreateTestContact());

// User2 adds a new claimmodifying the contact.


RewardsClaimed claim = CreateLocalClaim();
claim.ContactID = contactID;
int claimID = user2.CreateRewardsClaim(claim);
Contact actual1 = GetContactById(contactID);
Assert.AreEqual(9000, actual1.CurrentPoints);

// User1 adds a new claim for the same contact


//getting an OptimisticConcurrencyException exception.
RewardsClaimed newClaim = CreateLocalClaim();
newClaim.ClaimID = claimID;
newClaim.ContactID = contactID;
newClaim.PointsUsed = 500;
int newClaimID = user1.CreateRewardsClaim(newClaim);
Contact actual2 = GetContactById(contactID);
Assert.AreEqual(8500, actual2.CurrentPoints);
// Tidy updelete the contact.
user1.DeleteContact(contactID);
user1.Dispose();
user2.Dispose();
}

4. Locate the UpdateRewardsClaimConcurrencyTest method by double-clicking the comment TODO:


Ex1 - Create a test to verify that UpdateRewardsClaim handles concurrency issues item in the
task list. This task is located in the UpdateRewardsClaimConcurrencyTest method:
In the task list, double-click the TODO: Ex1 - Create a test to verify that UpdateRewardsClaim
handles concurrency issues item.
5. Using the comments in the UpdateRewardsClaimConcurrencyTest method for guidance, write
code that verifies the behavior of the UpdateRewardsClaim method when two users modify the
same contact while they are updating claims to the database.
Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' A test for UpdateRewardsClaim
''' when there is an OptimisticConcurrencyException
''' </summary>

<TestMethod()> _
Public Sub UpdateRewardsClaimConcurrencyTest()

' Create two instances of the DataAccessLayer


' to represent two users
Dim user1 As New DataAccessLayer()
Dim user2 As New DataAccessLayer()
10 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

' User1 creates a contact - this loads the


' contact into the context
Dim contactID As Integer = user1.AddContact(CreateTestContact())

' User2 adds a new claim - modifying the contact


Dim claim = CreateLocalClaim()
claim.ContactID = contactID
Dim claimID As Integer = user2.CreateRewardsClaim(claim)
Dim actual1 = GetContactById(contactID)
Assert.AreEqual(9000, actual1.CurrentPoints)

' User1 updates the claim for the same contact


' - getting an OptimisticConcurrencyException

Dim modifiedClaim As RewardsClaimed = GetRewardsClaimedByID(claimID)


modifiedClaim.ClaimID = claimID
modifiedClaim.ContactID = contactID
modifiedClaim.PointsUsed = 500
user1.UpdateRewardsClaim(modifiedClaim)
Dim actual2 As Contact = GetContactById(contactID)
Assert.AreEqual(9500, actual2.CurrentPoints)

' Tidy up - delete the contact


user1.DeleteContact(contactID)
user1.Dispose()
user2.Dispose()

End Sub

[Visual C#]

/// <summary>
/// A test for the UpdateRewardsClaim method
/// when there is an OptimisticConcurrencyException exception.
/// </summary>

[TestMethod()]
public void UpdateRewardsClaimConcurrencyTest()

{
// Create two instances of the DataAccessLayer
// to represent two users.

DataAccessLayer user1 = new DataAccessLayer();


DataAccessLayer user2 = new DataAccessLayer();

// User1 creates a contactthis loads the


// contact into the context.

int contactID = user1.AddContact(CreateTestContact());

// User2 adds a new claimmodifying the contact.


RewardsClaimed claim = CreateLocalClaim();
claim.ContactID = contactID;
int claimID = user2.CreateRewardsClaim(claim);
Contact actual1 = GetContactById(contactID);
Assert.AreEqual(9000, actual1.CurrentPoints);
// User1 updates the claim for the same contact
//getting an OptimisticConcurrencyException exception.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 11

RewardsClaimed modifiedClaim = GetRewardsClaimedByID(claimID);


modifiedClaim.ClaimID = claimID;
modifiedClaim.ContactID = contactID;
modifiedClaim.PointsUsed = 500;
user1.UpdateRewardsClaim(modifiedClaim);
Contact actual2 = GetContactById(contactID);
Assert.AreEqual(9500, actual2.CurrentPoints);

// Tidy updelete the contact.


user1.DeleteContact(contactID);
user1.Dispose();
user2.Dispose();
}

6. Locate the DeleteRewardsClaimConcurrencyTest method by double-clicking the comment TODO:


Ex1 - Create a test to verify that DeleteRewardsClaim handles concurrency issues item in the
task list. This task is located in the DeleteRewardsClaimConcurrencyTest method:
In the task list, double-click the TODO: Ex1 - Create a test to verify that DeleteRewardsClaim
handles concurrency issues item.
7. Using the comments in the DeleteRewardsClaimConcurrencyTest method for guidance, write code
that verifies the behavior of the DeleteRewardsClaim method when two users modify the same
contact while they are inserting and deleting claims in the database.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' A test for DeleteRewardsClaim
''' when there is an OptimisticConcurrencyException
''' </summary>
<TestMethod()> _
Public Sub DeleteRewardsClaimConcurrencyTest()

' Create two instances of the DataAccessLayer


' to represent two users
Dim user1 As New DataAccessLayer()
Dim user2 As New DataAccessLayer()

' User1 creates a contact - this loads the


' contact into the context
Dim contactID As Integer = user1.AddContact(CreateTestContact())
' User2 adds a new claim - modifying the contact
Dim claim As RewardsClaimed = CreateLocalClaim()
claim.ContactID = contactID
Dim claimID As Integer = user2.CreateRewardsClaim(claim)
Dim actual1 As Contact = GetContactById(contactID)
Assert.AreEqual(9000, actual1.CurrentPoints)

' User1 deletes the claim for the same contact


' - getting an OptimisticConcurrencyException
user1.DeleteRewardsClaim(claimID)
Dim actual2 As Contact = GetContactById(contactID)
Assert.AreEqual(10000, actual2.CurrentPoints)

' Tidy up - delete the contact


user1.DeleteContact(contactID)
user1.Dispose()
user2.Dispose()
12 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

End Sub

[Visual C#]

/// <summary>
/// A test for the DeleteRewardsClaim method
/// when there is an OptimisticConcurrencyException exception.
/// </summary>
[TestMethod()]
public void DeleteRewardsClaimConcurrencyTest()
{
// Create two instances of the DataAccessLayer
// to represent two users.
DataAccessLayer user1 = new DataAccessLayer();
DataAccessLayer user2 = new DataAccessLayer();

// User1 creates a contactthis loads the


// contact into the context.
int contactID = user1.AddContact(CreateTestContact());

// User2 adds a new claimmodifying the contact.


RewardsClaimed claim = CreateLocalClaim();
claim.ContactID = contactID;
int claimID = user2.CreateRewardsClaim(claim);
Contact actual1 = GetContactById(contactID);
Assert.AreEqual(9000, actual1.CurrentPoints);

// User1 deletes the claim for the same contact


//getting an OptimisticConcurrencyException exception.
user1.DeleteRewardsClaim(claimID);
Contact actual2 = GetContactById(contactID);
Assert.AreEqual(10000, actual2.CurrentPoints);

// Tidy updelete the contact.


user1.DeleteContact(contactID);
user1.Dispose();
user2.Dispose();
}

8. Save the DataAccessLayerTest file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 9: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed, including the CreateRewardsClaimConcurrencyTest,
UpdateRewardsClaimConcurrencyTest, and DeleteRewardsClaimConcurrencyTest tests.
4. Close the solution:
On the File menu, click Close Solution.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 13

Exercise 2: Updating the RewardsClaimed and ArchivedRewardsClaimed


Information by Using a Transaction
Task 1: Open the starter project for this exercise
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex2\Starter or
E:\Labfiles\Lab05\CS\Ex2\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab05\VB\Ex2\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab05\CS\Ex2\Starter folder, click DAL.sln, and then click Open.

Task 2: Create the AdventureWorksArchivedEDM Entity Data Model


1. Add a new ADO.NET Entity Data Model to the DAL project. Generate the data model from the
AdventureWorks Microsoft SQL Server database and create entities for the
ArchivedRewardsClaimed table:
a. In Solution Explorer, right-click DAL, point to Add, and then click New Item.
b. In the Add New Item - DAL dialog box, in the Templates list, click ADO.NET Entity Data
Model, in the Name box, type AdventureWorksArchivedEDM and then click Add.
c. In the Entity Data Model Wizard, on the Choose Model Contents page, click Generate from
database, and then click Next.
d. On the Choose Your Data Connection page, click New Connection.
e. In the Choose Data Source dialog box, in the Data source list, click Microsoft SQL Server, and
then click Continue.
f. In the Connection Properties dialog box, in the Server name box, type .\SQLEXPRESS and in
the Select or enter a database name box, enter AdventureWorks and then click OK.
g. On the Choose Your Data Connection page, in the Save entity connection settings in
App.Config as box, type AdventureWorksArchivedEntities and then click Next.
h. On the Choose Your Database Objects page, expand Tables, and then select the
ArchivedRewardsClaimed (Sales) check box.
i. Clear the Pluralize or singularize generated object names check box.
j. In the Model Namespace box, type AdventureWorksArchivedModel and then click Finish.
2. Copy the AdventureWorksArchivedEntities connection string from the App.Config file in the DAL
project to the App.Config file in the DALTest project:
a. In Solution Explorer, in the DAL project, right-click App.Config, and then click Open.
b. Select the whole line with the AdventureWorksArchivedEntities connection string, and on the
Edit menu, click Copy.
c. On the File menu, click Close.
d. In Solution Explorer, in the DALTest project, right-click App.Config, and then click Open.
e. Add a new blank line after the AdventureWorksEntities connection string, and on the Edit
menu, click Paste.

Your code should resemble the following code example.

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<connectionStrings>
<add name="AdventureWorksEntities"
connectionString="metadata=res://*/AdventureWorksEDM.csdl|res://*/AdventureWorksEDM.ssdl
|res://*/AdventureWorksEDM.msl;provider=System.Data.SqlClient;provider connection
14 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

string=&quot;Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks;Integrated


Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />
<add name="AdventureWorksArchivedEntities"
connectionString="metadata=res://*/AdventureWorksArchivedEDM.csdl|res://*/AdventureWorks
ArchivedEDM.ssdl|res://*/AdventureWorksArchivedEDM.msl;provider=System.Data.SqlClient;pr
ovider connection string=&quot;Data Source=10265A-GEN-DEV\SQLExpress;Initial
Catalog=AdventureWorks;Integrated Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>

f. On the File menu, click Save App.Config.


g. On the File menu, click Close.

Task 3: Modify the CreateRewardsClaim method to save a copy of the claim to the
archive table as part of a transaction
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - In
CreateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an
ArchivedRewardsClaim item in the task list. This task is located in the CreateRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaim, wrap SaveChanges in a
distributed transaction that creates an ArchivedRewardsClaim item.
3. Place the call to the SaveChanges method inside a new TransactionScope code block. In the
TransactionScope code block, after the call to the SaveChanges method, add code to perform the
following tasks:
a. Create a new AdventureWorksArchivedEntities context called archivedEntities.
b. In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a
copy of the data in the RewardsClaimed entity.
c. Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.
d. Save all of the changes in the archivedEntities context.
e. At the end of the TransactionScope code block, call the Complete method of the
TransactionScope object.

Your code should resemble the following code example.

[Visual Basic]

' TODO: Ex2 - In CreateRewardsClaim, wrap SaveChanges in a distributed


' transaction that creates an ArchivedRewardsClaim
' Begin a distributed transaction.
Using scope As New TransactionScope()

' Save all the changes to the database


entities.SaveChanges()
' Use a different ObjectContext.
Using archivedEntities As AdventureWorksArchivedEntities =
New AdventureWorksArchivedEntities()

' Create archived rewards claim.


Dim archivedClaim As ArchivedRewardsClaimed =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(0,
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 15

claim.ClaimID, claim.PointsUsed, claim.RewardID,


claim.ContactID, DateTime.Now)

archivedEntities.AddToArchivedRewardsClaimed(archivedClaim)

archivedEntities.SaveChanges()

End Using

' The Complete method commits the transaction.


' If an exception is thrown,
' Complete is not called and the transaction is rolled back.
scope.Complete()

End Using

[Visual C#]

// TODO: Ex2 - In CreateRewardsClaim, wrap SaveChanges


// in a distributed transaction that creates an
// ArchivedRewardsClaim

// Begin a distributed transaction.


using (TransactionScope scope = new TransactionScope())
{
// Save all the changes to the database
entities.SaveChanges();

// Use a different ObjectContext object.


using (AdventureWorksArchivedEntities archivedEntities =
new AdventureWorksArchivedEntities())
{
// Create archived rewards claim.
ArchivedRewardsClaimed archivedClaim =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(0,
claim.ClaimID, claim.PointsUsed, claim.RewardID,
claim.ContactID, DateTime.Now);
archivedEntities.AddToArchivedRewardsClaimed(archivedClaim);
archivedEntities.SaveChanges();

// The Complete method commits the transaction.


// If an exception is thrown, the Complete method is not called
// and the transaction is rolled back.
scope.Complete();
}

4. Add code to the CreateRewardsClaim method to handle the TransactionAbortedException


exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 -
In CreateRewardsClaim, handle TransactionAbortedException item in the task list:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaim, handle
TransactionAbortedException item.
5. Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh
the contact and the claim from the database, and throw a new DALException exception to report the
error.
Your code should resemble the following code example.
16 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

[Visual Basic]
' TODO: Ex2 - In CreateRewardsClaim, handle
' TransactionAbortedException
Catch ex As TransactionAbortedException

entities.Refresh(RefreshMode.StoreWins, claim.Contact)
entities.Refresh(RefreshMode.StoreWins, claim)
Throw New DALException("Could not save RewardsClaim and " &
"ArchivedRewardsClaim in transaction", ex)

[Visual C#]
// TODO: Ex2 - In CreateRewardsClaim, handle
// TransactionAbortedException
catch (TransactionAbortedException ex)
{
entities.Refresh(RefreshMode.StoreWins, claim.Contact);
entities.Refresh(RefreshMode.StoreWins, claim);
throw new DALException("Could not save RewardsClaim and
ArchivedRewardsClaim in transaction", ex);
}

6. Save the DataAccessLayer file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 4: Modify the UpdateRewardsClaim method to save a copy of the claim to the
archive table as part of a transaction
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 - In
UpdateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an
ArchivedRewardsClaim item in the task list. This task is located in the UpdateRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaim, wrap SaveChanges in a
distributed transaction that creates an ArchivedRewardsClaim item.
3. Place the call to the SaveChanges method inside a new TransactionScope code block. In the
TransactionScope code block, after the call to the SaveChanges method, add code to perform the
following tasks:
a. Create a new AdventureWorksArchivedEntities context called archivedEntities.
b. In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a
copy of the data in the RewardsClaimed entity.
c. Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.
d. Save all of the changes in the archivedEntities context.
e. At the end of the TransactionScope code block, call the Complete method of the
TransactionScope object.
Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 17

' TODO: Ex2 - In UpdateRewardsClaim, wrap SaveChanges in a distributed transaction that


creates an ArchivedRewardsClaim
' Begin a distributed transaction.
Using scope = New TransactionScope()

' Save all the changes to the database


entities.SaveChanges()

' Use a different ObjectContext.


Using archivedEntities =
New AdventureWorksArchivedEntities()

' Create archived rewards claim.


Dim archivedClaim As ArchivedRewardsClaimed =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(0,
rewardClaimToModify.ClaimID,
rewardClaimToModify.PointsUsed,
rewardClaimToModify.RewardID,
rewardClaimToModify.ContactID,
DateTime.Now)

archivedEntities.AddToArchivedRewardsClaimed(archivedClaim)

archivedEntities.SaveChanges()

End Using

' The Complete method commits the transaction.


' If an exception is thrown,
' Complete is not called and the transaction is rolled back.
scope.Complete()

End Using

[Visual C#]
// TODO: Ex2 - In UpdateRewardsClaim, wrap SaveChanges in a
// distributed transaction that creates an ArchivedRewardsClaim

// Begin a distributed transaction.


using (TransactionScope scope = new TransactionScope())
{
// Save all of the changes to the database.
entities.SaveChanges();

// Use a different ObjectContext object.


using (AdventureWorksArchivedEntities archivedEntities =
new AdventureWorksArchivedEntities())
{
// Create an archived rewards claim.
ArchivedRewardsClaimed archivedClaim =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(0,
rewardClaimToModify.ClaimID, rewardClaimToModify.PointsUsed,
rewardClaimToModify.RewardID, rewardClaimToModify.ContactID,
DateTime.Now);
archivedEntities.AddToArchivedRewardsClaimed(archivedClaim);

archivedEntities.SaveChanges();

}
// The Complete method commits the transaction.
// If an exception is thrown, the Complete method is not called
18 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

// and the transaction is rolled back.


scope.Complete();
}

4. Add code to the UpdateRewardsClaim method to handle the TransactionAbortedException


exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 -
In UpdateRewardsClaim, handle TransactionAbortedException item in the task list:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaim, handle
TransactionAbortedException item.
5. Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh
the contact and the claim from the database, and throw a new DALException exception to report the
error.
Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex2 - In UpdateRewardsClaim, handle
' TransactionAbortedException
Catch ex As TransactionAbortedException

entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)
entities.Refresh(RefreshMode.StoreWins, rewardClaimToModify)
Throw New DALException("Could not save RewardsClaim and " &
"ArchivedRewardsClaim in transaction", ex)

[Visual C#]
// TODO: Ex2 - In CreateRewardsClaim, handle
// TransactionAbortedException
catch (TransactionAbortedException ex)
{
entities.Refresh(RefreshMode.StoreWins, rewardClaimToModify.Contact);
entities.Refresh(RefreshMode.StoreWins, rewardClaimToModify);
throw new DALException("Could not save RewardsClaim and
ArchivedRewardsClaim in transaction", ex);
}

6. Save the DataAccessLayer file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 5: Modify the unit tests to verify your code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex2 - Count the
archived claims before the insert item in the task list. This task is located in the
CreateRewardsClaimTest method:
In the task list, double-click the TODO: Ex2 - Count the archived claims before the insert item.
3. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method.
Lab Answer Key: Handling Multi-User Scenarios by Using Object Services 19

Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex 2 - Count the archived claims before the insert
Dim start As Integer = CountArchivedRewardsClaimed()

[Visual C#]
// TODO: Ex 2 - Count the archived claims before the insert
int start = CountArchivedRewardsClaimed();

4. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived
claims after the insert and test item in the task list. This task is located in the
CreateRewardsClaimTest method:
In the task list, double-click the TODO: Ex2 - Count the archived claims after the insert and
test item.
5. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method, and verify that the number of archived rewards has
increased by one.
Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex 2 - Count the archived claims after the insert and test
Dim finish As Integer = CountArchivedRewardsClaimed()
Assert.AreEqual(start + 1, finish)

[Visual C#]
// TODO: Ex 2 - Count the archived claims after the insert and test
int finish = CountArchivedRewardsClaimed();
Assert.AreEqual(start + 1, finish);

6. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived
claims before the update item in the task list. This task is located in the UpdateRewardsClaimTest
method:
In the task list, double-click the TODO: Ex2 - Count the archived claims before the update
item.
7. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method.
Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex 2 - Count the archived claims before the update
Dim start As Integer = CountArchivedRewardsClaimed()

[Visual C#]
// TODO: Ex 2 - Count the archived claims before the update
int start = CountArchivedRewardsClaimed();
20 Lab Answer Key: Handling Multi-User Scenarios by Using Object Services

8. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived
claims after the update and test item in the task list. This task is located in the
UpdateRewardsClaimTest method:
In the task list, double-click the TODO: Ex2 - Count the archived claims after the update and
test item.
9. Immediately after the comment, add code to count the number of archived claims by calling the
CountArchivedRewardsClaimed method, and verify that the number of archived rewards has
increased by one.
Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex 2 - Count the archived claims after the update and test
Dim finish As Integer = CountArchivedRewardsClaimed()
Assert.AreEqual(start + 1, finish)

[Visual C#]
// TODO: Ex 2 - Count the archived claims after the update and test
int finish = CountArchivedRewardsClaimed();
Assert.AreEqual(start + 1, finish);

10. Save the DataAccessLayerTest file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 6: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed, including the CreateRewardsClaimTest and
UpdateRewardsClaimTest tests.
4. Close the solution, and then close Visual Studio:
On the File menu, click Close Solution.
On the File menu, click Exit.
Lab Answer Key: Building Optimized Solutions by Using Object Services 1

Module 6
Lab Answer Key: Building Optimized Solutions by Using
Object Services
Contents:
Exercise 1: Improving the Performance of Query Operations 2
Exercise 2: Improving the Performance of Update Operations 9
2 Lab Answer Key: Building Optimized Solutions by Using Object Services

Lab 6: Building Optimized Solutions by Using


Object Services
Exercise 1: Improving the Performance of Query Operations
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-06 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex1\Starter or
E:\Labfiles\Lab06\CS\Ex1\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab06\VB\Ex1\Starter folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab06\CS\Ex1\Starter folder, click DAL.sln, and then click Open.

Task 3: Print timing information during query execution


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Return all contacts
with timings item in the task list. This task is located in the GetContactListDetail method:
In the task list, double-click the TODO: Ex1 - Return all contacts with timings item.
3. Delete the comment in the GetContactListDetail method:
Select the comment in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Instantiate and start two Stopwatch objects called totaltime and stagetime.
b. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.

Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all
data access operations by using this context object.
Lab Answer Key: Building Optimized Solutions by Using Object Services 3

c. Print the value of the ElapsedTime property from the stagetime object, and then restart the
Stopwatch.
d. Retrieve an ObjectQuery object from the context's Contacts property.
e. Print the value of the ElapsedTime property from the stagetime object, and then restart the
Stopwatch.
f. Define and execute a Language-Integrated Query (LINQ) query to retrieve all of the contacts
from the ObjectQuery object, and then save the results to a List object.
g. Print the value of the ElapsedTime property from the stagetime object, and then restart the
Stopwatch.
h. Print the value of the ElapsedTime property from the totaltime object, and then restart the
Stopwatch.
i. Return the List object.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetContactListDetail() As List(Of Contact)
Dim totaltime As Stopwatch = Stopwatch.StartNew()
Dim stagetime As Stopwatch = Stopwatch.StartNew()

' Check you have an ObjectContext object.


If entities Is Nothing Then entities = New AdventureWorksEntities()

Console.WriteLine("Context Creation Time: " & vbTab & vbTab & "{0,6} ms",
stagetime.ElapsedMilliseconds)
stagetime.Restart()

Dim contacts As ObjectQuery(Of Contact) = entities.Contacts

Console.WriteLine("ObjectQuery Creation Time: " & vbTab & "{0,6} ms",


stagetime.ElapsedMilliseconds)
stagetime.Restart()

Dim query = From c In contacts


Select c

Dim results As List(Of Contact) = query.ToList()

Console.WriteLine("Query Run Time: " & vbTab & vbTab & "{0,6} ms",
stagetime.ElapsedMilliseconds)
stagetime.Restart()

Console.WriteLine("Total Time: " & vbTab & vbTab & vbTab & "{0,6} ms",
totaltime.ElapsedMilliseconds)
Return results

End Function

[Visual C#]
public List<Contact> GetContactListDetail()
{
Stopwatch totaltime = Stopwatch.StartNew();
Stopwatch stagetime = Stopwatch.StartNew();

// Check you have an ObjectContext object.


if (entities == null) entities = new AdventureWorksEntities();

Console.WriteLine("Context Creation Time: \t\t{0,6} ms",


4 Lab Answer Key: Building Optimized Solutions by Using Object Services

stagetime.ElapsedMilliseconds);
stagetime.Restart();

ObjectQuery<Contact> contacts = entities.Contacts;

Console.WriteLine("ObjectQuery Creation Time: \t{0,6} ms",


stagetime.ElapsedMilliseconds);
stagetime.Restart();

var query = from c in contacts


select c;
List<Contact> results = query.ToList();

Console.WriteLine("Query Run Time: \t\t{0,6} ms",


stagetime.ElapsedMilliseconds);
stagetime.Restart();

Console.WriteLine("Total Time: \t\t\t{0,6} ms",


totaltime.ElapsedMilliseconds);
return results;
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 4: Add code to define a compiled LINQ query


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Define the compiled
LINQ query item in the task list. This task is located in the DataAccessLayer class:
In the task list, double-click the TODO: Ex1 - Define the compiled LINQ query item.
3. Immediately after the comment, add code to define a compiled LINQ query called compiledQuery
by using a static function. The query should return all of the contact entities from the Entity Data
Model (EDM).
Your code should resemble the following code example.

[Visual Basic]
Public Shared compiledQuery As Func(Of AdventureWorksEntities, ObjectQuery(Of Contact))
=
System.Data.Objects.CompiledQuery.Compile(Of AdventureWorksEntities,
ObjectQuery(Of Contact))(Function(entities) entities.Contacts)

[Visual C#]
public static Func<AdventureWorksEntities, ObjectQuery<Contact>>
compiledQuery = CompiledQuery.Compile<AdventureWorksEntities, ObjectQuery<Contact>>(
entities => entities.Contacts);

4. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.
Lab Answer Key: Building Optimized Solutions by Using Object Services 5

Task 5: Add code to invoke the compiled LINQ query


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Retrieve all contacts
using the compiled query item in the task list. This task is located in the
GetContactListEntityCompiledLINQ method:
In the task list, double-click the TODO: Ex1 - Retrieve all contacts using the compiled query
item.
3. Delete the comment in the GetContactListEntityCompiledLINQ method:
Select the comment in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Obtain an ObjectQuery object by invoking the compiled LINQ query.
c. If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption
property to NoTracking.
d. Return the contact entities in a List object.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetContactListEntityCompiledLINQ() As List(Of Contact)

' Check you have an ObjectContext object.


If entities Is Nothing Then entities = New AdventureWorksEntities()

Dim contacts As ObjectQuery(Of Contact) = compiledQuery.Invoke(entities)

If NoTracking Then contacts.MergeOption = MergeOption.NoTracking

Return contacts.ToList()

End Function

[Visual C#]
public List<Contact> GetContactListEntityCompiledLINQ()
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

ObjectQuery<Contact> contacts = compiledQuery.Invoke(entities);


if (NoTracking) contacts.MergeOption = MergeOption.NoTracking;

return contacts.ToList<Contact>();
}

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.
6 Lab Answer Key: Building Optimized Solutions by Using Object Services

Task 6: Add code to retrieve all of the contact entities by using Entity SQL
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Retrieve all
contacts using Entity SQL item in the task list. This task is located in the
GetContactListEntityQuery method:
In the task list, double-click the TODO: Ex1 - Retrieve all contacts using Entity SQL item.
3. Delete the comment in the GetContactListEntityQuery method:
Select the comment in the method, and then press DELETE.
4. Write code that performs the following tasks:
a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the
AdventureWorksEntities context object.
b. Obtain an ObjectQuery object by creating a query that uses Entity SQL to retrieve all of the
contact entities from the EDM.
c. If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption
property to NoTracking.
d. Return the contact entities in a List object.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetContactListEntityQuery() As List(Of Contact)

' Check you have an ObjectContext object.


If (Entities is Nothing) Then
Entities = New AdventureWorksEntities()
End If

Dim contacts As ObjectQuery(Of Contact) = entities.CreateQuery(Of Contact)("Select


value c from Contacts as c")
If (NoTracking) Then
contacts.MergeOption = MergeOption.NoTracking
End If

Return contacts.ToList()

End Function

[Visual C#]
public List<Contact> GetContactListEntityQuery()
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();

ObjectQuery<Contact> contacts = entities.CreateQuery<Contact>("Select value c from


Contacts as c");
if (NoTracking) contacts.MergeOption = MergeOption.NoTracking;

return contacts.ToList<Contact>();
}
Lab Answer Key: Building Optimized Solutions by Using Object Services 7

5. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 7: Modify the GetContactList method to check the NoTracking variable


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Check
NoTracking item in the task list. This task is located in the GetContactList method:
In the task list, double-click the TODO: Ex1 - Check NoTracking item.
3. Modify the line of code immediately below the comment to check whether the value of the
NoTracking variable is true before you set the MergeOption property to NoTracking.
Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex1 - Check NoTracking
If NoTracking Then contacts.MergeOption = MergeOption.NoTracking

[Visual C#]
// TODO: Ex1 - Check NoTracking
if (NoTracking) contacts.MergeOption = MergeOption.NoTracking;

4. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 8: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. Observe the timing results obtained by running the TimingTests application:
a. In the command window, on the Detailed Timing for GetContactList() page, make a note of
the Total Time values, and then press ENTER.
b. In the command window, on the Compare Implementations of GetContactList() page, write
down the Average values, and then press ENTER.

Task 9: Pre-generate views to improve query performance


1. Open the AdventureWorksEDM.edmx file and change the Metadata Artifact Processing property to
Copy to Output Directory:
a. In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
b. In the Model Browser window, click anywhere in the white space.
8 Lab Answer Key: Building Optimized Solutions by Using Object Services

c. In the Properties window, change the Metadata Artifact Processing property to Copy to
Output Directory.
2. Save the AdventureWorksEDM.edmx file and build the solution:
a. On the File menu, click Save AdventureWorksEDM.edmx.
b. On the Build menu, click Build Solution.
3. Add view generation to the DAL project by using EdmGen.exe to generate the views during the pre-
build event:
a. In Solution Explorer, right-click the DAL project, and then click Properties.
b. If you are using Visual Basic, on the Compile panel, click Build Events, add the following
command to the Pre-build event command line, and then click OK.

"%windir%\Microsoft.NET\Framework\v4.0.30319\EdmGen.exe" /nologo /language:VB


/mode:ViewGeneration "/inssdl:$(TargetDir)AdventureWorksEDM.ssdl"
"/incsdl:$(TargetDir)AdventureWorksEDM.csdl" "/inmsl:$(TargetDir)AdventureWorksEDM.msl"
"/outviews:$(ProjectDir)AdventureWorksEDM.Views.vb"

c. If you are using Visual C#, on the Build Events panel, add the following command to the Pre-
build event command line.

"%windir%\Microsoft.NET\Framework\v4.0.30319\EdmGen.exe" /nologo /language:CSharp


/mode:ViewGeneration "/inssdl:$(TargetDir)AdventureWorksEDM.ssdl"
"/incsdl:$(TargetDir)AdventureWorksEDM.csdl" "/inmsl:$(TargetDir)AdventureWorksEDM.msl"
"/outviews:$(ProjectDir)AdventureWorksEDM.Views.cs"

Note: This command is one continuous line.

d. On the File menu, click Save All.


e. On the Build menu, click Build Solution.
4. Add the generated views to the project:
a. In Solution Explorer, right-click the DAL project, point to Add, and then click Existing Item.
b. If you are using Visual Basic, in the Add Existing Item - DAL dialog box, click
AdventureWorksEDM.Views.vb, and then click Add.
c. If you are using Visual C#, in the Add Existing Item - DAL dialog box, click
AdventureWorksEDM.Views.cs, and then click Add.
d. On the Build menu, click Build Solution.
5. Update the connection strings in the TimingTests project to use the new metadata resources:
a. In Solution Explorer, in the TimingTests project, right-click App.Config, and then click Open.
b. Comment out the Standard Connection String for AdventureWorksEntities, and uncomment
the Lab 6 Connection String for pre-generated views. Your code should resemble the
following code example.

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<connectionStrings>
<!-- Standard Connection String -->
<!--
<add name="AdventureWorksEntities"
connectionString="metadata=res://*/AdventureWorksEDM.csdl|res://*/AdventureWorksEDM.ssdl
|res://*/AdventureWorksEDM.msl;provider=System.Data.SqlClient;provider connection
Lab Answer Key: Building Optimized Solutions by Using Object Services 9

string=&quot;Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks;Integrated


Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />
-->

<!-- Lab 6 Connection String for pre-generated views -->


<add name="AdventureWorksEntities"
connectionString="metadata=..\..\..\DAL\bin\Debug\AdventureWorksEDM.csdl|..\..\..\DAL\bi
n\Debug\AdventureWorksEDM.ssdl|..\..\..\DAL\bin\Debug\AdventureWorksEDM.msl;provider=Sys
tem.Data.SqlClient;provider connection string=&quot;Data Source=.\SQLEXPRESS;Initial
Catalog=AdventureWorks;Integrated Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />

<add name="AdventureWorksArchivedEntities"
connectionString="metadata=res://*/AdventureWorksArchivedEDM.csdl|res://*/AdventureWorks
ArchivedEDM.ssdl|res://*/AdventureWorksArchivedEDM.msl;provider=System.Data.SqlClient;pr
ovider connection string=&quot;Data Source=.\SQLEXPRESS;Initial
Catalog=AdventureWorks;Integrated Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>

c. On the File menu, click Save App.Config.


6. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
7. Observe the timing results obtained by running the TimingTests application:
a. In the command window, on the Detailed Timing for GetContactList() page, write down the
Total Time values, and then press ENTER.
b. In the command window, on the Compare Implementations of GetContactList() page, write
down the Average values, and then press ENTER.
8. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Improving the Performance of Update Operations


Task 1: Open the starter project for this exercise
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex2\Starter or
E:\Labfiles\Lab06\CS\Ex2\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab06\VB\Ex2\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab06\CS\Ex2\Starter folder, click DAL.sln, and then click Open.

Task 2: Modify the CreateRewardsClaim method to run asynchronously


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
10 Lab Answer Key: Building Optimized Solutions by Using Object Services

2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In
CreateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in
the CreateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaim, instantiate a
BackgroundWorker item.
3. Immediately after the comment, write code that performs the following tasks:
a. Instantiate a new BackgroundWorker object.
b. Set the WorkerSupportsCancellation property to false.
c. Set the WorkerReportsProgress property to false.
Your code should resemble the following code example.

[Visual Basic]
Dim bw As New BackgroundWorker()
bw.WorkerSupportsCancellation = False
bw.WorkerReportsProgress = False

[Visual C#]
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = false;
bw.WorkerReportsProgress = false;

4. Locate the next comment by double-clicking the comment TODO: Ex2 - In CreateRewardsClaim,
place existing code in DoWork item in the task list. This task is located in the CreateRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaim, place existing code in
DoWork item.
5. Assign the existing code in the CreateRewardsClaim method to the BackgroundWorker object's
DoWork event handler by using a lambda expression. Replace the two existing return statements
with statements that assign the new claim to the Result property of the DoWork event's args
parameter.
Your code should resemble the following code example.

Note: New or modified code is highlighted in bold.

[Visual Basic]
AddHandler bw.DoWork,
Sub(o, args)

' Get an ObjectContext object.


Using entities As New AdventureWorksEntities()

Try

' Get the next valid claim id.


claim.ClaimID = GetNextClaimID()

' Add the RewardsClaimed to the entity set.


entities.RewardsClaimed.AddObject(claim)

' Get the contact and decrement the points.


Lab Answer Key: Building Optimized Solutions by Using Object Services 11

Dim contact As Contact = claim.Contact


contact.CurrentPoints =
contact.CurrentPoints - claim.PointsUsed
contact.ModifiedDate = DateTime.Now

' Begin a distributed transaction.


Using scope As New TransactionScope()

' Save all the changes to the database.


entities.SaveChanges()

' Use a different ObjectContext object.


Using archivedEntities As _
New AdventureWorksArchivedEntities()

' Create archived rewards claim.


Dim archivedClaim As ArchivedRewardsClaimed =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(
0, claim.ClaimID, claim.PointsUsed, claim.RewardID,
claim.ContactID, DateTime.Now)

archivedEntities.AddToArchivedRewardsClaimed(
archivedClaim)

archivedEntities.SaveChanges()

End Using

' The Complete method commits the transaction.


' If an exception is thrown, Complete is
' not called and the transaction is rolled back.
scope.Complete()
End Using

' Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, contact)
entities.Refresh(RefreshMode.StoreWins, claim)

args.Result = claim

Catch ex As TransactionAbortedException

entities.Refresh(RefreshMode.StoreWins, claim.Contact)
entities.Refresh(RefreshMode.StoreWins, claim)
Throw New DALException("Could not save " & _
"RewardsClaim and ArchivedRewardsClaim in " & _
"transaction", ex)

Catch ex As OptimisticConcurrencyException

' The contact could have been modified so


' get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, claim.Contact)

' Deduct points from customer.


claim.Contact.CurrentPoints =
claim.Contact.CurrentPoints - claim.PointsUsed
claim.Contact.ModifiedDate = DateTime.Now

' Try to save all the changes again.


entities.SaveChanges()

' Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, claim.Contact)
12 Lab Answer Key: Building Optimized Solutions by Using Object Services

entities.Refresh(RefreshMode.StoreWins, claim)

args.Result = claim
Catch ex As InvalidOperationException

Throw New DALException(


"There was a problem creating the RewardClaim",
ex)

Catch ex As UpdateException

Throw New DALException("There was a problem " & _


"saving the RewardClaim to the database", ex)

End Try

End Using
End Sub

[Visual C#]
bw.DoWork += (o, args) =>
{
// Get an ObjectContext object.
using (AdventureWorksEntities entities = new
AdventureWorksEntities())
{
try
{
// Get the next valid claim ID.
claim.ClaimID = GetNextClaimID();

// Add the RewardsClaimed to the entity set.


entities.RewardsClaimed.AddObject(claim);

// Get the contact and decrement the points.


Contact contact = claim.Contact;
contact.CurrentPoints -= claim.PointsUsed;
contact.ModifiedDate = DateTime.Now;

// Begin a distributed transaction.


using (TransactionScope scope = new TransactionScope())
{
// Save all of the changes to the database.
entities.SaveChanges();

// Use a different ObjectContext object.


using (AdventureWorksArchivedEntities archivedEntities
= new AdventureWorksArchivedEntities())
{
// Create an archived rewards claim.
ArchivedRewardsClaimed archivedClaim =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed
(0, claim.ClaimID, claim.PointsUsed,
claim.RewardID,
claim.ContactID, DateTime.Now);
archivedEntities.AddToArchivedRewardsClaimed(
archivedClaim);

archivedEntities.SaveChanges();

}
Lab Answer Key: Building Optimized Solutions by Using Object Services 13

// The Complete method commits the transaction.


// If an exception is thrown,
// the Complete method is not called,
// and the transaction is rolled back.
scope.Complete();
}

// Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, contact);
entities.Refresh(RefreshMode.StoreWins, claim);

args.Result = claim;
}
catch (TransactionAbortedException ex)
{
entities.Refresh(RefreshMode.StoreWins, claim.Contact);
entities.Refresh(RefreshMode.StoreWins, claim);
throw new DALException("Could not save RewardsClaim and
ArchivedRewardsClaim in transaction", ex);
}
catch (OptimisticConcurrencyException)
{
// The contact may have been modified, so
// get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, claim.Contact);

// Deduct points from customer.


claim.Contact.CurrentPoints -= claim.PointsUsed;
claim.Contact.ModifiedDate = DateTime.Now;

// Try to save all of the changes again.


entities.SaveChanges();

// Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, claim.Contact);
entities.Refresh(RefreshMode.StoreWins, claim);

args.Result = claim;
}
catch (InvalidOperationException ex)
{
throw new DALException(
"There was a problem creating the RewardClaim", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem saving the
RewardClaim to the database", ex);
}
}
};

6. Locate the next comment by double-clicking the comment TODO: Ex2 - In CreateRewardsClaim,
implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is
located in the CreateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaim, implement the
BackgroundWorkers RunWorkerCompleted event item.
7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted
event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted
method with false as the first parameter, the error message as the second parameter, and -1 as the
third parameter. If the DoWork event completed without errors, call the
14 Lab Answer Key: Building Optimized Solutions by Using Object Services

OnDataModificationCompleted method with true as the first parameter, a success message as the
second parameter, and the claimID property of the new claim as the third parameter.
Your code should resemble the following code example.

[Visual Basic]
AddHandler bw.RunWorkerCompleted,
Sub(o, args)

If args.Error IsNot Nothing Then


OnDataModificationCompleted(False, args.Error.Message, -1)

Else
OnDataModificationCompleted(True,
"Created new RewardsClaimed with ID: " &
DirectCast(args.Result, RewardsClaimed).ClaimID,
DirectCast(args.Result, RewardsClaimed).ClaimID)
End If
End Sub
[Visual C#]
bw.RunWorkerCompleted += (o, args) =>
{
if (args.Error != null)
OnDataModificationCompleted(false, args.Error.Message, -1);
else
OnDataModificationCompleted(true,
"Created new RewardsClaimed with ID: " &
((RewardsClaimed)args.Result).ClaimID,
((RewardsClaimed)args.Result).ClaimID);
};

8. Locate the next comment by double-clicking the comment TODO: Ex2 - In CreateRewardsClaim,
start the BackgroundWorker item in the task list. This task is located in the CreateRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaim, start the
BackgroundWorker item.
9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
Your code should resemble the following code example.

[Visual Basic]
bw.RunWorkerAsync()

[Visual C#]
bw.RunWorkerAsync();

10. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 3: Modify the UpdateRewardsClaim method to run asynchronously


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
Lab Answer Key: Building Optimized Solutions by Using Object Services 15

b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In
UpdateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in
the UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaim, instantiate a
BackgroundWorker item.
3. Immediately after the comment, write code that performs the following tasks:
a. Instantiate a new BackgroundWorker object.
b. Set the WorkerSupportsCancellation property to false.
c. Set the WorkerReportsProgress property to false.
Your code should resemble the following code example.

[Visual Basic]
Dim bw As New BackgroundWorker()
bw.WorkerSupportsCancellation = False
bw.WorkerReportsProgress = False

[Visual C#]
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = false;
bw.WorkerReportsProgress = false;

4. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,
place existing code in DoWork item in the task list. This task is located in the
UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaim, place existing code in
DoWork item.
5. Assign the existing code in the UpdateRewardsClaim method to the BackgroundWorker object's
DoWork event handler by using a lambda expression. Replace the two existing return statements
with statements that assign the updated claim to the Result property of the DoWork event's args
parameter.
Your code should resemble the following code example.

Note: New or modified code is highlighted in bold.

[Visual Basic]
AddHandler bw.DoWork,
Sub(o, args)

Dim originalPoints As Integer = 0


Dim newPoints As Integer = 0
Dim rewardClaimToModify As RewardsClaimed = Nothing

' Get an ObjectContext object.


Using entities As New AdventureWorksEntities()

Try

' Get the entity key you need.


Dim key As EntityKey = rewardClaim.EntityKey
16 Lab Answer Key: Building Optimized Solutions by Using Object Services

Dim objectClaim As Object = Nothing

' Make sure that the entity to modify is loaded.


If entities.TryGetObjectByKey(key, objectClaim) Then

rewardClaimToModify =
DirectCast(objectClaim, RewardsClaimed)

' Make sure you're working with


' the latest version of the claim.
entities.Refresh(
RefreshMode.StoreWins, rewardClaimToModify)

' Get the old and new points ready


' to update the contact.
originalPoints = rewardClaimToModify.PointsUsed
newPoints = rewardClaim.PointsUsed

' Copy all the changes over from


' the detached RewardClaim entity.

entities.ApplyCurrentValues(
key.EntitySetName, rewardClaim)

If originalPoints <> newPoints Then

' Update conatcts points.


rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints +
originalPoints

rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints -
newPoints

rewardClaimToModify.Contact.ModifiedDate =
DateTime.Now

End If

End If

' Begin a distributed transaction.


Using scope As New TransactionScope()

' Save all the changes to the database.


entities.SaveChanges()

' Use a different ObjectContext.


Using archivedEntities As New _
AdventureWorksArchivedEntities()

' Create archived rewards claim.


Dim archivedClaim As ArchivedRewardsClaimed =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(
0, rewardClaimToModify.ClaimID,
rewardClaimToModify.PointsUsed,
rewardClaimToModify.RewardID,
rewardClaimToModify.ContactID, DateTime.Now)

archivedEntities.AddToArchivedRewardsClaimed(
archivedClaim)
Lab Answer Key: Building Optimized Solutions by Using Object Services 17

archivedEntities.SaveChanges()

End Using

' The Complete method commits the transaction.


' If an exception is thrown,
' Complete is not called and the
' transaction is rolled back.
scope.Complete()
End Using

' Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify)

args.Result = rewardClaimToModify

Catch ex As TransactionAbortedException

entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify)
Throw New DALException("Could not save " & _
"RewardsClaim and ArchivedRewardsClaim in " & _
"transaction", ex)

Catch ex As OptimisticConcurrencyException

' The contact could have been modified


' so get the latest version of the
' contact from the database.
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)

' It's very unlikely the claim has been modified


' as you've only just refreshed it.
entities.Refresh(RefreshMode.ClientWins,
rewardClaimToModify)

' Adjust points for customer.


rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints +
originalPoints

rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints -
newPoints

rewardClaimToModify.Contact.ModifiedDate =
DateTime.Now

' Try to save all the changes again.


entities.SaveChanges()

' Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact)
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify)

args.Result = rewardClaimToModify
18 Lab Answer Key: Building Optimized Solutions by Using Object Services

Catch ex As InvalidOperationException

Throw New DALException("There was a problem " & _


"modifying the RewardClaim", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem " & _


"saving the RewardClaim changes to the " & _
"database", ex)

End Try

End Using

End Sub

[Visual C#]
bw.DoWork += (o, args) =>
{
int originalPoints = 0;
int newPoints = 0;
RewardsClaimed rewardClaimToModify = null;

// Get an an ObjectContext object.


using (AdventureWorksEntities entities =
new AdventureWorksEntities())
{
try
{
// Get the entity key you need.
EntityKey key = rewardClaim.EntityKey;

object objectClaim = null;

// Make sure that the entity to modify is loaded.


if (entities.TryGetObjectByKey(key, out objectClaim))
{
rewardClaimToModify = (RewardsClaimed)objectClaim;

// Make sure you are working with the latest version


// of the claim.
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify);

// Get the old and new points ready to update the


// contact.
originalPoints = rewardClaimToModify.PointsUsed;
newPoints = rewardClaim.PointsUsed;

// Copy all of the changes over from the detached


// RewardClaim entity.
entities.ApplyCurrentValues(key.EntitySetName,
rewardClaim);

if (originalPoints != newPoints)
{
// Update contacts points.
rewardClaimToModify.Contact.CurrentPoints +=
originalPoints;
Lab Answer Key: Building Optimized Solutions by Using Object Services 19

rewardClaimToModify.Contact.CurrentPoints -=
newPoints;
rewardClaimToModify.Contact.ModifiedDate =
DateTime.Now;
}
}

// Begin a distributed transaction.


using (TransactionScope scope = new TransactionScope())
{
// Save all of the changes to the database.
entities.SaveChanges();

// Use a different ObjectContext object.


using (AdventureWorksArchivedEntities archivedEntities
= new AdventureWorksArchivedEntities())
{
// Create an archived rewards claim.
ArchivedRewardsClaimed archivedClaim =
ArchivedRewardsClaimed.CreateArchivedRewardsClaimed(0,
rewardClaimToModify.ClaimID,
rewardClaimToModify.PointsUsed,
rewardClaimToModify.RewardID,
rewardClaimToModify.ContactID, DateTime.Now);
archivedEntities.AddToArchivedRewardsClaimed(
archivedClaim);

archivedEntities.SaveChanges();

}
// The Complete method commits the transaction.
// If an exception is thrown,
// the Complete method is not called,
// and the transaction is rolled back.
scope.Complete();
}

// Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact);
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify);

args.Result = rewardClaimToModify;
}
catch (TransactionAbortedException ex)
{
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact);
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify);
throw new DALException("Could not save RewardsClaim and
ArchivedRewardsClaim in transaction", ex);
}
catch (OptimisticConcurrencyException)
{
// The contact may have been modified,
// so get the latest version of the contact
// from the database.
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact);

// It is very unlikely the claim has been modified


// because you have only just refreshed it.
20 Lab Answer Key: Building Optimized Solutions by Using Object Services

entities.Refresh(RefreshMode.ClientWins,
rewardClaimToModify);

// Adjust points for customer.


rewardClaimToModify.Contact.CurrentPoints
+= originalPoints;
rewardClaimToModify.Contact.CurrentPoints
-= newPoints;
rewardClaimToModify.Contact.ModifiedDate = DateTime.Now;

// Try to save all of the changes again.


entities.SaveChanges();

// Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify.Contact);
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToModify);

args.Result = rewardClaimToModify;
}
catch (InvalidOperationException ex)
{
throw new DALException("There was a problem modifying the
RewardClaim", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem saving the
RewardClaim changes to the database", ex);
}
}
};

6. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,
implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is
located in the UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaim, implement the
BackgroundWorkers RunWorkerCompleted event item.
7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted
event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted
method with false as the first parameter, the error message as the second parameter, and -1 as the
third parameter. If the DoWork event completed without errors, call the
OnDataModificationCompleted method with true as the first parameter, a success message as the
second parameter, and the claimID property of the updated claim as the third parameter.
Your code should resemble the following code example.

[Visual Basic]
AddHandler bw.RunWorkerCompleted,
Sub(o, args)

If args.Error IsNot Nothing Then


OnDataModificationCompleted(False, args.Error.Message, -1)

Else
OnDataModificationCompleted(True,
"Updated RewardsClaimed with ID: " &
DirectCast(args.Result, RewardsClaimed).ClaimID,
DirectCast(args.Result, RewardsClaimed).ClaimID)
Lab Answer Key: Building Optimized Solutions by Using Object Services 21

End If

End Sub

[Visual C#]
bw.RunWorkerCompleted += (o, args) =>
{
if (args.Error != null)
OnDataModificationCompleted(false, args.Error.Message, -1);
else
OnDataModificationCompleted(true,
"Updated RewardsClaimed with ID: " &
((RewardsClaimed)args.Result).ClaimID,
((RewardsClaimed)args.Result).ClaimID);
};

8. Locate the next comment TODO: Ex2 - In UpdateRewardsClaim, start the BackgroundWorker
item in the task list. This task is located in the UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaim, start the
BackgroundWorker item.
9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
Your code should resemble the following code example.

[Visual Basic]
bw.RunWorkerAsync()

[Visual C#]
bw.RunWorkerAsync();

10. Save the DataAccessLayer code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 4: Modify the DeleteRewardsClaim method to run asynchronously


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In
DeleteRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in
the DeleteRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In DeleteRewardsClaim, instantiate a
BackgroundWorker item.
3. Immediately after the comment, write code that performs the following tasks:
a. Instantiate a new BackgroundWorker object.
b. Set the WorkerSupportsCancellation property to false.
22 Lab Answer Key: Building Optimized Solutions by Using Object Services

c. Set the WorkerReportsProgress property to false.

Your code should resemble the following code example.

[Visual Basic]
Dim bw As New BackgroundWorker()
bw.WorkerSupportsCancellation = False
bw.WorkerReportsProgress = False

[Visual C#]
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = false;
bw.WorkerReportsProgress = false;

4. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,
place existing code in DoWork item in the task list. This task is located in the DeleteRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - In DeleteRewardsClaim, place existing code in
DoWork item.
5. Assign the existing code in the DeleteRewardsClaim method to the BackgroundWorker object's
DoWork event handler by using a lambda expression. Delete the two existing return statements.
Your code should resemble the following code example.

Note: New or modified code is highlighted in bold.

[Visual Basic]
AddHandler bw.DoWork,
Sub(o, args)

Dim rewardClaimToDelete As RewardsClaimed = Nothing


Dim relatedContact As Contact = Nothing

' Get an ObjectContext object.


Using entities As New AdventureWorksEntities()

Try

' Get the entity key of the claim to delete.


Dim key As New EntityKey(
"AdventureWorksEntities.RewardsClaimed",
"ClaimID", claimId)

Dim objectClaim As Object = Nothing


' Make sure that the entity to modify is loaded.
If entities.TryGetObjectByKey(key, objectClaim) Then

rewardClaimToDelete = DirectCast(
objectClaim, RewardsClaimed)

' Make sure you're working with


' the latest version of the claim.

entities.Refresh(RefreshMode.StoreWins,
rewardClaimToDelete)
Lab Answer Key: Building Optimized Solutions by Using Object Services 23

' Give the points back to the contact.


relatedContact = rewardClaimToDelete.Contact
relatedContact.CurrentPoints =
relatedContact.CurrentPoints +
rewardClaimToDelete.PointsUsed
relatedContact.ModifiedDate = DateTime.Now

' Delete the object.


entities.DeleteObject(rewardClaimToDelete)
End If

' Save the changes to the database.


entities.SaveChanges()

' Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins,
relatedContact)

Catch ex As OptimisticConcurrencyException
' The contact could have been modified so
' get the latest version from the database.
entities.Refresh(
RefreshMode.StoreWins, relatedContact)

' Give the points back to the contact.


relatedContact.CurrentPoints =
relatedContact.CurrentPoints +
rewardClaimToDelete.PointsUsed
relatedContact.ModifiedDate = DateTime.Now

' Try to save all the changes again.


entities.SaveChanges()

' Make sure the correct datetime is in the context.


entities.Refresh(
RefreshMode.StoreWins, relatedContact)

Catch ex As InvalidOperationException

Throw New DALException("There was a problem " & _


"deleting the RewardsClaim", ex)

Catch ex As UpdateException

Throw New DALException("There was a problem " & _


"deleting the RewardClaim from the database", ex)

End Try

End Using

End Sub

[Visual C#]
bw.DoWork += (o, args) =>
{
RewardsClaimed rewardClaimToDelete = null;
Contact relatedContact = null;

// Get an ObjectContext object.


using (AdventureWorksEntities entities = new AdventureWorksEntities())
24 Lab Answer Key: Building Optimized Solutions by Using Object Services

{
try
{
// Get the entity key of the claim to delete.
EntityKey key =
new EntityKey("AdventureWorksEntities.RewardsClaimed",
"ClaimID", claimId);
object objectClaim = null;
// Make sure that the entity to modify is loaded.
if (entities.TryGetObjectByKey(key, out objectClaim))
{
rewardClaimToDelete = (RewardsClaimed)objectClaim;
// Make sure you are working with the latest version
// of the claim.
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToDelete);

// Give the points back to the contact.


relatedContact = rewardClaimToDelete.Contact;
relatedContact.CurrentPoints +=
rewardClaimToDelete.PointsUsed;
relatedContact.ModifiedDate = DateTime.Now;

// Delete the object.


entities.DeleteObject(rewardClaimToDelete);
}

// Save the changes to the database.


entities.SaveChanges();

// Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, relatedContact);
}
catch (OptimisticConcurrencyException)
{
// The contact may have been modified, so
// get the latest version from the database.
entities.Refresh(RefreshMode.StoreWins, relatedContact);

// Give the points back to the contact.


relatedContact.CurrentPoints +=
rewardClaimToDelete.PointsUsed;
relatedContact.ModifiedDate = DateTime.Now;
// Try to save all of the changes again.
entities.SaveChanges();

// Make sure the correct datetime is in the context.


entities.Refresh(RefreshMode.StoreWins, relatedContact);
}
catch (InvalidOperationException ex)
{
throw new DALException(
"There was a problem deleting the RewardsClaim", ex);
}
catch (UpdateException ex)
{
throw new DALException("There was a problem deleting the
RewardClaim from the database", ex);
}
}
};
Lab Answer Key: Building Optimized Solutions by Using Object Services 25

6. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,
implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is
located in the DeleteRewardsClaim method:
In the task list, double-click the TODO: Ex2 - In DeleteRewardsClaim, implement the
BackgroundWorkers RunWorkerCompleted event item.
7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted
event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted
method with false as the first parameter, the error message as the second parameter, and -1 as the
third parameter. If the DoWork event completed without errors, call the
OnDataModificationCompleted method with true as the first parameter, a success message as the
second parameter, and the claimID property of the deleted claim as the third parameter.
Your code should resemble the following code example.

[Visual Basic]
AddHandler bw.RunWorkerCompleted,
Sub(o, args)

If args.Error IsNot Nothing Then


OnDataModificationCompleted(False, args.Error.Message, -1)
Else
OnDataModificationCompleted(True,
"Deleted RewardsClaimed with ID: " & claimId, claimId)
End If
End Sub

[Visual C#]
bw.RunWorkerCompleted += (o, args) =>
{
if (args.Error != null)
OnDataModificationCompleted(false, args.Error.Message, -1);
else
OnDataModificationCompleted(true,
"Deleted RewardsClaimed with ID: " & claimId, claimId);
};

8. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,
start the BackgroundWorker item in the task list. This task is located in the DeleteRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - In DeleteRewardsClaim, start the
BackgroundWorker item.
9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
Your code should resemble the following code example.

[Visual Basic]
bw.RunWorkerAsync()

[Visual C#]
bw.RunWorkerAsync();

10. Save the DataAccessLayer code file:


26 Lab Answer Key: Building Optimized Solutions by Using Object Services

a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 5: Modify your unit tests to verify your asynchronous code


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex2 - In
CreateRewardsClaimTest, call CreateRewardsClaim item in the task list. This task is located in the
CreateRewardsClaimTest method:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaimTest, call
CreateRewardsClaim item.
3. Review the existing code in the CreateRewardsClaimTest method.
4. Immediately after the comment, call the CreateRewardsClaim method in the data access layer,
passing the test claim called claim as a parameter.
Your code should resemble the following code example.

[Visual Basic]
dal.CreateRewardsClaim(claim)

[Visual C#]
dal.CreateRewardsClaim(claim);

5. Locate the next TODO comment in the CreateRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In CreateRewardsClaimTest, check the values retrieved from the
database item in the task list:
In the task list, double-click the TODO: Ex2 - In CreateRewardsClaimTest, check the values
retrieved from the database item.
6. Immediately after the comment, add code to check that the property values of the claim object
match those of the lastClaim object.
Your code should resemble the following code example.

[Visual Basic]
' Check that it was correctly saved.
Assert.AreEqual(createResult, True)
Assert.AreEqual(claim.ClaimID, lastClaim.ClaimID)
Assert.AreEqual(claim.PointsUsed, lastClaim.PointsUsed)
Assert.AreEqual(claim.RewardID, lastClaim.RewardID)
Assert.AreEqual(claim.ContactID, lastClaim.ContactID)

[Visual C#]
// Check that it was correctly saved.
Assert.AreEqual(createResult, true);
Assert.AreEqual(claim.ClaimID, lastClaim.ClaimID);
Assert.AreEqual(claim.PointsUsed, lastClaim.PointsUsed);
Assert.AreEqual(claim.RewardID, lastClaim.RewardID);
Assert.AreEqual(claim.ContactID, lastClaim.ContactID);
Lab Answer Key: Building Optimized Solutions by Using Object Services 27

7. Locate the first TODO comment in the UpdateRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In UpdateRewardsClaimTest, modify the claim and save the changes
item in the task list:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaimTest, modify the claim
and save the changes item.
8. Review the existing code in the UpdateRewardsClaimTest method.
9. Immediately after the comment, modify the PointsUsed and RewardID properties of the claim
object, and call the UpdateRewardsClaim method in the data access layer, passing the test claim
called claim as a parameter.
Your code should resemble the following code example.

[Visual Basic]
' Modify the claim.
claim.PointsUsed = 2000
claim.RewardID = 21

' Save the changes


dal.UpdateRewardsClaim(claim)

[Visual C#]
// Modify the claim.
claim.PointsUsed = 2000;
claim.RewardID = 21;

// Save the changes


dal.UpdateRewardsClaim(claim);

10. Locate the next TODO comment in the UpdateRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In UpdateRewardsClaimTest, check the values retrieved from the
database item in the task list:
In the task list, double-click the TODO: Ex2 - In UpdateRewardsClaimTest, check the values
retrieved from the database item.
11. Immediately after the comment, add code to check that the property values of the claim object
match those of the updatedClaim object and that the value of the updateResult variable is true.
Your code should resemble the following code example.

[Visual Basic]
' Check that the changes were saved correctly.
Assert.AreEqual(updateResult, True)
Assert.AreEqual(claim.ClaimID, updatedClaim.ClaimID)
Assert.AreEqual(claim.PointsUsed, updatedClaim.PointsUsed)
Assert.AreEqual(claim.RewardID, updatedClaim.RewardID)
Assert.AreEqual(claim.ContactID, updatedClaim.ContactID)

[Visual C#]
// Check that the changes were saved correctly.
Assert.AreEqual(updateResult, true);
Assert.AreEqual(claim.ClaimID, updatedClaim.ClaimID);
Assert.AreEqual(claim.PointsUsed, updatedClaim.PointsUsed);
Assert.AreEqual(claim.RewardID, updatedClaim.RewardID);
28 Lab Answer Key: Building Optimized Solutions by Using Object Services

Assert.AreEqual(claim.ContactID, updatedClaim.ContactID);

12. Locate the first TODO comment in the DeleteRewardsClaimTest method by double-clicking the
TODO: Ex2 - In DeleteRewardsClaimTest, delete the claim item in the task list:
In the task list, double-click the TODO: Ex2 - In DeleteRewardsClaimTest, delete the claim
item.
13. Review the existing code in the DeleteRewardsClaimTest method.
14. Immediately after the comment, call the DeleteRewardsClaim method in the data access layer,
passing the claim object's ClaimID property as a parameter.
Your code should resemble the following code example.

[Visual Basic]
' Delete the claim.
DAL.DeleteRewardsClaim(claim.ClaimID)

[Visual C#]
// Delete the claim.
dal.DeleteRewardsClaim(claim.ClaimID);

15. Locate the next TODO comment in the DeleteRewardsClaimTest method by double-clicking the
comment TODO: Ex2 - In DeleteRewardsClaimTest, check the delete succeeded item in the task
list:
In the task list, double-click the TODO: Ex2 - In DeleteRewardsClaimTest, check the delete
succeeded item.
16. Immediately after the comment, add code to check that the value of the deleteResult variable is true.
Your code should resemble the following code example.

[Visual Basic]
' Check the delete worked.
Assert.AreEqual(deleteResult, True)

[Visual C#]
// Check the delete worked.
Assert.AreEqual(deleteResult, true);

17. Save the DataAccessLayerTest code file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 6: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed.
Lab Answer Key: Building Optimized Solutions by Using Object Services 29

4. Start the Customer Rewards application in Debug mode:


On the Debug menu, click Start Debugging.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that you can add, modify, and delete claims and that the contacts points are
adjusted correctly.
6. Close the application.
7. Close the solution, and then close Visual Studio:
a. On the File menu, click Close Solution.
b. On the File menu, click Exit.
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 1

Module 7
Lab Answer Key: Customizing Entities and Building Custom
Entity Classes
Contents:
Exercise 1: Using a Template to Add Custom Functionality to Entity Classes 2
Exercise 2: Creating Custom Entity Classes 16
2 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

Lab 7: Customizing Entities and Building


Custom Entity Classes
Exercise 1: Using a Template to Add Custom Functionality to Entity Classes
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-07 virtual machine as Student with the password Pa$$w0rd.
2. Run AWReset.bat in the E:\Labfiles folder:

a. Click Start, and then click Computer.


b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab07\VB\Ex1\Starter or
E:\Labfiles\Lab07\CS\Ex1\Starter folder:

a. On the File menu, point to Open, and then click Project/Solution.


b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab07\VB\Ex1\Starter folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab07\CS\Ex1\Starter folder, click DAL.sln, and then click Open.

Task 3: Create the IValidate class


1. Add a new interface named IValidate to the DAL project:

a. In Solution Explorer, right-click DAL, point to Add, and then click New Item.
b. In the Add New Item - DAL dialog box, in the Templates list, click Interface.
c. In the Name box, type IValidate and then click Add.

2. Modify the interface definition to make it public, and add a void method named Validate that takes
no arguments.

a. If you are using Visual Basic, in IValidate.vb, replace both occurrences of Class with Interface,
and in the body of the class, type Sub Validate();
b. If you are using Visual C#, in IValidate.cs, replace class with public interface, and in the body of
the class, type void Validate();

Your code should resemble the following code example.

[Visual Basic]
Public Interface IValidate

Sub Validate()

End Interface
[Visual C#]
public interface IValidate
{
void Validate();
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 3

Task 4: Create the template


1. Add an ADO.NET EntityObject Generator item named AWModel.tt to the DAL project:

a. In Solution Explorer, expand DAL, right-click AdventureWorksEDM.edmx, and then click Open.
b. In the Entity Designer pane, right-click anywhere in the white space, and then click Add Code
Generation Item.
c. In the Add New Item - DAL dialog box, in the templates list, click ADO.NET EntityObject
Generator.
d. In the Name box, type AWModel.tt and then click Add.
e. If the Security Warning dialog box appears, select the Do not show this message again check
box, and then click OK.

2. Open AdventureWorksEDM.Designer.vb or AdventureWorksEDM.Designer.cs and review the


comment that it contains:

a. In Solution Explorer, expand AdventureWorksEDM.edmx. If you are using Visual Basic, right-
click AdventureWorksEDM.Designer.vb, and then click Open. If you are using Visual C#, right-
click AdventureWorksEDM.Designer.cs, and then click Open.
b. Review the comment at the beginning of the file.
c. On the File menu, click Close.

Task 5: Customize the template


1. In AWModel.tt, locate the line of code that begins <#=Accessibility.ForType(entity)#>:

a. In Solution Explorer, right-click AWModel.tt, and then click Open.


b. On the Edit menu, point to Find and Replace, and then click Quick Find.
c. In the Find and Replace dialog box, in the Find what box, type
<#=Accessibility.ForType(entity)#> and then click Find Next.
d. Close the Find and Replace dialog box.
2. Edit the line of code to make every entity object implement the IValidate interface:
If you are using Visual Basic, you will see the next line begins with Inherits. On the line below
this, type Implements IValidate
If you are using Visual C#, at the end of the line of code, type, IValidate (including the comma)
3. Within the body of this section, declare a partial void method named OnValidate that takes no
arguments:

a. If you are using Visual Basic, position your cursor after Implements IValidate, and then press
ENTER.
b. If you are using Visual C#, position your cursor after the brace on the following line, and then
press ENTER.

Your code should resemble the following code example.

[Visual Basic]
Private Partial Sub OnValidate()
End Sub

[Visual C#]
4 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

partial void OnValidate();

4. Immediately after the statement that declares the OnValidate method, implement the
IValidate.Validate method. Inside this method, call the OnValidate method that you have just
declared.

Your code should resemble the following code example.

[Visual Basic]
Overloads Sub Validate() Implements IValidate.Validate

OnValidate()

End Sub

[Visual C#]
void IValidate.Validate()
{
OnValidate();
}

5. If you are using Visual Basic, you must also manually adjust the namespace to match the rest of the
project.
a. Locate the line of code that begins Dim namespaceName As String. This code is located near
the top of AWModel.tt.
b. Replace this line with the following code.

Dim namespaceName As String = "DAL"

Task 6: View the generated code


Build the solution, and then review the generated code in each class in AWModel.vb or AWModel.cs:
a. On the Build menu, click Build Solution.
b. In Solution Explorer, expand AWModel.tt. If you are using Visual Basic, right-click AWModel.vb,
and then click Open. If you are using Visual C#, right-click AWModel.cs, and then click Open.
c. On the Edit menu, point to Find and Replace, and then click Quick Find.
d. In the Find and Replace dialog box, in the Find what box, type IValidate and then click Find
Next.
e. Close the Find and Replace dialog box.
f. Review the generated code in the class.

Task 7: Implement the OnValidate method


1. Add a new class named ContactExtension to the DAL project:
a. In Solution Explorer, right-click DAL, point to Add, and then click New Item.
b. In the Add New Item - DAL dialog box, in the Templates list, click Class.
c. In the Name box, type ContactExtension and then click Add.

2. Modify the class definition to define the class as a public partial class for the Contact class.

Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 5

Namespace DAL

Partial Public Class Contact

End Class

End Namespace

[Visual C#]

namespace DAL
{
public partial class Contact
{

}
}

3. Add a void method named OnValidate to the class. If you are using Visual C#, this method should be
declared partial.

Your code should resemble the following code example.

[Visual Basic]

Partial Public Class Contact

Private Sub OnValidate()

End Sub

End Class

[Visual C#]

public partial class Contact


{
partial void OnValidate()
{

}
}

4. Add code to the OnValidate method to throw a DALValidationException exception in each of the
following scenarios:
If the CurrentPoints property is set to a negative value.
If the EmailAddress property does not contain an @ symbol.
If the EmailAddress property does not contain a period.
Your code should resemble the following code example.

[Visual Basic]
Namespace DAL
Partial Public Class Contact
6 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

Private Sub OnValidate()

' Validation rules for contacts


If CurrentPoints < 0 Then
Throw New DALValidationException(
"Customers cannot have negative points total")
End If

If EmailAddress.Contains("@") = False Then


Throw New DALValidationException(
"E-mail address must contain an @ symbol")

End If

If EmailAddress.Contains(".") = False Then

Throw New DALValidationException(


"E-mail address must contain a period")

End If

End Sub

End Class

End Namespace

[Visual C#]

partial void OnValidate()


{
// Validation rules for contacts
if (CurrentPoints < 0)
{
throw new DALValidationException("Customers cannot have negative points
total");
}

if (EmailAddress.Contains("@") == false)
{
throw new DALValidationException ("E-mail address must contain an @
symbol");
}
if (EmailAddress.Contains(".") == false)
{
throw new DALValidationException ("E-mail address must contain a period");
}

Task 8: Modify the DAL code to validate the data


1. In the DataAccessLayer class, modify the UpdateContact method to call the Validate method
before saving changes to the object:

a. If you are using Visual Basic, in Solution Explorer, right-click DataAccessLayer.vb, and then click
Open.
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 7

b. If you are using Visual C#, in Solution Explorer, right-click DataAccessLayer.cs, and then click
Open.
c. Locate the UpdateContact method and call the Validate method before saving changes. Your
code should resemble the following code example.

[Visual Basic]

' Validate the changes


DirectCast(contactToModify, IValidate).Validate()

' Save the changes to the database


entities.SaveChanges()

[Visual C#]

// Validate the changes


((IValidate)contactToModify).Validate();

// Save the changes to the database


entities.SaveChanges();

2. In the DataAccessLayer class, modify the AddContact method to call the Validate method before
saving changes to the object:
Locate the AddContact method and call the Validate method before saving changes. Your code
should resemble the following code example.

[Visual Basic]
' Validate the changes
DirectCast(contact, IValidate).Validate()

' Save the changes to the database


entities.SaveChanges()

[Visual C#]
// Validate the changes
((IValidate)contact).Validate();

// Save the changes to the database


entities.SaveChanges();

3. In the CustomerRewards project, in the MainWindow.xaml.vb or MainWindow.xaml.cs class, in the


contacts_MouseDoubleClick event, refresh the contacts list:

a. In Solution Explorer, expand CustomerRewards, expand MainWindow.xaml. If you are using


Visual Basic, right-click MainWindow.xaml.vb, and then click Open. If you are using Visual C#,
right-click MainWindow.xaml.cs, and then click Open.
b. Locate the catch block in the contacts_MouseDoubleClick method.
c. Add code to refresh the data. Your code should resemble the following code example.

[Visual Basic]
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
Me.RefreshContacts()
8 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

End Try

[Visual C#]
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}", ex.Message));
this.RefreshContacts();
}

Task 9: Add unit tests to verify your code


1. Review the task list:

a. On the View menu, click Task List.


b. In the Task List pane, in the Categories list, click Comments.
2. Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex1 - Add a test
for AddContact when there is a CurrentPoints validation exception item in the task list. This task
is located in the AddContactCurrentPointsValidationTest method:
In the task list, double-click the TODO: Ex1 - Add a test for AddContact when there is a
CurrentPoints validation exception item.
3. Add an ExpectedException attribute to the method for the DALValidationException type:
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactCurrentPointsValidationTest()

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactCurrentPointsValidationTest()

4. Delete the comment in the AddContactCurrentPointsValidationTest method:


Select the comment in the method, and then press DELETE.
5. Add a unit test to create a Contact object, set the CurrentPoints property of the Contact object to
an invalid value, and then add the contact to the database. Be sure to release all resources at the end
of the test.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactCurrentPointsValidationTest()

Dim target As New DataAccessLayer()


' Create a new contact and set the CurrentPoints
' property to an invalid value
Dim contact As Contact = CreateTestContact()
contact.CurrentPoints = -5
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 9

' Add the new contact to the database


Dim contactId As Integer = 0
contactId = target.AddContact(contact)

' Tidy up
If contactId > 0 Then
target.DeleteContact(contactId)
End If

target.Dispose()

End Sub

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactCurrentPointsValidationTest()
{
DataAccessLayer target = new DataAccessLayer();

// Create a new contact and set the CurrentPoints property to an invalid value
Contact contact = CreateTestContact();
contact.CurrentPoints = -5;

// Add the new contact to the database


int contactId = 0;
contactId = target.AddContact(contact);

// Tidy up
if (contactId > 0)
target.DeleteContact(contactId);
target.Dispose();
}

6. Locate the AddContactAtSymbolValidationTest method by double-clicking the comment TODO:


Ex1 - Add a test for AddContact when there is a missing @ sign in the e-mail address
validation exception item in the task list:
In the task list, double-click the TODO: Ex1 - Add a test for AddContact when there is a
missing @ sign in the e-mail address validation exception item.
7. Add an ExpectedException attribute to the method for the DALValidationException type.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactAtSymbolValidationTest()

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactAtSymbolValidationTest()

8. Delete the comment in the AddContactAtSymbolValidationTest method:


10 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

Select the comment in the method, and then press DELETE.


9. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to
an invalid value with a missing @ symbol, and then add the contact to the database. Be sure to
release all resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactAtSymbolValidationTest()

Dim target As New DataAccessLayer()

' Create a new contact and set the EmailAddress property


' to an invalid value
Dim contact As Contact = CreateTestContact()
contact.EmailAddress = "ronald1adventure-works.com"

' Add the new contact to the database


Dim contactId As Integer = 0
contactId = target.AddContact(contact)

' Tidy up
If contactId > 0 Then
target.DeleteContact(contactId)
End If

target.Dispose()

End Sub

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]

public void AddContactAtSymbolValidationTest()


{
DataAccessLayer target = new DataAccessLayer();

// Create a new contact and set the EmailAddress property to an invalid value
Contact contact = CreateTestContact();
contact.EmailAddress = "ronald1adventure-works.com";

// Add the new contact to the database


int contactId = 0;
contactId = target.AddContact(contact);

// Tidy up
if (contactId > 0)
target.DeleteContact(contactId);
target.Dispose();

10. Locate the AddContactPeriodValidationTest method by double-clicking the comment TODO: Ex1
- Add a test for AddContact when there is a missing period in the e-mail address validation
exception item in the task list:
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 11

In the task list, double-click the TODO: Ex1 - Add a test for AddContact when there is a
missing period in the e-mail address validation exception item.
11. Add an ExpectedException attribute to the method for the DALValidationException type.

Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactPeriodValidationTest()

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactPeriodValidationTest()

12. Delete the comment in the AddContactPeriodValidationTest method:


Select the comment in the method, and then press DELETE.
13. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to
an invalid value with a missing period, and then add the contact to the database. Be sure to release all
resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactPeriodValidationTest()

Dim target As New DataAccessLayer()

' Create a new contact and set the EmailAddress property


' to an invalid value
Dim contact As Contact = CreateTestContact()
contact.EmailAddress = "ronald1@adventure-workscom"

' Add the new contact to the database


Dim contactId As Integer = 0
contactId = target.AddContact(contact)

' Tidy up
If contactId > 0 Then
target.DeleteContact(contactId)
End If

target.Dispose()

End Sub

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactPeriodValidationTest()
{
DataAccessLayer target = new DataAccessLayer();
12 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

// Create a new contact and set the EmailAddress property to an invalid value
Contact contact = CreateTestContact();
contact.EmailAddress = "ronald1@adventure-workscom";

// Add the new contact to the database


int contactId = 0;
contactId = target.AddContact(contact);

// Tidy up
if (contactId > 0)
target.DeleteContact(contactId);
target.Dispose();
}

14. Locate the UpdateContactCurrentPointsValidationTest method by double-clicking the comment


TODO: Ex1 - Add a test for UpdateContact when there is a CurrentPoints validation exception
item in the task list:
In the task list, double-click the TODO: Ex1 - Add a test for UpdateContact when there is a
CurrentPoints validation exception item.
15. Add an ExpectedException attribute to the method for the DALValidationException type.

Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactCurrentPointsValidationTest()

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactCurrentPointsValidationTest()

16. Delete the comment in the UpdateContactCurrentPointsValidationTest method:


Select the comment in the method, and then press DELETE.
17. Add a unit test to create a Contact object, retrieve that contact from the database, set the
CurrentPoints property of that Contact object to an invalid value, and then update the contact in
the database. Be sure to release all resources at the end of the test.

Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactCurrentPointsValidationTest()

Dim target As New DataAccessLayer()

' Create a test contact and then get a detached version


' of the contact
Dim contact As Contact = CreateTestContact()
Dim testId As Integer = target.AddContact(contact)
contact = GetContactById(testId)
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 13

' Set the CurrentPoints property to an invalid value


contact.CurrentPoints = -10

' Save the changes


target.UpdateContact(contact)

' Tidy up
target.DeleteContact(testId)
target.Dispose()

End Sub

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactCurrentPointsValidationTest()
{
DataAccessLayer target = new DataAccessLayer();

// Create a test contact and then get a detached version of the contact
Contact contact = CreateTestContact();
int testId = target.AddContact(contact);
contact = GetContactById(testId);

// Set the CurrentPoints property to an invalid value


contact.CurrentPoints = -10;

// Save the changes


target.UpdateContact(contact);

// Tidy up
target.DeleteContact(testId);
target.Dispose();
}

18. Locate the UpdateContactAtSymbolValidationTest method by double-clicking the comment


TODO: Ex1 - Add a test for UpdateContact when there is a missing @ sign in the e-mail
address validation exception item in the task list:
In the task list, double-click the TODO: Ex1 - Add a test for UpdateContact when there is a
missing @ sign in the e-mail address validation exception item.
19. Add an ExpectedException attribute to the method for the DALValidationException type.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactAtSymbolValidationTest()

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactAtSymbolValidationTest()

20. Delete the comment in the UpdateContactAtSymbolValidationTest method:


14 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

Select the comment in the method, and then press DELETE.


21. Add a unit test to create a Contact object, retrieve that contact from the database, set the
EmailAddress property of the Contact object to an invalid value with a missing @ symbol, and then
update the contact in the database. Be sure to release all resources at the end of the test.
Your code should resemble the following code example.

[Visual Basic]

<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactAtSymbolValidationTest()

Dim target As New DataAccessLayer()


' Create a test contact and then get a detached version
' of the contact
Dim contact As Contact = CreateTestContact()
Dim testId As Integer = target.AddContact(contact)
contact = GetContactById(testId)

' Set the EmailAddress property to an invalid value


contact.EmailAddress = "ronald1adventure-works.com"
' Save the changes
target.UpdateContact(contact)

' Tidy up
target.DeleteContact(testId)
target.Dispose()

End Sub

[Visual C#]

[TestMethod()]
[ExpectedException(typeof(DALValidationException))]

public void UpdateContactAtSymbolValidationTest()


{
DataAccessLayer target = new DataAccessLayer();

// Create a test contact and then get a detached version of the contact
Contact contact = CreateTestContact();
int testId = target.AddContact(contact);
contact = GetContactById(testId);

// Set the EmailAddress property to an invalid value


contact.EmailAddress = "ronald1adventure-works.com";

// Save the changes


target.UpdateContact(contact);

// Tidy up
target.DeleteContact(testId);
target.Dispose();
}

22. Locate the UpdateContactPeriodValidationTest method by double-clicking the comment TODO:


Ex1 - Add a test for UpdateContact when there is a missing period in the e-mail address
validation exception item in the task list:
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 15

In the task list, double-click the TODO: Ex1 - Add a test for UpdateContact when there is a
missing period in the e-mail address validation exception item.
23. Add an ExpectedException attribute to the method for the DALValidationException type.

Your code should resemble the following code example.

[Visual Basic]

<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactPeriodValidationTest()

[Visual C#]

[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactPeriodValidationTest()

24. Delete the comment in the UpdateContactPeriodValidationTest method:


Select the comment in the method, and then press DELETE.
25. Add a unit test to create a Contact object, retrieve that contact from the database , set the
EmailAddress property of the Contact object to an invalid value with a missing period, and then add
the contact to the database. Be sure to release all resources at the end of the test.

Your code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactPeriodValidationTest()
Dim target As New DataAccessLayer()

' Create a test contact and then get a detached version


' of the contact
Dim contact As Contact = CreateTestContact()
Dim testId As Integer = target.AddContact(contact)
contact = GetContactById(testId)

' Set the EmailAddress property to an invalid value


contact.EmailAddress = "ronald1@adventure-workscom"

' Save the changes


target.UpdateContact(contact)

' Tidy up
target.DeleteContact(testId)
target.Dispose()

End Sub

[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactPeriodValidationTest()
{
16 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

DataAccessLayer target = new DataAccessLayer();


// Create a test contact and then get a detached version of the contact
Contact contact = CreateTestContact();
int testId = target.AddContact(contact);
contact = GetContactById(testId);

// Set the EmailAddress property to an invalid value


contact.EmailAddress = "ronald1@adventure-workscom";

// Save the changes


target.UpdateContact(contact);

// Tidy up
target.DeleteContact(testId);
target.Dispose();
}

26. Save the DataAccessLayerTest code file:


If you are using Visual Basic, on the File menu, click Save DataAccessLayerTest.vb.
If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.

Task 10: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application in Debug mode.
On the Debug menu, click Start Debugging.
3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that the application functions as expected.
4. Close the application.
5. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed.
7. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Creating Custom Entity Classes


Task 1: Open the starter project for this exercise
1. Open the DAL solution in the E:\Labfiles\Lab07\VB\Ex2\Starter or E:\Labfiles\Lab07\CS\Ex2\Starter
folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution:
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab07\VB\Ex2\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab07\CS\Ex2\Starter folder, click DAL.sln, and then click Open.
2. If you are using Visual C#, if a Problem Loading message is displayed, on the Build menu, click
Rebuild Solution, and then in the designer pane, click Reload the designer.
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 17

Task 2: Remove the existing Contact class from the DAL project
Open AdventureWorksEDM.Designer.vb or AdventureWorksEDM.Designer.cs, and then in the Entities
region, comment out all of the Contact partial class:
a. In Solution Explorer, expand AdventureWorksEDM.edmx. If you are using Visual Basic, right-
click AdventureWorksEDM.Designer.vb, and then click Open. If you are using Visual C#, right-
click AdventureWorksEDM.Designer.cs, and then click Open.
b. In the code pane, scroll through the code until you find the #region defined as Entities, and
then position your insertion point after the region definition.
c. On the Edit menu, point to Find and Replace, and then click Quick Find.
d. In the Find and Replace dialog box, in the Find what box, type Contact
e. Expand Find options, select the Match whole word check box, and then click Find Next.
f. Close the Find and Replace dialog box.
g. Select all of the Contact partial class, on the Edit menu, point to Advanced, and then click
Comment Selection.

Task 3: Add the existing business class to the DAL project


1. Add the businessLogicCustomer.cs or businessLogicCustomer.vb file in the
E:\Labfiles\Lab07\CS\Ex2\Starter or E:\Labfiles\Lab07\VB\Ex2\Starter folder to the DAL project:
a. In Solution Explorer, right-click DAL, point to Add, and then click Existing Item.
b. If you are using Visual Basic, in the Add Existing Item - DAL dialog box, move to the
E:\Labfiles\Lab07\VB\Ex2\Starter folder, click businessLogicCustomer.vb, and then click Add.
c. If you are using Visual C#, in the Add Existing Item - DAL dialog box, move to the
E:\Labfiles\Lab07\CS\Ex2\Starter folder, click businessLogicCustomer.cs, and then click Add.
2. Rename the businessLogicCustomer class file to Contact.cs or Contact.vb, and the class to Contact:
a. If you are using Visual Basic, in Solution Explorer, right-click businessLogicCustomer.vb, and
then click Rename.
b. If you are using Visual C#, in Solution Explorer, right-click businessLogicCustomer.cs, and then
click Rename.
c. If you are using Visual Basic, type Contact.vb and then press ENTER.
d. If you are using Visual C#, type Contact.cs and then press ENTER.
e. If you are using Visual C#, in the Microsoft Visual Studio dialog box, click Yes.
f. If you are using Visual Basic, right-click Contact.vb, and then click Open. Locate the line of code
that begins Public Class businessLogicCustomer, and replace it with Public Class Contact.

Task 4: Modify the business class to operate as an entity class


1. In Contact.cs or Contact.vb, bring the following namespaces into scope:
System.Data
System.Data.Objects.DataClasses
System.Data.Metadata.Edm
a. If you are using Visual Basic, in Solution Explorer, right-click Contact.vb, and then click Open.
b. If you are using Visual C#, in Solution Explorer, right-click Contact.cs, and then click Open.
c. At the top of the code file, add statements to bring the namespaces into scope.

Your code should resemble the following code example.

[Visual Basic]
18 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

' TODO: Add using statements for System.Data, System.Data.Objects.DataClasses, and


System.Data.Metadata.Edm
Imports System.Data
Imports System.Data.Objects.DataClasses
Imports System.Data.Metadata.Edm

[Visual C#]
// TODO: Add using statements for System.Data, System.Data.Objects.DataClasses, and
System.Data.Metadata.Edm
using System.Data;
using System.Data.Objects.DataClasses;
using System.Data.Metadata.Edm;

2. In the Contact class, modify the class definition to inherit from EntityObject.

Your code should resemble the following code example.

[Visual Basic]
Public Class Contact
Inherits EntityObject

[Visual C#]
public class Contact : EntityObject

3. In the Contact class, use the EdmEntityType attribute to link the class to the Contact entity in the
AdventureWorksModel namespace.
Your code should resemble the following code example.

[Visual Basic]
<EdmEntityType(NamespaceName:="AdventureWorksModel",
Name:="Contact")> _
Public Class Contact
Inherits EntityObject

[Visual C#]
[EdmEntityType(NamespaceName = "AdventureWorksModel", Name = "Contact")]
public class Contact : EntityObject

4. Use the EdmScalarProperty attribute to configure the entity properties in the class, as the following
table shows.

Property name EntityKeyProperty IsNullable

ContactID true false

NameStyle false false

Title false true

FirstName false false

MiddleName false true


Lab Answer Key : Customizing Entities and Building Custom Entity Classes 19

Property name EntityKeyProperty IsNullable

LastName false false

Suffix false true

EmailAddress false true

EmailPromotion false false

Phone false true

PasswordHash false false

PasswordSalt false false

AdditionalContactInfo false true

rowguid false false

ModifiedDate false false

CurrentPoints false false

Your code should resemble the following code example.

[Visual Basic]
<EdmScalarProperty(EntityKeyProperty:=True, IsNullable:=False)> _
Public Property ContactID As Int32
Get
Return _ContactID
End Get
Set(ByVal value As Int32)
If _ContactID <> value Then
_ContactID = value
End If
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property NameStyle As Boolean
Get
Return _NameStyle
End Get
Set(ByVal value As Boolean)
_NameStyle = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property Title As String
Get
Return _Title
End Get
Set(ByVal value As String)
_Title = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property FirstName As String
20 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property MiddleName As String
Get
Return _MiddleName
End Get
Set(ByVal value As String)
_MiddleName = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property LastName As String
Get
Return _LastName
End Get
Set(ByVal value As String)
_LastName = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property Suffix As String
Get
Return _Suffix
End Get
Set(ByVal value As String)
_Suffix = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property EmailAddress As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
If value.Contains("@") = False Then
Throw New DALValidationException("E-mail address must contain an @ symbol.")
End If
If value.Contains(".") = False Then
Throw New DALValidationException("E-mail address must contain a period.")
End If
_EmailAddress = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property EmailPromotion As Int32
Get
Return _EmailPromotion
End Get
Set(ByVal value As Int32)
_EmailPromotion = value
End Set
End Property
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 21

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property Phone As String
Get
Return _Phone
End Get
Set(ByVal value As String)
_Phone = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property PasswordHash As String
Get
Return _PasswordHash
End Get
Set(ByVal value As String)
_PasswordHash = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property PasswordSalt As String
Get
Return _PasswordSalt
End Get
Set(ByVal value As String)
_PasswordSalt = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property AdditionalContactInfo As String
Get
Return _AdditionalContactInfo
End Get
Set(ByVal value As String)
_AdditionalContactInfo = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property rowguid As Guid
Get
Return _rowguid
End Get
Set(ByVal value As Guid)
_rowguid = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property ModifiedDate As DateTime
Get
Return _ModifiedDate
End Get
Set(ByVal value As DateTime)
If value > DateTime.Now Then
Throw New DALValidationException("Modified date must not be in the future.")
End If
_ModifiedDate = value
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
22 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

Public Property CurrentPoints As Int32


Get
Return _CurrentPoints
End Get
Set(ByVal value As Int32)
If value < 0 Then
Throw New DALValidationException("Customers cannot have a negative points
balance.")
End If
_CurrentPoints = value
End Set
End Property

[Visual C#]
[EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)]
public Int32 ContactID
{
get
{
return _ContactID;
}
set
{
if (_ContactID != value)
{
_ContactID = value;
}
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public Boolean NameStyle
{
get
{
return _NameStyle;
}
set
{
_NameStyle = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]


public String Title
{
get
{
return _Title;
}
set
{
_Title = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public String FirstName
{
get
{
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 23

return _FirstName;
}
set
{
_FirstName = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]

public String MiddleName


{
get
{
return _MiddleName;
}
set
{
_MiddleName = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]

public String LastName


{
get
{
return _LastName;
}
set
{
_LastName = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]

public String Suffix


{
get
{
return _Suffix;
}
set
{
_Suffix = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]

public String EmailAddress


{
get
{
return _EmailAddress;
}
set
{
if (value.Contains("@") == false)
{
throw new DALValidationException("E-mail address must contain an @
symbol.");
24 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

}
if (value.Contains(".") == false)
{
throw new DALValidationException("E-mail address must contain a period.");
}
_EmailAddress = value;
}
}
[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]
public Int32 EmailPromotion
{
get
{
return _EmailPromotion;
}
set
{
_EmailPromotion = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]


public String Phone
{
get
{
return _Phone;
}
set
{
_Phone = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public String PasswordHash
{
get
{
return _PasswordHash;
}
set
{
_PasswordHash = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public String PasswordSalt
{
get
{
return _PasswordSalt;
}
set
{
_PasswordSalt = value;
}
}
[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]
public String AdditionalContactInfo
{
get
{
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 25

return _AdditionalContactInfo;
}
set
{
_AdditionalContactInfo = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public Guid rowguid
{
get
{
return _rowguid;
}
set
{
_rowguid = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public DateTime ModifiedDate
{
get
{
return _ModifiedDate;
}
set
{
if (value > DateTime.Now)
{
throw new DALValidationException("Modified date must not be in the
future.");
}
_ModifiedDate = value;
}
}

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]


public Int32 CurrentPoints
{
get
{
return _CurrentPoints;
}
set
{
if (value < 0)
{
throw new DALValidationException("Customers cannot have a negative points
balance.");
}
_CurrentPoints = value;
}
}

5. Modify the Set statements for each property to notify the change tracker when a property change is
pending and then completed, and to use the SetValidValue method of the StructuralObject object
to change the property value.

Your code should resemble the following code example.


26 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

[Visual Basic]

<EdmScalarProperty(EntityKeyProperty:=True, IsNullable:=False)> _
Public Property ContactID As Int32

Get
Return _ContactID
End Get
Set(ByVal value As Int32)
If _ContactID <> value Then
ReportPropertyChanging("ContactID")
_ContactID = StructuralObject.SetValidValue(value)
ReportPropertyChanged("ContactID")
End If
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property NameStyle As Boolean
Get
Return _NameStyle
End Get
Set(ByVal value As Boolean)
ReportPropertyChanging("NameStyle")
_NameStyle = StructuralObject.SetValidValue(value)
ReportPropertyChanged("NameStyle")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property Title As String
Get
Return _Title
End Get
Set(ByVal value As String)
ReportPropertyChanging("Title")
_Title = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("Title")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property FirstName As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
ReportPropertyChanging("FirstName")
_FirstName = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("FirstName")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property MiddleName As String
Get
Return _MiddleName
End Get
Set(ByVal value As String)
ReportPropertyChanging("MiddleName")
_MiddleName = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("MiddleName")
End Set
End Property
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 27

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property LastName As String
Get
Return _LastName
End Get
Set(ByVal value As String)
ReportPropertyChanging("LastName")
_LastName = StructuralObject.SetValidValue(value, False)
ReportPropertyChanged("LastName")
End Set
End Property
<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property Suffix As String
Get
Return _Suffix
End Get
Set(ByVal value As String)
ReportPropertyChanging("Suffix")
_Suffix = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("Suffix")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property EmailAddress As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
If value.Contains("@") = False Then
Throw New DALValidationException("E-mail address must contain an @ symbol.")
End If
If value.Contains(".") = False Then
Throw New DALValidationException("E-mail address must contain a period.")
End If
ReportPropertyChanging("EmailAddress")
_EmailAddress = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("EmailAddress")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property EmailPromotion As Int32
Get
Return _EmailPromotion
End Get
Set(ByVal value As Int32)
ReportPropertyChanging("EmailPromotion")
_EmailPromotion = StructuralObject.SetValidValue(value)
ReportPropertyChanged("EmailPromotion")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property Phone As String
Get
Return _Phone
End Get
Set(ByVal value As String)
ReportPropertyChanging("Phone")
_Phone = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("Phone")
End Set
28 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property PasswordHash As String
Get
Return _PasswordHash
End Get
Set(ByVal value As String)
ReportPropertyChanging("PasswordHash")
_PasswordHash = StructuralObject.SetValidValue(value, False)
ReportPropertyChanged("PasswordHash")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property PasswordSalt As String
Get
Return _PasswordSalt
End Get
Set(ByVal value As String)
ReportPropertyChanging("PasswordSalt")
_PasswordSalt = StructuralObject.SetValidValue(value, False)
ReportPropertyChanged("PasswordSalt")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=True)> _
Public Property AdditionalContactInfo As String
Get
Return _AdditionalContactInfo
End Get
Set(ByVal value As String)
ReportPropertyChanging("AdditionalContactInfo")
_AdditionalContactInfo = StructuralObject.SetValidValue(value, True)
ReportPropertyChanged("AdditionalContactInfo")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property rowguid As Guid
Get
Return _rowguid
End Get
Set(ByVal value As Guid)
ReportPropertyChanging("rowguid")
_rowguid = StructuralObject.SetValidValue(value)
ReportPropertyChanged("rowguid")
End Set
End Property

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property ModifiedDate As DateTime
Get
Return _ModifiedDate
End Get
Set(ByVal value As DateTime)
If value > DateTime.Now Then
Throw New DALValidationException("Modified date must not be in the future.")
End If
ReportPropertyChanging("ModifiedDate")
_ModifiedDate = StructuralObject.SetValidValue(value)
ReportPropertyChanged("ModifiedDate")
End Set
End Property
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 29

<EdmScalarProperty(EntityKeyProperty:=False, IsNullable:=False)> _
Public Property CurrentPoints As Int32
Get
Return _CurrentPoints
End Get
Set(ByVal value As Int32)
If value < 0 Then
Throw New DALValidationException("Customers cannot have a negative points
balance.")
End If
ReportPropertyChanging("CurrentPoints")
_CurrentPoints = StructuralObject.SetValidValue(value)
ReportPropertyChanged("CurrentPoints")
End Set
End Property

[Visual C#]
[EdmScalarPropertyAttribute(EntityKeyProperty = true, IsNullable = false)]
public Int32 ContactID
{
get
{
return _ContactID;
}
set
{
if (_ContactID != value)
{
ReportPropertyChanging("ContactID");
_ContactID = StructuralObject.SetValidValue(value);
ReportPropertyChanged("ContactID");
}
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public Boolean NameStyle
{
get
{
return _NameStyle;
}
set
{
ReportPropertyChanging("NameStyle");
_NameStyle = StructuralObject.SetValidValue(value);
ReportPropertyChanged("NameStyle");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]


public String Title
{
get
{
return _Title;
}
set
{
ReportPropertyChanging("Title");
30 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

_Title = StructuralObject.SetValidValue(value, true);


ReportPropertyChanged("Title");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]


public String FirstName
{
get
{
return _FirstName;
}
set
{
ReportPropertyChanging("FirstName");
_FirstName = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("FirstName");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]


public String MiddleName
{
get
{
return _MiddleName;
}
set
{
ReportPropertyChanging("MiddleName");
_MiddleName = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("MiddleName");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public String LastName
{
get
{
return _LastName;
}
set
{
ReportPropertyChanging("LastName");
_LastName = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("LastName");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]


public String Suffix
{
get
{
return _Suffix;
}
set
{
ReportPropertyChanging("Suffix");
_Suffix = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("Suffix");
}
}
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 31

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]


public String EmailAddress
{
get
{
return _EmailAddress;
}
set
{
if (value.Contains("@") == false)
{
throw new DALValidationException("E-mail address must contain an @
symbol.");
}
if (value.Contains(".") == false)
{
throw new DALValidationException("E-mail address must contain a period.");
}
ReportPropertyChanging("EmailAddress");
_EmailAddress = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("EmailAddress");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public Int32 EmailPromotion
{
get
{
return _EmailPromotion;
}
set
{
ReportPropertyChanging("EmailPromotion");
_EmailPromotion = StructuralObject.SetValidValue(value);
ReportPropertyChanged("EmailPromotion");
}
}
[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]
public String Phone
{
get
{
return _Phone;
}
set
{
ReportPropertyChanging("Phone");
_Phone = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("Phone");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public String PasswordHash
{
get
{
return _PasswordHash;
}
set
{
ReportPropertyChanging("PasswordHash");
_PasswordHash = StructuralObject.SetValidValue(value, false);
32 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

ReportPropertyChanged("PasswordHash");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public String PasswordSalt
{
get
{
return _PasswordSalt;
}
set
{
ReportPropertyChanging("PasswordSalt");
_PasswordSalt = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("PasswordSalt");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]


public String AdditionalContactInfo
{
get
{
return _AdditionalContactInfo;
}
set
{
ReportPropertyChanging("AdditionalContactInfo");
_AdditionalContactInfo = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("AdditionalContactInfo");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public Guid rowguid
{
get
{
return _rowguid;
}
set
{
ReportPropertyChanging("rowguid");
_rowguid = StructuralObject.SetValidValue(value);
ReportPropertyChanged("rowguid");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public DateTime ModifiedDate
{
get
{
return _ModifiedDate;
}
set
{
if (value > DateTime.Now)
{
throw new DALValidationException("Modified date must not be in the
future.");
}
ReportPropertyChanging("ModifiedDate");
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 33

_ModifiedDate = StructuralObject.SetValidValue(value);
ReportPropertyChanged("ModifiedDate");
}
}

[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]


public Int32 CurrentPoints
{
get
{
return _CurrentPoints;
}
set
{
if (value < 0)
{
throw new DALValidationException("Customers cannot have a negative points
balance.");
}
ReportPropertyChanging("CurrentPoints");
_CurrentPoints = StructuralObject.SetValidValue(value);
ReportPropertyChanged("CurrentPoints");
}
}

6. Add navigation properties to link the Contact entity to the SalesOrderHeader, StoreContact, and
RewardsClaimed entities.
Your code should resemble the following code example.

[Visual Basic]
'TODO: Add navigation properties for the links to the other entities in the model.

<EdmRelationshipNavigationProperty("AdventureWorksModel",
"FK_SalesOrderHeader_Contact_ContactID", "SalesOrderHeader")> _
Public Property SalesOrderHeaders() As EntityCollection(Of SalesOrderHeader)
Get
Return DirectCast(Me,
IEntityWithRelationships).RelationshipManager.GetRelatedCollection(Of
SalesOrderHeader)("AdventureWorksModel.FK_SalesOrderHeader_Contact_ContactID",
"SalesOrderHeader")
End Get
Set(ByVal value As EntityCollection(Of SalesOrderHeader))
If (value IsNot Nothing) Then
DirectCast(Me,
IEntityWithRelationships).RelationshipManager.InitializeRelatedCollection(Of
SalesOrderHeader)("AdventureWorksModel.FK_SalesOrderHeader_Contact_ContactID",
"SalesOrderHeader", value)
End If
End Set
End Property

<EdmRelationshipNavigationProperty("AdventureWorksModel",
"FK_StoreContact_Contact_ContactID", "StoreContact")> _
Public Property StoreContacts() As EntityCollection(Of StoreContact)
Get
Return DirectCast(Me,
IEntityWithRelationships).RelationshipManager.GetRelatedCollection(Of
StoreContact)("AdventureWorksModel.FK_StoreContact_Contact_ContactID", "StoreContact")
End Get
Set(ByVal value As EntityCollection(Of StoreContact))
If (value IsNot Nothing) Then
34 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

DirectCast(Me,
IEntityWithRelationships).RelationshipManager.InitializeRelatedCollection(Of
StoreContact)("AdventureWorksModel.FK_StoreContact_Contact_ContactID", "StoreContact",
value)
End If
End Set
End Property

<EdmRelationshipNavigationProperty("AdventureWorksModel", "FK_ContactRewardsClaimed",
"RewardsClaimed")> _
Public Property RewardsClaimed() As EntityCollection(Of RewardsClaimed)
Get
Return DirectCast(Me,
IEntityWithRelationships).RelationshipManager.GetRelatedCollection(Of
RewardsClaimed)("AdventureWorksModel.FK_ContactRewardsClaimed", "RewardsClaimed")
End Get
Set(ByVal value As EntityCollection(Of RewardsClaimed))
If (value IsNot Nothing) Then
DirectCast(Me,
IEntityWithRelationships).RelationshipManager.InitializeRelatedCollection(Of
RewardsClaimed)("AdventureWorksModel.FK_ContactRewardsClaimed", "RewardsClaimed", value)
End If
End Set
End Property

[Visual C#]
// Add navigation properties for the links to the other entities in the model.
[EdmRelationshipNavigationProperty("AdventureWorksModel",
"FK_SalesOrderHeader_Contact_ContactID", "SalesOrderHeader")]
public EntityCollection<SalesOrderHeader> SalesOrderHeaders
{
get
{
return
((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<SalesOrderHead
er>("AdventureWorksModel.FK_SalesOrderHeader_Contact_ContactID", "SalesOrderHeader");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<SalesOr
derHeader>("AdventureWorksModel.FK_SalesOrderHeader_Contact_ContactID",
"SalesOrderHeader", value);
}
}
}

[EdmRelationshipNavigationProperty("AdventureWorksModel",
"FK_StoreContact_Contact_ContactID", "StoreContact")]
public EntityCollection<StoreContact> StoreContacts
{
get
{
return
((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<StoreContact>(
"AdventureWorksModel.FK_StoreContact_Contact_ContactID", "StoreContact");
}
set
{
if ((value != null))
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 35

{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<StoreCo
ntact>("AdventureWorksModel.FK_StoreContact_Contact_ContactID", "StoreContact", value);
}
}
}
[EdmRelationshipNavigationProperty("AdventureWorksModel", "FK_ContactRewardsClaimed",
"RewardsClaimed")]
public EntityCollection<RewardsClaimed> RewardsClaimed
{
get
{
return
((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<RewardsClaimed
>("AdventureWorksModel.FK_ContactRewardsClaimed", "RewardsClaimed");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Rewards
Claimed>("AdventureWorksModel.FK_ContactRewardsClaimed", "RewardsClaimed", value);
}
}
}

7. If you are using Visual C#, build the solution and correct any errors:
On the Build menu, click Build Solution.

Task 5: Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class
(for Visual Basic only)
1. Open the AdventureWorksEDM.Designer.vb file:
In Solution Explorer, expand AdventureWorksEDM.edmx, and then double-click
AdventureWorksEDM.Designer.vb.
2. At the top of the file, if it is not already present, add a statement to bring the DAL namespace into
scope.
Your code should resemble the following code example.

[Visual Basic]
Imports DAL

3. Update code that references AdventureWorksModel.Contact to reference DAL.Contact:


a. Examine the code located in the EDM Relationship Metadata region, located near the top of
the file.
b. Update the AdventureWorksModel.Contact references to DAL.Contact.

Your code should resemble the following code example.

#Region "EDM Relationship Metadata"


<Assembly: EdmRelationshipAttribute("AdventureWorksModel",
"FK_SalesOrderHeader_Contact_ContactID", "Contact",
System.Data.Metadata.Edm.RelationshipMultiplicity.One, GetType(DAL.Contact),
"SalesOrderHeader", System.Data.Metadata.Edm.RelationshipMultiplicity.Many,
GetType(AdventureWorksModel.SalesOrderHeader), True)>
<Assembly: EdmRelationshipAttribute("AdventureWorksModel",
"FK_StoreContact_Contact_ContactID", "Contact",
36 Lab Answer Key : Customizing Entities and Building Custom Entity Classes

System.Data.Metadata.Edm.RelationshipMultiplicity.One, GetType(DAL.Contact),
"StoreContact", System.Data.Metadata.Edm.RelationshipMultiplicity.Many,
GetType(AdventureWorksModel.StoreContact), True)>
<Assembly: EdmRelationshipAttribute("AdventureWorksModel",
"FK_SalesOrderHeader_SalesTerritory_TerritoryID", "SalesTerritory",
System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne,
GetType(AdventureWorksModel.SalesTerritory), "SalesOrderHeader",
System.Data.Metadata.Edm.RelationshipMultiplicity.Many,
GetType(AdventureWorksModel.SalesOrderHeader), True)>
<Assembly: EdmRelationshipAttribute("AdventureWorksModel", "FK_ContactRewardsClaimed",
"Contact", System.Data.Metadata.Edm.RelationshipMultiplicity.One, GetType(DAL.Contact),
"RewardsClaimed", System.Data.Metadata.Edm.RelationshipMultiplicity.Many,
GetType(AdventureWorksModel.RewardsClaimed), True)>
<Assembly: EdmRelationshipAttribute("AdventureWorksModel", "FK_RewardsRewardsClaimed",
"Reward", System.Data.Metadata.Edm.RelationshipMultiplicity.One,
GetType(AdventureWorksModel.Reward), "RewardsClaimed",
System.Data.Metadata.Edm.RelationshipMultiplicity.Many,
GetType(AdventureWorksModel.RewardsClaimed), True)>
#End Region

4. Build the solution and correct any errors:


On the Build menu, click Build Solution.

Task 6: Modify the user interface to catch the validation exception


1. In CustomerWindow.xaml or CustomerAddWindow.xaml, add the ExceptionValidationRule rule to
the binding validation rules for the CurrentPoints text box to catch the validation exception:

a. If you are using Visual Basic, in Solution Explorer, right-click CustomerAddWindow.xaml, and
then click Open.
b. If you are using Visual C#, in Solution Explorer, right-click CustomerWindow.xaml, and then
click Open.
c. In the Design pane, click the CurrentPoints text box.
d. In the XAML pane, locate the <Binding.ValidationRules> element for the text box.
e. Inside this element, add an element to ensure that exceptions are caught during the update of
the bound property.

Your code should resemble the following code example.

<TextBox.Text>
<Binding Path="CurrentPoints">
<Binding.ValidationRules>
<c:NumericValidationRule />
<c:NotNullOrEmptyValidationRule />
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>

2. In CustomerWindow.xaml or CustomerAddWindow.xaml, add the binding exception validation rule


for the EmailAddress text box to catch the validation exception and change the Style attribute of the
text box to display errors:

a. In the Design pane, click the Email text box.


b. In the XAML pane, locate the <TextBox> element for the text box.
c. Add a Style attribute to display the error.
d. Modify the element so that it contains a <Binding> element to describe the validation rules that
it should use.
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 37

Your code should resemble the following code example.

<TextBox Height="23"
HorizontalAlignment="Left"
Margin="171,114,0,0"
Style="{StaticResource TextBoxError}"
Name="emailAddress"
VerticalAlignment="Top"
Width="120" >
<TextBox.Text>
<Binding Path="EmailAddress">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Task 7: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application without debugging:
On the Debug menu, click Start Without Debugging.
3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that the application functions as expected.
4. Close the application.
5. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
6. Verify that all of the tests succeed.
7. Save and close the solution, and then close Visual Studio:

a. On the File menu, click Save All.


b. On the File menu, click Close Solution.
c. On the File menu, click Exit.
Lab Answer Key: Using POCO Classes with the Entity Framework 1

Module 8
Lab Answer Key: Using POCO Classes with the Entity
Framework
Contents:
Exercise 1: Using POCO Classes 2
Exercise 2: Extending Your POCO Classes 11
2 Lab Answer Key: Using POCO Classes with the Entity Framework

Lab 8: Using POCO Classes with the Entity


Framework
Exercise 1: Using POCO Classes
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-08 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex1\Starter or
E:\Labfiles\Lab08\VB\Ex1\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab08\VB\Ex1\Starter folder, click DAL.sln, and then click Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab08\CS\Ex1\Starter folder, click DAL.sln, and then click Open.

Task 3: Disable object layer generation for your EDM


1. Open the AdventureWorks EDM model in the ADO.NET Entity Data Model Designer (Entity Designer):
In Solution Explorer, right-click AdventureWorksEDM.edmx, and then click Open.
2. In the AdventureWorks EDM model, set the Code Generation Strategy property to None:
a. In the Entity Designer pane, right-click the designer surface, and then click Properties.
b. In the Properties pane, click Code Generation Strategy, and then in the drop-down list, click
None.
3. Save the AdventureWorks EDM model:
On the File menu, click Save AdventureWorksEDM.edmx.

Task 4: Implement a custom ObjectContext class


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the AdventureWorksContext file by double-clicking the TODO: Ex1 - Add a constructor task
in the task list. This task is located in the AdventureWorksContext class:
In the task list, double-click the TODO: Ex1 - Add a constructor task.
Lab Answer Key: Using POCO Classes with the Entity Framework 3

3. Immediately after the comment, add a no-argument constructor that enables automatic lazy loading.
The constructor should invoke the base class constructor passing the strings
"name=AdventureWorksEntities" and "AdventureWorksEntities" as parameters.
Your code should resemble the following code example.

[Visual Basic]

Public Sub New()


MyBase.New("name=AdventureWorksEntities", "AdventureWorksEntities")
Me.ContextOptions.LazyLoadingEnabled = True
End Sub

[Visual C#]

public AdventureWorksContext()
: base("name=AdventureWorksEntities", "AdventureWorksEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
}

4. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the Contacts entity set task in the task list. This task is located in the
AdventureWorksContext class:
In the task list, double-click the TODO: Ex1 - Define the Contacts entity set task.
5. Immediately after the comment, add a read-only property called Contacts based on the ObjectSet
generic type. Specify Contact as the type parameter for the ObjectSet type. Create the ObjectSet
object if it does not exist by calling the CreateObjectSet method in the base class, and then save the
ObjectSet object in a private field.
Your code should resemble the following code example.

[Visual Basic]

Public ReadOnly Property Contacts As ObjectSet(Of Contact)


Get
If _Contacts Is Nothing Then
_Contacts = MyBase.CreateObjectSet(Of Contact)("Contacts")
End If
Return _Contacts
End Get
End Property
Private _Contacts As ObjectSet(Of Contact)

[Visual C#]

public ObjectSet<Contact> Contacts


{
get
{
if (_Contacts == null)
{
_Contacts = base.CreateObjectSet<Contact>("Contacts");
}
return _Contacts;
}
4 Lab Answer Key: Using POCO Classes with the Entity Framework

private ObjectSet<Contact> _Contacts;

6. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the RewardsClaimed entity set task in the task list. This task is located in the
AdventureWorksContext class:
In the task list, double-click the TODO: Ex1 - Define the RewardsClaimed entity set task.
7. Immediately after the comment, add a read-only property called RewardsClaimed based on the
ObjectSet generic type. Specify RewardsClaimed as the type parameter for the ObjectSet type.
Create the ObjectSet object if it does not exist by calling the CreateObjectSet method in the base
class, and then save the ObjectSet object in a private field.
Your code should resemble the following code example.

[Visual Basic]

Public ReadOnly Property RewardsClaimed As ObjectSet(Of RewardsClaimed)

Get
If _RewardsClaimed Is Nothing Then
_RewardsClaimed = MyBase.CreateObjectSet(Of
RewardsClaimed)("RewardsClaimed")
End If
Return _RewardsClaimed
End Get

End Property
Private _RewardsClaimed As ObjectSet(Of RewardsClaimed)

[Visual C#]

public ObjectSet<RewardsClaimed> RewardsClaimed


{
get
{
if (_RewardsClaimed == null)
{
_RewardsClaimed =
base.CreateObjectSet<RewardsClaimed>("RewardsClaimed");
}
return _RewardsClaimed;
}
}

private ObjectSet<RewardsClaimed> _RewardsClaimed;

8. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the Rewards entity set task in the task list. This task is located in the
AdventureWorksContext class:
In the task list, double-click the TODO: Ex1 - Define the Rewards entity set task.
9. Immediately after the comment, add a read-only property called Rewards based on the ObjectSet
generic type. Specify Rewards as the type parameter for the ObjectSet type. Create the ObjectSet
object if it does not exist by calling the CreateObjectSet method in the base class, and then save the
ObjectSet object in a private field.
Lab Answer Key: Using POCO Classes with the Entity Framework 5

Your code should resemble the following code example.

[Visual Basic]

Public ReadOnly Property Rewards As ObjectSet(Of Reward)

Get
If _Rewards Is Nothing Then
_Rewards = MyBase.CreateObjectSet(Of Reward)("Rewards")
End If
Return _Rewards
End Get
End Property

Private _Rewards As ObjectSet(Of Reward)

[Visual C#]

public ObjectSet<Reward> Rewards


{
get
{
if (_Rewards == null)
{
_Rewards = base.CreateObjectSet<Reward>("Rewards");
}
return _Rewards;
}
}

private ObjectSet<Reward> _Rewards;

10. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the AddToContacts method task in the task list. This task is located in the
AdventureWorksContext class:
In the task list, double-click the TODO: Ex1 - Define the AddToContacts method task.
11. Immediately after the comment, add a void method called AddToContacts that takes a contact
entity as a parameter. The method should call the AddObject method in the base class to add the
contact entity to the contacts entity set.
Your code should resemble the following code example.

[Visual Basic]

Public Sub AddToContacts(ByVal contact As Contact)


MyBase.AddObject("Contacts", contact)
End Sub

[Visual C#]

public void AddToContacts(Contact contact)


{
base.AddObject("Contacts", contact);
}
6 Lab Answer Key: Using POCO Classes with the Entity Framework

12. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -
Define the AddToRewards method task in the task list. This task is located in the
AdventureWorksContext class:
In the task list, double-click the TODO: Ex1 - Define the AddToRewards method task.
13. Immediately after the comment, add a void method called AddToRewards that takes a reward entity
as a parameter. The method should call the AddObject method in the base class to add the reward
entity to the rewards entity set.
Your code should resemble the following code example.

[Visual Basic]

Public Sub AddToRewards(ByVal reward As Reward)


MyBase.AddObject("Rewards", reward)
End Sub

[Visual C#]

public void AddToRewards(Reward reward)


{
base.AddObject("Rewards", reward);
}

14. Save the AdventureWorksContext file:


a. If you are using Visual Basic, on the File menu, click Save AdventureWorksContext.vb.
b. If you are using Visual C#, on the File menu, click Save AdventureWorksContext.cs.

Task 5: Complete the RewardsClaimed class in the AdventureWorks project


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the RewardsClaimed file by double-clicking the TODO: Ex1 - Add virtual public accessors for
every RewardsClaimed entity property task in the task list. This task is located in the
RewardsClaimed class:
In the task list, double-click the TODO: Ex1 - Add virtual public accessors for every
RewardsClaimed entity property task.
3. Immediately after the comment, add a virtual public property for every entity property of the
RewardsClaimed entity object in the Entity Data Model (EDM).
Your code should resemble the following code example.

[Visual Basic]
Public Overridable Property ClaimID As Integer
Public Overridable Property PointsUsed As Integer
Public Overridable Property RewardID As Integer
Public Overridable Property ContactID As Integer
Public Overridable Property TimeStamp As DateTime
[Visual C#]
public virtual int ClaimID { get; set; }
public virtual int PointsUsed { get; set; }
public virtual int RewardID { get; set; }
public virtual int ContactID { get; set; }
Lab Answer Key: Using POCO Classes with the Entity Framework 7

public virtual DateTime TimeStamp { get; set; }

4. Locate the next comment in the RewardsClaimed file by double-clicking the TODO: Ex1 - Add
virtual public accessors for every RewardsClaimed navigation property task in the task list. This
task is located in the RewardsClaimed class:
In the task list, double-click the TODO: Ex1 - Add virtual public accessors for every
RewardsClaimed navigation property task.
5. Immediately after the comment, add a virtual public property for every navigation property of the
RewardsClaimed entity object in the EDM.
Your code should resemble the following code example.

[Visual Basic]
Public Overridable Property Reward As Reward
Public Overridable Property Contact As Contact

[Visual C#]
public virtual Reward Reward { get; set; }
public virtual Contact Contact { get; set; }

6. Save the RewardsClaimed file:


a. If you are using Visual Basic, on the File menu, click Save RewardsClaimed.vb.
b. If you are using Visual C#, on the File menu, click Save RewardsClaimed.cs.

Task 6: Modify the data access layer to work with the new POCO classes
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the TODO: Ex1 - Add a using clause for the
AdventureWorks namespace task in the task list. This task is located near the top of the
DataAccessLayer file:
In the task list, double-click the TODO: Ex1 - Add a using clause for the AdventureWorks
namespace task.
3. Immediately after the comment, add a using statement for the AdventureWorks namespace.
Your code should resemble the following code example.

[Visual Basic]
Imports AdventureWorks

[Visual C#]
using AdventureWorks;

4. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Use the
custom ObjectContext class task in the task list. This task is located in the SetContext method:
In the task list, double-click the TODO: Ex1 - Use the custom ObjectContext class task.
8 Lab Answer Key: Using POCO Classes with the Entity Framework

5. Immediately after the comment, modify the next line of code to use the AdventureWorksContext
class instead of the AdventureWorksEntities class.
Your code should resemble the following code example.

[Visual Basic]
If entities Is Nothing Then
entities = New AdventureWorksContext()
End If

[Visual C#]
if (entities == null) entities = new AdventureWorksContext();

6. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a
new contact by using the CreateObject method task in the task list. This task is located in the
AddContact method:
In the task list, double-click the TODO: Ex1 - Create a new contact by using the CreateObject
method task.
7. Immediately after the comment, add code that creates a new contact entity by calling the
CreateObject method. Then, use the Copy method of the contact object to copy the values from the
parameter passed to the AddContact method.
Your code should resemble the following code example.

[Visual Basic]
Dim contact As Contact = entities.CreateObject(Of Contact)()
contact.Copy(pcontact)

[Visual C#]
Contact contact = entities.CreateObject<Contact>();
contact.Copy(pcontact);

8. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a
new reward by using the CreateObject method task in the task list. This task is located in the
AddReward method:
In the task list, double-click the TODO: Ex1 - Create a new reward by using the CreateObject
method task.
9. Immediately after the comment, add code that creates a new reward entity by calling the
CreateObject method. Then, use the Copy method of the reward object to copy the values from the
parameter passed to the AddReward method. You must check the type of reward passed as a
parameter to the AddReward method (AdventureWorksReward, SupermarketReward, or
AirMilesReward), and then create the correct reward type.
Your code should resemble the following code example.

[Visual Basic]

Dim reward As Reward = Nothing


If TypeOf preward Is AdventureWorksReward Then
reward = entities.CreateObject(Of AdventureWorksReward)()
DirectCast(reward, AdventureWorksReward).Copy(DirectCast(preward,
AdventureWorksReward))
Lab Answer Key: Using POCO Classes with the Entity Framework 9

ElseIf TypeOf preward Is SupermarketReward Then


reward = entities.CreateObject(Of SupermarketReward)()
DirectCast(reward, SupermarketReward).Copy(DirectCast(preward, SupermarketReward))

ElseIf TypeOf preward Is AirMilesReward Then


reward = entities.CreateObject(Of AirMilesReward)()
DirectCast(reward, AirMilesReward).Copy(DirectCast(preward, AirMilesReward))

Else
Throw New InvalidOperationException("Unrecognized Reward Type")
End If

[Visual C#]

Reward reward = null;

if (preward is AdventureWorksReward)
{
reward = entities.CreateObject<AdventureWorksReward>();
((AdventureWorksReward)reward).Copy(
(AdventureWorksReward)preward);
}

else if (preward is SupermarketReward)


{
reward = entities.CreateObject<SupermarketReward>();
((SupermarketReward)reward).Copy((SupermarketReward)preward);
}

else if (preward is AirMilesReward)


{
reward = entities.CreateObject<AirMilesReward>();
((AirMilesReward)reward).Copy((AirMilesReward)preward);
}

else
{
throw new InvalidOperationException("Unrecognized Reward Type");
}

10. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a
new claim by using the CreateObject method task in the task list. This task is located in the
CreateRewardsClaim method:
In the task list, double-click the TODO: Ex1 - Create a new claim by using the CreateObject
method task.
11. Immediately after the comment, add code that creates a new RewardsClaimed entity by calling the
CreateObject method. Then, use the Copy method of the RewardsClaimed object to copy the
values from the parameter passed to the CreateRewardsClaim method.
Your code should resemble the following code example.

[Visual Basic]
Dim claim As RewardsClaimed = entities.CreateObject(Of RewardsClaimed)()
claim.Copy(pclaim)
10 Lab Answer Key: Using POCO Classes with the Entity Framework

[Visual C#]
RewardsClaimed claim = entities.CreateObject<RewardsClaimed>();
claim.Copy(pclaim);

12. Save the DataAccessLayer file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 7: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that you can add, search for, and delete customer data and that you can add and
delete claim data:
a. Click Create Customer, complete all the fields on the Add new customer form with your own
data. Make sure that your customer has at least 10000 points, and then click Add.
b. In the Name text box, type the last name of the customer you added in the previous step, and
then click Search.
c. In the data grid, click the customer that you added, click Create Claim, select a reward, and then
click OK.
d. Click the claim that you added in the previous step, and then click Delete Claim.
e. Click the customer that you added, and then click Delete Customer.

Note: If you try to delete a contact that has reward data, you may see an exception thrown.

6. Close the application.


7. Close the solution:
On the File menu, click Close Solution.
8. Reset the AdventureWorks Database. In the E:\Labfiles folder, run AWReset.bat:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.
Lab Answer Key: Using POCO Classes with the Entity Framework 11

Exercise 2: Extending Your POCO Classes


Task 1: Open the starter project for this exercise
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex2\Starter or
E:\Labfiles\Lab08\VB\Ex2\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab08\VB\Ex2\Starter folder, click DAL.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab08\CS\Ex2\Starter folder, click DAL.sln, and then click Open.

Task 2: Add business operations to the Contact class


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the Contact file by double-clicking the TODO: Ex2 - Create the Salt and Hash task in the task
list. This task is located in the Password property:
In the task list, double-click the TODO: Ex2 - Create the Salt and Hash task.
3. Immediately after the comment, generate a value for the PasswordSalt property by calling the static
CreateSalt method of the Hashing class with a parameter value of 5. Then, generate a value for the
PasswordHash property by calling the CreatePasswordHash method of the Hashing class, passing
the password and PasswordSalt value as parameters.
Your code should resemble the following code example.

[Visual Basic]
Me.PasswordSalt = Hashing.CreateSalt(5)
Me.PasswordHash = Hashing.CreatePasswordHash(value, Me.PasswordSalt)

[Visual C#]
this.PasswordSalt = Hashing.CreateSalt(5);
this.PasswordHash = Hashing.CreatePasswordHash(value,
this.PasswordSalt);

4. Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the
AddRewardClaim method task in the task list. This task is located in the AddRewardClaim method:
In the task list, double-click the TODO: Ex2 - Implement the AddRewardClaim method task.
5. Immediately after the comment, add code to decrement the CurrentPoints property by the value of
the PointsUsed property of the claim object, set the ModifiedDate property to the current date and
time, and then add the claim object to the _rewardsClaimed list.
Your code should resemble the following code example.

[Visual Basic]
Me.CurrentPoints = Me.CurrentPoints - claim.PointsUsed
Me.ModifiedDate = DateTime.Now
Me._rewardsClaimed.Add(claim)
12 Lab Answer Key: Using POCO Classes with the Entity Framework

[Visual C#]
this.CurrentPoints -= claim.PointsUsed;
this.ModifiedDate = DateTime.Now;
this._rewardsClaimed.Add(claim);

6. Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the
RemoveRewardClaim method task in the task list. This task is located in the RemoveRewardClaim
method:
In the task list, double-click the TODO: Ex2 - Implement the RemoveRewardClaim method
task.
7. Immediately after the comment, add code to increment the CurrentPoints property by the value of
the PointsUsed property of the claim object, set the ModifiedDate property to the current date and
time, and then add the claim object to the _rewardsClaimed list.
Your code should resemble the following code example.

[Visual Basic]
Me.CurrentPoints = Me.CurrentPoints + claim.PointsUsed
Me.ModifiedDate = DateTime.Now
Me._rewardsClaimed.Remove(claim)

[Visual C#]
this.CurrentPoints += claim.PointsUsed;
this.ModifiedDate = DateTime.Now;
this._rewardsClaimed.Remove(claim);

8. Save the Contact file:


a. If you are using Visual Basic, on the File menu, click Save Contact.vb.
b. If you are using Visual C#, on the File menu, click Save Contact.cs.

Task 3: Add a business operation to the RewardsClaimed class


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the RewardsClaimed file by double-clicking the TODO: Ex2 - Implement the ModifyClaim
method task in the task list. This task is located in the ModifyClaim method:
In the task list, double-click the TODO: Ex2 - Implement the ModifyClaim method task.
3. Immediately after the comment, add code to perform the following tasks:
a. Increment the CurrentPoints property of the Contact property by the value of the PointsUsed
property of the current claim object.
b. Decrement the CurrentPoints property of the Contact property by the value of the pointsUsed
parameter.
c. Assign the rewardID parameter to the RewardID property.
d. Assign the pointsUsed parameter to the PointsUsed property.
Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Using POCO Classes with the Entity Framework 13

Me.Contact.CurrentPoints = Me.Contact.CurrentPoints + Me.PointsUsed


Me.Contact.CurrentPoints = Me.Contact.CurrentPoints - pointsUsed
Me.RewardID = rewardID
Me.PointsUsed = pointsUsed

[Visual C#]
this.Contact.CurrentPoints += this.PointsUsed;
this.Contact.CurrentPoints -= pointsUsed;
this.RewardID = rewardID;
this.PointsUsed = pointsUsed;

4. Save the RewardsClaimed file:


a. If you are using Visual Basic, on the File menu, click Save RewardsClaimed.vb.
b. If you are using Visual C#, on the File menu, click Save RewardsClaimed.cs.

Task 4: Modify the data access layer to work with your new POCO entities
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the DataAccessLayer file by double-clicking the TODO: Ex2 - Delete the call to the
EncryptPassword method task in the task list. This task is located in the AddContact method:
In the task list, double-click the TODO: Ex2 - Delete the call to the EncryptPassword method
task.
3. The Contact class now handles password encryption. Delete the line of code after the comment that
calls the EncryptPassword method.
Your code should resemble the following code example.

[Visual Basic]
' TODO: Ex2 - Delete the call to the EncryptPassword method.
' Encrypt the password in the detached object.

' Set the time modified.


contact.ModifiedDate = DateTime.Now

[Visual C#]
// TODO: Ex2 - Delete the call to the EncryptPassword method.
// Encrypt the password in the detached object.

// Set the time modified.


contact.ModifiedDate = DateTime.Now;

4. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Remove
the EncryptPassword method task in the task list. This task is located in the DataAccessLayer class:
In the task list, double-click the TODO: Ex2 - Remove the EncryptPassword method task.
5. The password encryption functionality is now in the AdventureWorks project. Delete the whole of the
EncryptPassword method from the DataAccessLayer class.
Your code should resemble the following code example.
14 Lab Answer Key: Using POCO Classes with the Entity Framework

[Visual Basic]
' TODO: Ex2 - Remove the EncryptPassword method.

#End Region

[Visual C#]
// TODO: Ex2 - Remove the EncryptPassword method.

#endregion

6. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Make sure
that all the claims are loaded task in the task list. This task is located in the DeleteContact method:
In the task list, double-click the TODO: Ex2 - Make sure that all the claims are loaded task.
7. The new plain-old CLR object (POCO) classes do not support automatic lazy loading. Immediately
after the comment, add code to load all of the claims that are related to the contact by using the
LoadProperty method.

Note: If you are using Visual Basic, refer to the RewardsClaimed navigation property by name by
supplying a string as a parameter. If you are using Visual C#, you can use a lambda that identifies the
objects in the RewardsClaimed property for the contact.

Your code should resemble the following code example.

[Visual Basic]

entities.LoadProperty(DirectCast(contactToDelete, Contact), "RewardsClaimed")

[Visual C#]

entities.LoadProperty((Contact)contactToDelete,
c => c.RewardsClaimed);

8. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Load the
contact and then call the AddRewardClaim method task in the task list. This task is located in the
CreateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - Load the contact and then call the
AddRewardClaim method task.
9. Immediately after the comment, add code to perform the following tasks:
a. Create an EntityKey object for the contact associated with the claim.
b. Use the TryGetObjectByKey method to load the contact entity.
c. Use the AddRewardClaim method to add the claim to the contact.
Your code should resemble the following code example.

[Visual Basic]
' Get the entity key you need.
Dim key As New EntityKey("AdventureWorksEntities.Contacts",
"ContactID", claim.ContactID)
Lab Answer Key: Using POCO Classes with the Entity Framework 15

Dim contact As Object = Nothing

' Make sure that the entity to modify is loaded.


If entities.TryGetObjectByKey(key, contact) Then
DirectCast(contact, Contact).AddRewardClaim(claim)
End If

[Visual C#]
// Get the entity key you need.
EntityKey key = new EntityKey("AdventureWorksEntities.Contacts",
"ContactID", claim.ContactID);

object contact = null;

// Make sure that the entity to modify is loaded.


if (entities.TryGetObjectByKey(key, out contact))
{
((Contact)contact).AddRewardClaim(claim);
}

10. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Remove
the claim before you refresh the contact task in the task list. This task is located in the
CreateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - Remove the claim before you refresh the
contact task.
11. Immediately after the comment, add code to remove the claim from the contact by calling the
RemoveRewardClaim method.
Your code should resemble the following code example.

[Visual Basic]
claim.Contact.RemoveRewardClaim(claim)

[Visual C#]
claim.Contact.RemoveRewardClaim(claim);

12. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
AddRewardClaim method task in the task list. This task is located in the CreateRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - Use the AddRewardClaim method task.
13. Immediately after the comment, add code to add the claim to the contact by calling the
AddRewardClaim method on the Contact property of the claim variable.
Your code should resemble the following code example.

[Visual Basic]
claim.Contact.AddRewardClaim(claim)

[Visual C#]
16 Lab Answer Key: Using POCO Classes with the Entity Framework

claim.Contact.AddRewardClaim(claim);

14. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
ModifyClaim business method task in the task list. This task is located in the UpdateRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - Use the ModifyClaim business method task.
15. Immediately after the comment, add code to call the ModifyClaim method.
Your code should resemble the following code example.

[Visual Basic]
rewardClaimToModify.ModifyClaim(rewardClaim.RewardID, rewardClaim.PointsUsed)

[Visual C#]
rewardClaimToModify.ModifyClaim(rewardClaim.RewardID,
rewardClaim.PointsUsed);

16. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Give the
original points back to the Contact task in the task list. This task is located in the
UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - Give the original points back to the Contact task.
17. Immediately after the comment, add code to call the ModifyClaim method, passing the
originalPoints variable as the second parameter.
Your code should resemble the following code example.

[Visual Basic]
rewardClaimToModify.ModifyClaim(rewardClaim.RewardID, originalPoints)

[Visual C#]
rewardClaimToModify.ModifyClaim(rewardClaim.RewardID, originalPoints);

18. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
ModifyClaim method to give the points to the contact task in the task list. This task is located in
the UpdateRewardsClaim method:
In the task list, double-click the TODO: Ex2 - Use the ModifyClaim method to give the points
to the contact task.
19. Immediately after the comment, add code to call the ModifyClaim method, passing the RewardID
property of the rewardClaim object as the first parameter and the PointsUsed property of the
rewardClaim object as the second parameter.
Your code should resemble the following code example.

[Visual Basic]
rewardClaimToModify.ModifyClaim(rewardClaim.RewardID, rewardClaim.PointsUsed)

[Visual C#]
rewardClaimToModify.ModifyClaim(rewardClaim.RewardID,
Lab Answer Key: Using POCO Classes with the Entity Framework 17

rewardClaim.PointsUsed);

20. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
RemoveRewardClaim method task in the task list. This task is located in the DeleteRewardsClaim
method:
In the task list, double-click the TODO: Ex2 - Use the RemoveRewardClaim method task.
21. Immediately after the comment, add code to call the RemoveRewardClaim method of the
relatedContact object, passing the rewardClaimToDelete object as a parameter.
Your code should resemble the following code example.

[Visual Basic]
relatedContact.RemoveRewardClaim(rewardClaimToDelete)

[Visual C#]
relatedContact.RemoveRewardClaim(rewardClaimToDelete);

22. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Add the
claim back while you refresh the contact task in the task list. This task is located in the
DeleteRewardsClaim method:
In the task list, double-click the TODO: Ex2 - Add the claim back while you refresh the
contact task.
23. Immediately after the comment, add code to call the AddRewardClaim method of the
relatedContact object, passing the rewardClaimToDelete object as a parameter.
Your code should resemble the following code example.

[Visual Basic]
relatedContact.AddRewardClaim(rewardClaimToDelete)

[Visual C#]
relatedContact.AddRewardClaim(rewardClaimToDelete);

24. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the
RemoveRewardClaim method again task in the task list. This task is located in the
DeleteRewardsClaim method:
In the task list, double-click the TODO: Ex2 - Use the RemoveRewardClaim method again
task.
25. Immediately after the comment, add code to call the RemoveRewardClaim method of the
relatedContact object, passing the rewardClaimToDelete object as a parameter.
Your code should resemble the following code example.

[Visual Basic]
relatedContact.RemoveRewardClaim(rewardClaimToDelete)

[Visual C#]
18 Lab Answer Key: Using POCO Classes with the Entity Framework

relatedContact.RemoveRewardClaim(rewardClaimToDelete);

26. Save the DataAccessLayer file:


a. If you are using Visual Basic, on the File menu, click Save DataAccessLayer.vb.
b. If you are using Visual C#, on the File menu, click Save DataAccessLayer.cs.

Task 5: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into
the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add,
modify, and delete Adventure Works reward data:
a. Click Create Customer, complete all the fields on the Add new customer form with your own
data (note that the password field shows blank when you move to another field). Make sure that
your customer has at least 10000 points, and then click Add.
b. Use the Search button to find the customer you added in the previous step.
c. Click Create Claim, select a reward, and then click OK.
d. Use the Delete Claim button to delete the claim you added in the previous step.
e. Use the Delete Customer button to delete the customer you added in step a.

Note: If you try to delete a contact that has reward data, you will see an exception thrown.

6. Close the application.


7. Close the solution, and then close Visual Studio:
a. On the File menu, click Close Solution.
b. On the File menu, click Exit.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 1

Module 9
Lab Answer Key: Building an N-Tier Solution by Using the
Entity Framework
Contents:
Exercise 1: Creating the Contacts and Orders Data Access Tier 2
Exercise 2: Protecting Data Access Operations 35
2 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

Lab 9: Building an N-Tier Solution by Using the


Entity Framework
Exercise 1: Creating the Contacts and Orders Data Access Tier
Task 1: Prepare the environment for the lab
1. Log on to the 10265A-GEN-DEV-09 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures Internet Information
Services(IIS) and creates the required users and groups:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), and then double-click Labfiles.
c. Right-click EnvSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running.

Task 2: Prepare the AdventureWorks database for the lab


In the E:\Labfiles folder, run AWReset.bat:
a. In the E:\Labfiles folder, double-click AWReset.bat.
b. Wait for the batch file to finish running.

Task 3: Open the starter project


1. In the E:\Labfiles\Lab09\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic), or
E:\Labfiles\Lab09\CS\Ex1\Starter folder (if you are using Microsoft Visual C#), run ExSetup.bat as an
administrator. This script adds the required virtual directories to IIS:
a. If you are using Visual Basic, in Windows Explorer, double-click Lab09, double-click VB,
double-click Ex1, and then double-click Starter.
b. If you are using Visual C#, in Windows Explorer, double-click Lab09, double-click CS, double-
click Ex1, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open Microsoft Visual Studio 2010 as an administrator:
a. Click Start, point to All Programs, click Microsoft Visual Studio 2010, right-click Microsoft Visual
Studio 2010, and then click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
3. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex1\Starter\OrdersDAL or
E:\Labfiles\Lab09\CS\Ex1\Starter\OrdersDAL folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab09\VB\Ex1\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 3

c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab09\CS\Ex1\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.

Task 4: Create the OrdersDAL class library


1. Add a new Class Library project named OrdersDAL to the solution:
a. On the File menu, point to Add, and then click New Project.
b. If you are using Visual Basic, in the Add New Project dialog box, in the Installed Templates list,
click Visual Basic.
c. If you are using Visual C#, in the Add New Project dialog box, in the Installed Templates list,
click Visual C#.
d. In the list of templates, click Class Library.
e. In the Name box, type OrdersDAL and then click OK.
2. Delete the Class1 file:
a. If you are using Visual Basic, in Solution Explorer, right-click Class1.vb, and then click Delete.
b. If you are using Visual C#, in Solution Explorer, right-click Class1.cs, and then click Delete.
c. In the Microsoft Visual Studio dialog box, click OK.
3. Add a new ADO.NET Entity Data Model (EDM) named AdventureWorksModel to the OrdersDAL
project. Generate the EDM from the AdventureWorks Microsoft SQL Server database and create
entities for the Contact, SalesOrderHeader, and SalesOrderDetail tables:
a. In Solution Explorer, right-click OrdersDAL, point to Add, and then click New Item.
b. In the Add New Item - OrdersDAL dialog box, in the list of templates, click ADO.NET Entity
Data Model, in the Name box, type AdventureWorksModel.edmx and then click Add.
c. In the Entity Data Model Wizard, on the Choose Model Contents page, click Generate from
database, and then click Next.
d. On the Choose Your Data Connection page, click New Connection.
e. In the Choose Data Source dialog box, in the Data source list, click Microsoft SQL Server, and
then click Continue.
f. In the Connection Properties dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress
g. In the Select or enter a database name box, enter AdventureWorks, and then click OK.
h. On the Choose Your Data Connection page, click Next.
i. On the Choose Your Database Objects page, expand Tables, select the Contact (Person),
SalesOrderHeader (Sales), and SalesOrderDetail (Sales) check boxes, and then click Finish.
4. Modify the EDM to generate self-tracking entities by adding the ADO.NET Self-Tracking Entity
Generator code generation item to the EDM. Name the code generation item
AdventureWorksModel.tt:
a. In the AdventureWorksModel.edmx window, right-click the design surface, and then click Add
Code Generation Item.
b. In the Add New Item OrdersDAL dialog box, in the templates list, click ADO.NET Self-
Tracking Entity Generator.
c. In the Name box, type AdventureWorksModel.tt and then click Add.
d. In the Security Warning dialog box, click OK.
5. Build the OrdersDAL project and correct any errors:
4 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

Important: Only build the OrdersDAL project. The OrderManagement project will not build
successfully because it is not yet complete.

In Solution Explorer, right-click OrdersDAL, and then click Build. Correct any errors.

Task 5: Create the OrdersClientLibrary class library


1. Add a new Class Library project named OrdersClientLibrary to the solution:
a. On the File menu, point to Add, and then click New Project.
b. In the Add New Project dialog box, in the templates list, click Class Library.
c. In the Name box, type OrdersClientLibrary and then click OK.
2. Delete the Class1 file:
a. If you are using Visual Basic, in Solution Explorer, right-click Class1.vb, and then click Delete.
b. If you are using Visual C#, in Solution Explorer, right-click Class1.cs, and then click Delete.
c. In the Microsoft Visual Studio dialog box, click OK.
3. Add a reference to the System.Runtime.Serialization assembly:
a. In Solution Explorer, right-click OrdersClientLibrary, and then click Add Reference.
b. In the Add Reference dialog box, on the .NET tab, click System.Runtime.Serialization, and
then click OK.
4. If you are using Visual Basic, change the Root namespace property of the OrdersClientLibrary project
to OrdersDAL.
a. In Solution Explorer, right-click OrdersClientLibrary, and then click Properties.
b. In the OrdersClientLibary window, click the Application tab.
c. In the Root namespace box, delete the text OrdersClientLibrary, and type OrdersDAL
d. On the File menu, click Close.
5. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project:
a. In Solution Explorer, expand the OrdersDAL project, right-click AdventureWorksModel.tt, and
then click Cut.
b. In Solution Explorer, right-click the OrdersClientLibrary project, and then click Paste.
6. Add a reference to the OrdersClientLibrary assembly to the OrdersDAL project:
a. In Solution Explorer, right-click OrdersDAL, and then click Add Reference.
b. In the Add Reference dialog box, on the Projects tab, click OrdersClientLibrary, and then click
OK.
7. Add a class named AdditionalMethods to the OrdersClientLibrary project:
a. In Solution Explorer, right-click the OrdersClientLibrary project, point to Add, and then click
Class.
b. In the Add New Item - OrdersClientLibrary dialog box, in the list of templates, click Class.
c. In the Name box, type AdditionalMethods and then click Add.
8. In the AdditionalMethods class, perform the following tasks:
a. Remove the AdditionalMethods class declaration.
b. If you are using Visual C#, change the namespace to OrdersDAL.
c. Create a public partial class named SalesOrderHeader.
d. Create a public partial class named SalesOrderDetail.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 5

Your code should resemble the following code example.

[Visual Basic]

Public Partial Class SalesOrderHeader

End Class

Public Partial Class SalesOrderDetail

End Class

[Visual C#]

namespace OrdersDAL
{

public partial class SalesOrderHeader


{

public partial class SalesOrderDetail


{

9. In the partial SalesOrderHeader class, add code to overwrite the ToString method by returning a
string that contains the SalesOrderID, ContactID, AccountNumber, OrderDate,
PurchaseOrderNumber, and TotalDue properties from the current object.
Your code should resemble the following code example.

[Visual Basic]
Partial Public Class SalesOrderHeader

Public Overrides Function ToString() As String

Return String.Format("Order: {0}" & vbTab & "Contact: {1}" &


vbTab & "Account: {2}" & vbTab &
"Date: {3}" & vbTab & "PO: {4}" & vbTab &
"Total: {5}", Me.SalesOrderID,
Me.ContactID, Me.AccountNumber,
Me.OrderDate, Me.PurchaseOrderNumber,
Me.TotalDue)
End Function

End Class

[Visual C#]
public partial class SalesOrderHeader
{
public override string ToString()
{
6 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

return string.Format("Order: {0}\tContact: {1}\tAccount: {2}\tDate: {3}\tPO:


{4}\tTotal: {5}", this.SalesOrderID, this.ContactID, this.AccountNumber, this.OrderDate,
this.PurchaseOrderNumber, this.TotalDue);
}
}

10. In the partial SalesOrderDetail class, add code to overwrite the ToString method by returning the
ProductID, OrderQty, UnitPrice, UnitPriceDiscount, and LineTotal properties of the current object.
Your code should resemble the following code example.

[Visual Basic]
Partial Public Class SalesOrderDetail
Public Overrides Function ToString() As String
Return String.Format("Product: {0}" + vbTab & "Qty: {1}" &
vbTab & "Price: {2}" & vbTab &
"Discount: {3}" & vbTab &
"Line Cost: {4}", Me.ProductID,
Me.OrderQty, Me.UnitPrice,
Me.UnitPriceDiscount, Me.LineTotal)
End Function
End Class

[Visual C#]

public partial class SalesOrderDetail


{
public override string ToString()
{
return string.Format("Product: {0}\tQty: {1}\tPrice: {2}\tDiscount:{3}\tLine
Cost: {4}", this.ProductID, this.OrderQty, this.UnitPrice, this.UnitPriceDiscount,
this.LineTotal);
}

11. Build the OrdersClientLibrary project and correct any errors:

Important: Only build the OrdersClientLibrary project. The OrderManagement project will not build
successfully because it is not yet complete.

In Solution Explorer, right-click OrdersClientLibrary, and then click Build. Correct any errors.

Task 6: Create the IOrdersService interface


1. Add a new Class Library project named OrdersService to the solution:
a. On the File menu, point to Add, and then click New Project.
b. In the Add New Project dialog box, in the list of templates, click Class Library.
c. In the Name box, type OrdersService and then click OK.
2. Delete the Class1 file:
a. If you are using Visual Basic, in Solution Explorer, right-click Class1.vb, and then click Delete.
b. If you are using Visual C#, in Solution Explorer, right-click Class1.cs, and then click Delete.
c. In the Microsoft Visual Studio dialog box, click OK.
3. Create an interface named IOrdersService in the OrdersService project:
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 7

a. In Solution Explorer, right-click OrdersService, point to Add, and then click New Item.
b. In the Add New Item - OrdersService dialog box, in the templates list, click Interface.
c. In the Name box, type IOrdersService and then click Add.
4. Add a reference to the OrdersClientLibrary assembly:
a. In Solution Explorer, right-click OrdersService, and then click Add Reference.
b. In the Add Reference dialog box, on the Projects tab, click OrdersClientLibrary, and then click
OK.
5. Add a reference to the OrdersDAL assembly:
a. In Solution Explorer, right-click OrdersService, and then click Add Reference.
b. In the Add Reference dialog box, on the Projects tab, click OrdersDAL, and then click OK.
6. Add a reference to the System.ServiceModel assembly:
a. In Solution Explorer, right-click OrdersService, and then click Add Reference.
b. In the Add Reference dialog box, on the .NET tab, click System.ServiceModel, and then click
OK.
7. Add a reference to the System.Runtime.Serialization assembly:
a. In Solution Explorer, right-click OrdersService, and then click Add Reference.
b. In the Add Reference dialog box, on the .NET tab, click System.Runtime.Serialization, and
then click OK.
8. Add a reference to the System.Data.Entity assembly:
a. In Solution Explorer, right-click OrdersService, and then click Add Reference.
b. In the Add Reference dialog box, on the .NET tab, click System.Data.Entity, and then click OK.
9. In the IOrdersService code file, add code to bring the System.Runtime.Serialization,
System.ServiceModel, and OrdersDAL namespaces into scope.
Your code should resemble the following code example.

[Visual Basic]

Imports System.Runtime.Serialization
Imports System.ServiceModel
Imports OrdersDAL

Public Interface IOrdersService

End Interface

[Visual C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Runtime.Serialization;
using System.ServiceModel;
using OrdersDAL;

namespace OrdersService
{
interface IOrdersService
8 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

{
}
}

10. In the IOrdersService file, before the interface declaration, write code to perform the following tasks:
a. Add a new class called ServiceFault. Prefix the class with the DataContract attribute.
b. Add a public string field to the ServiceFault class called ExceptionType. Prefix the field with the
DataMember attribute.
c. Add a public string field to the ServiceFault class called ExceptionMessage. Prefix the field with
the DataMember attribute.
Your code should resemble the following code example.

[Visual Basic]
<DataContract()>
Public Class ServiceFault

<DataMember()>
Public ExceptionType As String
<DataMember()>
Public ExceptionMessage As String
End Class

[Visual C#]

[DataContract]
public class ServiceFault

{
[DataMember]
public string ExceptionType;

[DataMember]
public string ExceptionMessage;
}

11. In the IOrdersService file, perform the following tasks:


a. Make the IOrdersService interface public (if it is not already public).
b. Specify that the IOrdersService interface defines a service contract called OrdersWebService. Use
http://microsoft.com as the Web service namespace.
Your code should resemble the following code example.

[Visual Basic]

<ServiceContract (Name:="OrdersWebService",
Namespace:="http://microsoft.com")>
Public Interface IOrdersService

End Interface

[Visual C#]

[ServiceContract (Name="OrdersWebService", Namespace="http://microsoft.com")]


public interface IOrdersService
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 9

{
}

12. In the IOrdersService interface, perform the following tasks:


a. Define a method called GetContactDetails that returns an object of type Contact and accepts
an integer named contactID as a parameter.
b. Specify that the GetContactDetails method is Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetContactDetails method returns a message of type ServiceFault if an
exception occurs.
Your code should resemble the following code example.

[Visual Basic]
Public Interface IOrdersService

<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetContactDetails(ByVal contactID As Integer) As Contact

End Interface

[Visual C#]

public interface IOrdersService


{

[FaultContract(typeof(ServiceFault))]
[OperationContract]
Contact GetContactDetails(int contactID);

13. In the IOrdersService interface, perform the following tasks:


a. Define a method called GetAllContactsInRange that returns a generic IEnumerable object of
with a type parameter of Contact, and accepts an integer named lowerBound and an integer
named upperBound as parameters.
b. Specify that the GetAllContactsInRange method is Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetAllContactsInRange method returns a message of type ServiceFault if an
exception occurs.
Your code should resemble the following code example.

[Visual Basic]

Public Interface IOrdersService

...
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetAllContactsInRange(ByVal lowerBound As Integer,
ByVal upperBound As Integer) _
As IEnumerable(Of Contact)

End Interface
10 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

[Visual C#]

public interface IOrdersService

{
...

[FaultContract(typeof(ServiceFault))]
[OperationContract]
IEnumerable<Contact> GetAllContactsInRange(
int lowerBound, int upperBound);
}

14. In the IOrdersService interface, perform the following tasks:


a. Define a method called GetOrderDetails that returns an object of type SalesOrderHeader and
accepts an integer named orderID as a parameter.
b. Specify that the GetOrderDetails method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetOrderDetails method returns a message of type ServiceFault if an exception
occurs.
Your code should resemble the following code example.

[Visual Basic]

Public Interface IOrdersService


...
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetOrderDetails(ByVal orderID As Integer) _
As SalesOrderHeader

End Interface

[Visual C#]

public interface IOrdersService


{
...
[FaultContract(typeof(ServiceFault))]
[OperationContract]
SalesOrderHeader GetOrderDetails(int orderID);
}

15. In the IOrdersService interface, perform the following tasks:


a. Define a method called GetOrdersForContact that returns a generic IEnumerable object of with
a type parameter of SalesOrderHeader and accepts an integer named contactID as a
parameter.
b. Specify that the GetOrdersForContact method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetOrdersForContact method returns a message of type ServiceFault if an
exception occurs.
Your code should resemble the following code example.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 11

[Visual Basic]

Public Interface IOrdersService


...
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetOrdersForContact(ByVal contactID As Integer) _
As IEnumerable(Of SalesOrderHeader)
End Interface

[Visual C#]

public interface IOrdersService


{
...
[FaultContract(typeof(ServiceFault))]
[OperationContract]
IEnumerable<SalesOrderHeader> GetOrdersForContact(int contactID);
}

16. In the IOrdersService interface, perform the following tasks:


a. Define a method called GetOrdersForProduct that returns a generic IEnumerable object of
with a type parameter of SalesOrderHeader and accepts an integer named productID as a
parameter.
b. Specify that the GetOrdersForProduct method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetOrdersForProduct method returns a message of type ServiceFault if an
exception occurs.
Your code should resemble the following code example.

[Visual Basic]

Public Interface IOrdersService

...
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetOrdersForProduct(ByVal productID As Integer) _
As IEnumerable(Of SalesOrderHeader)

End Interface

[Visual C#]

public interface IOrdersService


{
...
[FaultContract(typeof(ServiceFault))]
[OperationContract]
IEnumerable<SalesOrderHeader> GetOrdersForProduct(int productID);
}

17. In the IOrdersService interface, perform the following tasks:


12 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

a. Define a method called GetAllOrdersInRange that returns a generic IEnumerable object with a
type parameter of SalesOrderHeader and accepts an integer named lowerBound and an
integer named upperBound as parameters.
b. Specify that the GetAllOrdersInRange method is a Web service operation that is part of the
OrdersWebService service contract.
c. Specify that the GetAllOrdersInRange method returns a message of type ServiceFault if an
exception occurs.
Your code should resemble the following code example.

[Visual Basic]

Public Interface IOrdersService

...
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetAllOrdersInRange(ByVal lowerBound As Integer,
ByVal upperBound As Integer) _
As IEnumerable(Of SalesOrderHeader)

End Interface

[Visual C#]
public interface IOrdersService
{
...
[FaultContract(typeof(ServiceFault))]
[OperationContract]
IEnumerable<SalesOrderHeader> GetAllOrdersInRange(
int lowerBound, int upperBound);

18. Build the project and correct any errors:

Important: Only build the OrdersService project. The OrderManagement project will not build
successfully because it is not yet complete.

In Solution Explorer, right-click OrdersService, and then click Build. Correct any errors.

Task 7: Implement exception handling and logging


1. Create a class named OrdersServiceImpl in the OrdersService project:
In Solution Explorer, right-click OrdersService, point to Add, and then click Class.
In the Add New Item - OrdersService dialog box, in the list of templates, click Class.
In the Name box, type OrdersServiceImpl and then click Add.
2. In the OrdersServiceImpl code file, write code to bring the following namespaces into scope:
System.Text

Note: If you are using Visual C#, the System.Text namespace is already in scope and there is no need to
add it again.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 13

System.ServiceModel
System.Diagnostics
System.Threading
OrdersDAL
System.Security.Permissions
Your completed code should resemble the following code example.

[Visual Basic]
Imports System.Text
Imports System.ServiceModel
Imports System.Diagnostics
Imports System.Threading
Imports OrdersDAL
Imports System.Security.Permissions

...

[Visual C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text

using System.ServiceModel;
using System.Diagnostics;
using System.Threading;
using OrdersDAL;
using System.Security.Permissions;

...

3. In the OrdersServiceImpl code file, perform the following tasks:


a. Prefix the OrdersWebService class with the ServiceBehavior attribute, specify the name of the
service as OrdersWebService, and set the namespace to http://microsoft.com.
b. Set the InstanceContextMode property of the ServiceBehavior attribute to PerCall.
c. Set the ConcurrencyMode property of the ServiceBehavior attribute to Multiple.
d. Declare the OrdersServiceImpl class as a public class (if it is not already public) that implements
the IOrdersService interface.
Your completed code should resemble the following code example.

[Visual Basic]

<ServiceBehavior(Name :="OrdersWebService",
Namespace := "http://microsoft.com",
InstanceContextMode := InstanceContextMode.PerCall,
ConcurrencyMode := ConcurrencyMode.Multiple)>

Public Class OrdersServiceImpl


Implements IOrdersService

End Class
14 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

[Visual C#]

[ServiceBehavior(Name ="OrdersWebService", Namespace = "http://microsoft.com",


InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode =
ConcurrencyMode.Multiple)]
public class OrdersServiceImpl : IOrdersService
{

4. In the OrdersServiceImpl class, write code to perform the following tasks:


a. Declare a constant integer named maxContactsCount and assign it a value of 500.
b. Declare a constant integer named maxOrdersCount and assign it a value of 400.
c. Declare a constant string named eventSource and assign it the value "Orders Service".
d. Declare a constant string named eventLog and assign it the value "Application".
Your code should resemble the following code example.

[Visual Basic]

Public Class OrdersServiceImpl


Implements IOrdersService

Private Const maxContactsCount As Integer = 500


Private Const maxOrdersCount As Integer = 400

Private Const eventSource As String = "Orders Service"


Private Const eventLog As String = "Application"

End Class

[Visual C#]

public class OrdersServiceImpl : IOrdersService


{
private const int maxContactsCount = 500;
private const int maxOrdersCount = 400;

private const string eventSource = "Orders Service";


private const string eventLog = "Application";
}

5. In the OrdersServiceImpl class, write code to perform the following tasks:


a. Create a new private method named logException that accepts an Exception object named ex
and a string object named eventName as parameters. This method should not return a value.
b. In the logException method, verify that the event source identified by the eventSource constant
exists by calling the SourceExists method of the EventLog class in the System.Diagnostics
namespace. If the event source does not exist, create it by calling the CreateEventSource
method of the EventLog class, specifying the eventSource and eventLog constants as
parameters.
c. Declare a string named eventMessage and assign it a value by combining the eventName
variable, the Message property of the ex variable, and the value of the
Thread.CurrentPrincipal.Identity.Name property.
d. Call the WriteEntry method of the EventLog class, passing the eventSource and eventMessage
variables as parameters and specifying Error as the EventLogEntryType parameter.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 15

Your code should resemble the following code example.

[Visual Basic]

Private Sub logException(ByVal ex As Exception,


ByVal eventName As String)

If Not System.Diagnostics.EventLog.SourceExists(eventSource) Then


System.Diagnostics.EventLog.CreateEventSource(
eventSource, eventLog)
End If

Dim eventMessage As String = String.Format("{0}: {1}: {2}:",


eventName, ex.Message, Thread.CurrentPrincipal.Identity.Name)

System.Diagnostics.EventLog.WriteEntry(eventSource, eventMessage,
EventLogEntryType.Error)

End Sub

[Visual C#]

private void logException(Exception ex, string eventName)


{
if (!EventLog.SourceExists(eventSource))
{
EventLog.CreateEventSource(eventSource, eventLog);
}
string eventMessage = string.Format("{0}: {1}: {2}:", eventName,
ex.Message, Thread.CurrentPrincipal.Identity.Name);
EventLog.WriteEntry(eventSource, eventMessage,
EventLogEntryType.Error);
}

6. In the OrdersServiceImpl class, create a new private method named handleException that accepts
the following parameters:
An Exception object named ex.
A string parameter named operationName.
An optional integer parameter named operationData with a default value of 0.
Your code should resemble the following code example.

[Visual Basic]

Private Sub handleException(ByVal ex As Exception,


ByVal operationName As String,
Optional ByVal operationData As Integer = 0)

End Sub

[Visual C#]

private void handleException(Exception ex, string operationName, int operationData =0)


{
16 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

7. In the handleException method, write code to perform the following tasks:


a. Create a new StringBuilder object named eventMessageBuilder.
b. Append the message "Failure in {0}" to the eventMessageBuilder object, and specify the value
of the operationName parameter as the {0} placeholder.
c. If the value of the operationData variable is not equal to 0, append the value of the
operationData parameter to the eventMessageBuilder object.
d. Call the logException method, passing the ex object and the string value of the
eventMessageBuilder object as parameters.
Your code should resemble the following code example.

[Visual Basic]
Private Sub handleException(ByVal ex As Exception,
ByVal operationName As String,
Optional ByVal operationData As Integer = 0)

Dim eventMessageBuilder As New StringBuilder()


eventMessageBuilder.Append(
String.Format("Failure in {0}", operationName))

If operationData <> 0 Then


eventMessageBuilder.Append(String.Format(":Data {0}",
operationData))
End If
Dim eventMessage As String = eventMessageBuilder.ToString()
logException(ex,eventMessage)
End Sub

[Visual C#]
private void handleException(Exception ex, string operationName, int operationData =0)
{
StringBuilder eventMessageBuilder = new StringBuilder();
eventMessageBuilder.Append(
string.Format("Failure in {0}", operationName));

if (operationData !=0)
{
eventMessageBuilder.Append(string.Format(":Data {0}", operationData));
}
string eventMessage = eventMessageBuilder.ToString();
logException(ex,eventMessage);
}

8. In the handleException method, add code to perform the following tasks:


a. If the ex object is of type ApplicationException, create a new ServiceFault object named sf. Set
the ExceptionType property to the type of the ex object, and set the ExceptionMessage
property by using Message property of the ex object.
b. Throw a new FaultException<ServiceFault> exception, specifying the sf object and the string
value of the eventMessageBuilder object as parameters.
c. If the ex object is some other type of exception, create a new ServiceFault object named sf, and
set the ExceptionType property to the type of the ex object and the value of the
ExceptionMessage property to the string "Exception occurred fetching data".
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 17

d. Throw a new FaultException<ServiceFault> exception. Pass the value of the sf object and the
message "Failure in {0}" where the {0} placeholder is the value of the operationName variable as
parameters to the constructor.
Your code should resemble the following code example.

[Visual Basic]
Private Sub handleException(ByVal ex As Exception,
ByVal operationName As String,
Optional ByVal operationData As Integer = 0)
...
If TypeOf ex Is ApplicationException Then
Dim sf As New ServiceFault() With {
.ExceptionType = ex.GetType().ToString(),
.ExceptionMessage = ex.Message
}

Throw New FaultException(Of ServiceFault)(


sf, eventMessageBuilder.ToString())
Else
Dim sf As New ServiceFault() With {
.ExceptionType = ex.GetType.ToString(),
.ExceptionMessage = "Exception occurred fetching data"
}

Throw New FaultException(Of ServiceFault)(


sf, String.Format("Failure in {0}", operationName))
End If
End Sub

[Visual C#]

private void handleException(Exception ex, string operationName, int operationData =0)


{
...

if (ex is ApplicationException)
{
ServiceFault sf = new ServiceFault
{
ExceptionType = ex.GetType().ToString(),
ExceptionMessage = ex.Message
};

throw new FaultException<ServiceFault>(


sf,eventMessageBuilder.ToString());
}
else
{
ServiceFault sf = new ServiceFault
{
ExceptionType = ex.GetType().ToString(),
ExceptionMessage = "Exception occurred fetching data"
};

throw new FaultException<ServiceFault>(


sf,string.Format("Failure in {0}", operationName));
}
}

9. Save the OrdersServiceImpl file:


18 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

a. If you are using Visual Basic, on the File menu, click Save OrdersServiceImpl.vb.
b. If you are using Visual C#, on the File menu, click Save OrdersServiceImpl.cs.

Task 8: Implement the IOrdersService interface


1. In the OrdersServiceImpl class, generate stub methods for each of the items in the IOrdersService
interface:
a. If you are using Visual Basic, in the OrdersServiceImpl class declaration, move to the end of the
line that contains the code Implements IOrdersService, and then press ENTER. Method stubs for
each of the methods in the IOrdersService interface will be added to the end of the class.
b. If you are using Visual C#, in the OrdersServiceImpl class declaration, right-click IOrdersService,
point to Implement Interface, and then click Implement Interface. Method stubs for each of
the methods in the IOrdersService interface will be added to the end of the class.
2. Locate the GetContactDetails method. This method takes an integer value as a parameter and
returns a Contact object.
3. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
4. In the body of the method, add code to perform the following tasks:
a. Create a Contact object named contact and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a Language-Integrated Query (LINQ) query named matchingContacts that retrieves all of
the Contact entities by using the AdventureWorksEntities object, with a ContactID property
that is equal to the value of the contactID variable.
d. If there is at least one Contact object in the matchingContacts collection, return it; otherwise,
return a null (Nothing in Visual Basic) value. If there is more than one matching contact, return
the first one found.

Note: The ContactID should be unique, so there should only be at most one matching contact. However,
it is good practice to write defensive code just in case a database administrator amends the structure of
the Contact table in the database and creates a different key column.

e. Handle any exceptions by calling the handleException method; pass the Exception object, the
method name, and the contactID variable as parameters before returning a null (Nothing in
Visual Basic) value.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetContactDetails(ByVal contactID As Integer) As OrdersDAL.Contact
Implements IOrdersService.GetContactDetails
Dim contact As Contact = Nothing

Try
Using context As New AdventureWorksEntities()

Dim matchingContacts As IEnumerable(Of Contact) =


From c In context.Contacts
Where c.ContactID = contactID
Select c

If matchingContacts.Count() > 0 Then


contact = matchingContacts.First
End If
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 19

Return contact

End Using
Catch ex As Exception
Me.handleException(ex, "GetContactDetails", contactID)
Return Nothing
End Try

End Function

[Visual C#]

public Contact GetContactDetails(int contactID)


{
Contact contact = null;

try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
IEnumerable<Contact> matchingContacts =
from c in context.Contacts
where c.ContactID == contactID
select c;

if (matchingContacts.Count() > 0)
{
contact = matchingContacts.First();
}

return contact;
}
}
catch (Exception ex)
{
this.handleException(ex, "GetContactDetails", contactID);
return null;
}
}

5. Locate the GetAllContactsInRange method. This method takes two integer values, lowerBound and
upperBound, as parameters and returns an IEnumerable list of Contact objects.
6. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
7. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named contacts by using the Contact type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the Contact entities where the ContactID property is
between the values of the lowerBound and upperBound variables. The result of this query should
be assigned to the contacts object.
d. If the number of objects in the contacts collection is greater than or equal to the value of the
maxContactsCount constant, throw a new ApplicationException exception with the message
"Too many contacts".
20 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

e. Return the contacts collection as a generic List object. If you are using Visual C#, specify the
Contact type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object and
the method name as parameters before returning null (Nothing in Visual Basic).
Your code should resemble the following code example.

[Visual Basic]

Public Function GetAllContactsInRange(ByVal lowerBound As Integer, ByVal upperBound As


Integer) As System.Collections.Generic.IEnumerable(Of OrdersDAL.Contact) Implements
IOrdersService.GetAllContactsInRange

Dim contacts As IEnumerable(Of Contact) = Nothing

Try
Using context As New AdventureWorksEntities()

contacts = From c In context.Contacts


Where c.ContactID >= lowerBound And
c.ContactID <= upperBound
Select c

If contacts.Count() >= maxContactsCount Then


Throw New ApplicationException("Too many contacts")

End If

Return contacts.ToList()

End Using
Catch ex As Exception
Me.handleException(ex, "GetAllContacts")
Return Nothing
End Try

End Function

[Visual C#]

public IEnumerable<Contact> GetAllContactsInRange(int lowerBound, int upperBound)


{
IEnumerable<Contact> contacts = null;

try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
contacts = from c in context.Contacts
where c.ContactID >= lowerBound &&
c.ContactID <= upperBound
select c;
if (contacts.Count() >= maxContactsCount)
{
throw new ApplicationException("Too many contacts");
}

return contacts.ToList<Contact>();
}
}
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 21

catch (Exception ex)


{
this.handleException(ex, "GetAllContacts");
return null;
}
}

8. Locate the GetOrderDetails method. This method takes an integer value as a parameter and returns
a SalesOrderHeader object.
9. If you are using Visual C#, delete the default method that throws a NotImplementedException
exception.
10. In the body of the method, add code to perform the following tasks:
a. Create a SalesOrderHeader object named order and assign it the value null (Nothing in Visual
Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query named matchingOrders that retrieves all of the SalesOrderHeader entities
and the related SalesOrderDetail entities where the SalesOrderID property matches the value in
the orderID variable.
d. If the number of objects in the matchingOrders collection is greater than zero, return the first
SalesOrderHeader object in the collection; otherwise, return null (Nothing in Visual Basic).
e. Handle any Exception exceptions by calling the handleException method; pass the exception
object, the method name, and the orderID variable as parameters before returning null (Nothing
in Visual Basic).
Your code should resemble the following code example.

[Visual Basic]

Public Function GetOrderDetails(ByVal orderID As Integer) As OrdersDAL.SalesOrderHeader


Implements IOrdersService.GetOrderDetails

Dim order As SalesOrderHeader = Nothing

Try
Using context = New AdventureWorksEntities

Dim matchingOrders As IEnumerable(Of SalesOrderHeader) =


From o In context.SalesOrderHeaders.Include(
"SalesOrderDetails")
Where o.SalesOrderID = orderID
Select o

If matchingOrders.Count() > 0 Then


order = matchingOrders.First()
End If

Return order

End Using

Catch ex As Exception
Me.handleException(ex, "GetOrderDetails", orderID)
Return Nothing
End Try

End Function
22 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

[Visual C#]

public SalesOrderHeader GetOrderDetails(int orderID)


{
SalesOrderHeader order = null;

try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{

IEnumerable<SalesOrderHeader> matchingOrders =
from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.SalesOrderID == orderID
select o;

if (matchingOrders.Count() > 0)
{
order = matchingOrders.First();
}

return order;
}
}
catch (Exception ex)
{
this.handleException(ex, "GetOrderDetails", orderID);
return null;
}
}

11. Locate the GetOrdersForContact method. This method takes an integer value as a parameter and
returns an IEnumerable list of SalesOrderHeader objects.
12. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
13. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related
SalesOrderDetail entities where the ContactID property matches the value in the contactID
variable. Assign the result of this query to the orders object.
d. If the number of objects in the orders collection is greater than or equal to the value of the
maxOrdersCount constant, throw a new ApplicationException exception with the message "Too
many orders".
e. Return the orders collection as a generic List object. If you are using Visual C#, specify the
SalesOrderHeader type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object, the
method name, and the contactID variable as parameters before returning null (Nothing in Visual
Basic).
Your code should resemble the following code example.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 23

[Visual Basic]

Public Function GetOrdersForContact(ByVal contactID As Integer) As


System.Collections.Generic.IEnumerable(Of OrdersDAL.SalesOrderHeader) Implements
IOrdersService.GetOrdersForContact

Dim orders As IEnumerable(Of SalesOrderHeader) = Nothing

Try
Using context = New AdventureWorksEntities()

orders = From o In context.SalesOrderHeaders.Include(


"SalesOrderDetails")
Where o.ContactID = contactID
Select o

If orders.Count >= maxOrdersCount Then


Throw New ApplicationException("Too many orders")
End If

Return orders.ToList()

End Using

Catch ex As Exception
Me.handleException(ex, "GetOrdersForContact", contactID)
Return Nothing
End Try

End Function

[Visual C#]

public IEnumerable<SalesOrderHeader> GetOrdersForContact(int contactID)


{
IEnumerable<SalesOrderHeader> orders = null;

try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
orders = from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.ContactID == contactID
select o;

if (orders.Count() >= maxOrdersCount)


{
throw new ApplicationException("Too many orders");
}

return orders.ToList<SalesOrderHeader>();
}
}
catch (Exception ex)
{
this.handleException(ex, "GetOrdersForContact", contactID);
return null;
}
}
24 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

14. Locate the GetOrdersForProduct method. This method takes an integer value as a parameter and
returns an IEnumerable list of SalesOrderHeader objects.
15. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
16. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related
SalesOrderDetail entities where the ProductID property of at least one of the SalesOrderDetail
entities for the SalesOrderHeader entity matches the productID variable. Assign the result of this
query to the orders object.

Note: The SalesOrderDetail records for an order specify the products being ordered. An order can have
one or more SalesOrderDetail records. To find all orders for a specific product, you must find all
SalesOrderDetail records that match the product and return the SalesOrderHeader objects that
reference these SalesOrderDetail records.

d. If the number of objects in the orders collection is greater than or equal to the value of the
maxOrdersCount constant, throw a new ApplicationException exception with the message "Too
many orders".
e. Return the orders collection as a generic List object. If you are using Visual C#, specify the
SalesOrderHeader type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object, and
the method name as parameters before returning null (Nothing in Visual Basic).
Your code should resemble the following code example.

[Visual Basic]

Public Function GetOrdersForProduct(ByVal productID As Integer) As


System.Collections.Generic.IEnumerable(Of OrdersDAL.SalesOrderHeader) Implements
IOrdersService.GetOrdersForProduct

Dim orders As IEnumerable(Of SalesOrderHeader) = Nothing

Try

Using context = New AdventureWorksEntities()

orders = From o In context.SalesOrderHeaders.Include(


"SalesOrderDetails")
Where o.SalesOrderDetails.Any(
Function(d) d.ProductID = productID)
Select o

If orders.Count() >= maxOrdersCount Then


Throw New ApplicationException("Too many orders")

End If

Return orders.ToList()

End Using

Catch ex As Exception
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 25

Me.handleException(ex, "GetOrdersForProduct")
Return Nothing

End Try

End Function

[Visual C#]

public IEnumerable<SalesOrderHeader> GetOrdersForProduct(int productID)


{
IEnumerable<SalesOrderHeader> orders = null;

try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
orders = from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.SalesOrderDetails.Any(
d => d.ProductID == productID)
select o;

if (orders.Count() >= maxOrdersCount)


{
throw new ApplicationException("Too many orders");
}

return orders.ToList<SalesOrderHeader>();
}
}

catch (Exception ex)


{
this.handleException(ex, "GetOrdersForProduct");
return null;
}
}

17. Locate the GetAllOrdersInRange method. This method takes two integer values, lowerBound and
upperBound, as parameters and returns an IEnumerable list of Contact objects.
18. If you are using Visual C#, delete the default method body that throws a
NotImplementedException exception.
19. In the body of the method, add code to perform the following tasks:
a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type
parameter, and assign it the value null (Nothing in Visual Basic).
b. Create a new AdventureWorksEntities object.
c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related
SalesOrderDetails entities where the value in the SalesOrderID property is between the
lowerBound and upperBound variables. The result of this query should be assigned to the orders
object.
d. If the number of objects in the orders collection is greater than or equal to the value of the
maxOrdersCount constant, throw a new ApplicationException exception with the message "Too
many orders".
26 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

e. Return the orders collection as a generic List object. If you are using Visual C#, specify the
SalesOrderHeader type as the type parameter for the List object.
f. Handle any exceptions by calling the handleException method; pass the exception object, and
the method name as parameters before returning null (Nothing in Visual Basic).
Your code should resemble the following code example.

[Visual Basic]

Public Function GetAllOrdersInRange(ByVal lowerBound As Integer, ByVal upperBound As


Integer) As System.Collections.Generic.IEnumerable(Of OrdersDAL.SalesOrderHeader)
Implements IOrdersService.GetAllOrdersInRange

Dim orders As IEnumerable(Of SalesOrderHeader) = Nothing

Try
Using context = New AdventureWorksEntities

orders = From o In context.SalesOrderHeaders.Include(


"SalesOrderDetails")
Where o.SalesOrderID >= lowerBound And
o.SalesOrderID <= upperBound
Select o

If orders.Count >= maxOrdersCount Then


Throw New ApplicationException("Too many orders")
End If
Return orders.ToList()

End Using
Catch ex As Exception
Me.handleException(ex, "GetAllOrdersInRange")
Return Nothing
End Try

End Function

[Visual C#]

public IEnumerable<SalesOrderHeader> GetAllOrdersInRange(int lowerBound, int upperBound)


{
IEnumerable<SalesOrderHeader> orders = null;

try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
orders = from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.SalesOrderID >= lowerBound &&
o.SalesOrderID <= upperBound
select o;

if (orders.Count() >= maxOrdersCount)


{
throw new ApplicationException("Too many orders");
}

return orders.ToList<SalesOrderHeader>();
}
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 27

}
catch (Exception ex)
{
this.handleException(ex, "GetAllOrdersInRange");
return null;
}
}

20. Build the OrdersService project and correct any errors:

Important: Only build the OrdersService project. The OrderManagement project will not build
successfully because it is not yet complete.

In Solution Explorer, right-click OrdersService, and then click Build. Correct any errors.

Task 9: Create the OrdersWebService Web service


1. Add a new empty ASP.NET Web Application project named OrdersWebService to the solution:
a. On the File menu, point to Add, and then click New Project.
b. In the Add New Project dialog box, in the Installed Templates pane click Web.
c. In the list of templates, click ASP.NET Empty Web Application.
d. In the Name box, type OrdersWebService and then click OK.
2. Configure the project to use the local IIS Web server with the URL
http://localhost/OrdersWebService:
a. In Solution Explorer, right-click OrdersWebService, and then click Properties.
b. In the OrdersWebService window, click the Web tab, and then click Use Local IIS Web server.
c. In the Project Url box, check that the URL is set to http://localhost/OrdersWebService.
d. On the File menu, click Close.
3. Add a new WCF service called OrdersWebService.svc to the OrdersWebService project:
a. In Solution Explorer, right-click OrdersWebService, point to Add, and then click New Item.
b. In the Add New Item - OrdersWebService dialog box, in the list of templates, click WCF
Service.
c. In the Name box, type OrdersWebService.svc and then click Add.
4. Delete the IOrdersWebService code file from the OrdersWebService project:
a. If you are using Visual Basic, in Solution Explorer, right-click IOrdersWebService.vb, and then
click Delete.
b. If you are using Visual C#, in Solution Explorer, right-click IOrdersWebService.cs, and then click
Delete.
c. In the Microsoft Visual Studio dialog box, click OK.
5. Add a reference to the OrdersClientLibrary assembly:
a. In Solution Explorer, right-click OrdersWebService, and then click Add Reference.
b. In the Add Reference dialog box, on the Projects tab, click OrdersClientLibrary, and then click
OK.
6. Add a reference to the OrdersDAL assembly:
a. In Solution Explorer, right-click OrdersWebService, and then click Add Reference.
b. In the Add Reference dialog box, on the Projects tab, click OrdersDAL, and then click OK.
28 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

7. Add a reference to the OrdersService assembly:


a. In Solution Explorer, right-click OrdersWebService, and then click Add Reference.
b. In the Add Reference dialog box, on the Projects tab, click OrdersService, and then click OK.
8. Delete the OrdersWebService code-behind file:
a. In the Solution Explorer toolbar, click Show All Files.
b. In Solution Explorer, expand OrdersWebService.svc.
c. If you are using Visual Basic, right-click OrdersWebService.svc.vb, and then click Delete.
d. If you are using Visual C#, right-click OrdersWebService.svc.cs, and then click Delete.
e. In the Microsoft Visual Studio dialog box, click OK.
9. In the OrdersWebService.svc file, delete the existing markup code:
a. In Solution Explorer, right-click OrdersWebService.svc, and then click Open.
b. In the OrdersWebService.svc window, delete the existing markup code.
10. In the OrdersWebService.svc file, add markup statements that perform the following tasks:
a. Create a ServiceHost instance and set the Service property to point to the OrdersServiceImpl
service that is defined in the OrdersService project.
b. Specify that this service is located in the OrdersService assembly.
Your code should resemble the following code example.

<%@ ServiceHost Service="OrdersService.OrdersServiceImpl" %>


<%@ Assembly Name="OrdersService" %>

Note: Visual Studio reports that it cannot find the OrdersService assembly. This warning will disappear
when you build the project and you can safely ignore it.

11. Delete the Web.config file from the OrdersWebService project:


a. In Solution Explorer, right-click Web.config, and then click Delete.
b. In the Microsoft Visual Studio dialog box, click OK.
12. Add the existing Web.config file from the E:\Labfiles\Lab09\CS\Ex1\Starter or
E:\Labfiles\Lab09\VB\Ex1\Starter folder to the OrdersWebService project:
a. In Solution Explorer, right-click OrdersWebService, point to Add, and then click Existing Item.
b. If you are using Visual Basic, in the Add Existing Item - OrdersWebService dialog box, move to
the E:\Labfiles\Lab09\VB\Ex1\Starter folder, click web.config, and then click Add.
c. If you are using Visual C#, in the Add Existing Item - OrdersWebService dialog box, move to
the E:\Labfiles\Lab09\CS\Ex1\Starter folder, click web.config, and then click Add.
13. Build the OrdersWebService project and correct any errors:

Important: Only build the OrdersWebService project. The OrderManagement project will not build
successfully because it is not yet complete.

In Solution Explorer, right-click OrdersWebService, and then click Build. Correct any errors.

Task 10: Configure the OrderManagement application


1. In the OrderManagement project, add a reference to the OrdersClientLibrary assembly:
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 29

a. In Solution Explorer, right-click OrderManagement, and then click Add Reference.


b. In the Add Reference dialog box, on the Projects tab, click OrdersClientLibrary, and then click
OK.
2. Add a service reference to the OrdersWebService service to the OrderManagement application.
Generate the service reference in the OWService namespace. The URL of the OrdersWebService is
http://localhost/OrdersWebService/OrdersWebService.svc. Use System.Collections.Generic.List as
the collection type and ensure that you reuse types in referenced assemblies:
a. In Solution Explorer, right-click OrderManagement, and then click Add Service Reference.
b. In the Add Service Reference dialog box, click Discover.
c. In the Namespace box, type OWService and then click Advanced.
d. In the Service Reference Settings dialog box, in the Collection type list, click
System.Collections.Generic.List.
e. Select the Reuse types in referenced assemblies check box, and then click Reuse types in all
referenced assemblies.
f. In the Service Reference Settings dialog box, click OK.
g. In the Add Service Reference dialog box, click OK.
3. In the app.config file, modify the definition of the wsHttpBinding binding by increasing the
maxBufferPoolSize property to 5242880 and the maxReceivedMessageSize property to 5242880:
a. In Solution Explorer, in the OrderManagement project, right-click app.config, and then click
Open.
b. In the app.config window, in the wsHttpBinding configuration section, change the value of the
maxBufferPoolSize property to 5242880.
c. In the wsHttpBinding configuration section, change the value of the maxReceivedMessageSize
property to 5242880.
Your code should resemble the following code example.

<wsHttpBinding>
<binding name="WSHttpBinding_OrdersWebService" closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="5242880" maxReceivedMessageSize="5242880"
messageEncoding="Text" textEncoding="utf-8"
...
</wsHttpBinding>

4. Review the task list:


a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
5. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Lab 9, Ex1 - Fetch a single contact task in the task list. This task is located in the
RetrieveContacts method:
In the task list, double-click the TODO: Lab9, Ex1 - Fetch a single contact task.
6. Immediately after the TODO: Lab9, Ex1 - Fetch a single contact comment, write code to perform
the following tasks:
a. Create a new Contact object named contact by calling the GetContactDetails method of the
service object. Pass the rangeFrom variable as a parameter.
30 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

Note: The service variable is an OrdersWebServiceClient object. The OdersWebServiceClient type was
generated when you added the service reference to the OrdersWebService service. This type provides the
Web service proxy for connecting to the OrdersWebService service, and it exposes methods that you can
call to invoke the operations in the OrdersWebService service.

b. If the contact object is not null (Nothing in Visual Basic), instantiate the contacts generic List
collection and specify Contact as the type parameter for this list.
c. Add the contact object to the contacts list.
Your code should resemble the following code example.

[Visual Basic]

...

If rangeTo <= rangeFrom Then

Dim contact As Contact = service.GetContactDetails(rangeFrom)

If Not contact Is Nothing Then


contacts = New List(Of Contact)
contacts.Add(contact)

End If

Else

...

[Visual C#]

...

if (rangeTo <= rangeFrom)


{

Contact contact = service.GetContactDetails(rangeFrom);

if (contact != null)
{
contacts = new List<Contact>();
contacts.Add(contact);
}

}
...

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch all contacts in range task in the task list:
In the task list, double-click the TODO: Lab 9, Ex1 - Fetch all contacts in range task.
8. Immediately after the TODO: Lab 9, Ex1 - Fetch all contacts in range comment, write code to call
the GetAllContactsInRange method of the service object. Specify the rangeFrom and rangeTo
variables as parameters. Assign the returned value to the contacts object.
Your completed code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 31

...
If rangeTo <= rangeFrom Then
Dim contact As Contact = service.GetContactDetails(rangeFrom)
If Not contact Is Nothing Then
contacts = New List(Of Contact)
contacts.Add(contact)
End If

Else
contacts = service.GetAllContactsInRange(rangeFrom, rangeTo)
End If
...

[Visual C#]

...

if (rangeTo <= rangeFrom)


{

Contact contact = service.GetContactDetails(rangeFrom);


if (contact != null)
{
Contacts = new List<Contact>();
contacts.Add(contact);
}

else
{
contacts = service.GetAllContactsInRange(rangeFrom, rangeTo) as List<Contact>;
}

...

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact task in the task
list. This task is located in the getOrdersForContact_Click method:
In the task list, double-click the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact
task.
10. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact comment,
write code to call the GetOrdersForContact method of the service object. Specify the contactID
variable as the parameter. Assign the returned value to the orders object.
Your completed code should resemble the following code example.

[Visual Basic]
...
Try
orders = service.GetOrdersForContact(contactID)
Catch sf As FaultException(Of ServiceFault)
...

[Visual C#]
...
32 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

try
{
orders = service.GetOrdersForContact(contactID);
} ...

11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified product task in the task
list. This task is located in the getOrdersForProduct_Click method:
In the task list, double-click the TODO: Lab 9, Ex1 - Fetch the orders for the specified product
task.
12. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified product comment,
write code to call the GetOrdersForProduct method of the service object. Specify the productID
variable as the parameter. Assign the returned value to the orders object.
Your completed code should resemble the following code example.

[Visual Basic]
...

Try
orders = service.GetOrdersForProduct(productID)

Catch sf As FaultException(Of ServiceFault)

...

[Visual C#]
...

try
{
orders = service.GetOrdersForProduct(productID);
}

...

13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch a single order task in the task list. This task is located
in the RetrieveOrders method:
In the task list, double-click the TODO: Lab 9, Ex1 - Fetch a single order task.
14. Immediately after the comment, write code to create a new SalesOrderHeader object named order.
Assign this the value that is returned by calling the GetOrderDetails method of the service object.
Specify the rangeFrom variable as a parameter. If an order is returned by the GetOrderDetails
method, instantiate the orders generic List object and add the order to this list. Specify
SalesOrderHeader as the type parameter for the orders list.
Your completed code should resemble the following code example.

[Visual Basic]
...
If rangeTo <= rangeFrom Then
Dim order As SalesOrderHeader = service.GetOrderDetails(rangeFrom)
If Not order Is Nothing Then
orders = New List(Of SalesOrderHeader)()
orders.Add(order)
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 33

End If
Else
...

[Visual C#]
...
if (rangeTo <= rangeFrom)
{
SalesOrderHeader order = service.GetOrderDetails(rangeFrom);
if (order != null)
{
orders = new List<SalesOrderHeader>();
orders.Add(order);
}
}
...

15. Locate the next comment in the code behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab 9, Ex1 - Fetch all orders in range task in the task list:
In the task list, double-click the TODO: Lab 9, Ex1 - Fetch all orders in range task.
16. Immediately after the TODO: Lab 9, Ex1 - Fetch all orders in range comment, write code to call the
GetAllOrdersInRange method of the service object. Specify the rangeFrom and rangeTo variables as
parameters. Assign the returned value to the orders object.
Your completed code should resemble the following code example.

[Visual Basic]
...
If rangeTo <= rangeFrom Then
Dim order As SalesOrderHeader = service.GetOrderDetails(rangeFrom)
If Not order Is Nothing Then
orders = new List(Of SalesOrderHeader)()
orders.Add(order)
Else
orders = service.GetAllOrdersInRange(rangeFrom, rangeTo)
End If
...

[Visual C#]
...
if (rangeTo <= rangeFrom)
{
SalesOrderHeader order = service.GetOrderDetails(rangeFrom);
if (order != null)
{
orders = new List<SalesOrderHeader>();
orders.Add(order);
}
}
else
{
orders = service.GetAllOrdersInRange(rangeFrom, rangeTo) as
List<SalesOrderHeader>;
}
...
34 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

17. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 11: Test the OrderManagement application


1. Check that the OrderManagement application is set as the startup project:
In Solution Explorer, if the OrderManagement project is not highlighted in bold text, right-click
the OrderManagement application, and then click Set as StartUp Project.
2. Start the application without debugging:
On the Debug menu, click Start Without Debugging.
3. In the Order Management window, on the Contacts tab, click Get. One contact is displayed in the
contacts grid.
4. Adjust the To slider to a value that is less than 500, and then click Get. Check that the correct number
of rows is returned.
5. Adjust the To slider to a value that is greater than 500, and then click Get. An exception will be
thrown containing the message "Too many contacts".
6. In the Service Fault occurred dialog box, click OK.
7. In the Order Management window, click the General Orders tab.
8. On the General Orders tab, adjust the From slider to a value that is greater than 50000, and then
click Get. A single order is displayed in the orders grid. Expand the order to view the order details.
9. Adjust the To slider to retrieve a range of between 20 and 50 orders, and then click Get. Verify that
the correct number of orders is displayed.

You can use the arrow keys to adjust the slider in small increments.

10. Adjust the To slider to retrieve a range of over 500 orders. Verify that a message box appears with the
message Too many orders.
11. In the Service Fault occurred dialog box, click OK.
12. On the Orders By Contact tab, in the Contact ID box, type 10 and then click Get. The four orders
placed by customer 10 should appear. Expand each order to view the details.
13. On the Orders By Product tab, in the Product ID box, type 710 and then click Get. The 44 orders for
this product should appear. Expand each order to view the details.
14. Close the Order Management window and return to Visual Studio.
15. Open the Orders Service event log to view the details of exceptions generated by the Web service:
a. On the View menu, click Server Explorer.
b. In Server Explorer, expand Servers, and then expand 10265A-GEN-DEV.
c. Expand Event Logs, expand Application, and then expand Orders Service.
d. Right-click an exception, and then click Properties.
e. In the Properties window, examine the Message property.
16. Close the solution:
On the File menu, click Close Solution.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 35

Exercise 2: Protecting Data Access Operations


Task 1: Open the starter project
1. In the E:\Labfiles\Lab09\VB\Ex2\Starter folder (if you are using Visual Basic), or
E:\Labfiles\Lab09\CS\Ex2\Starter folder (if you are using Visual C#), run ExSetup.bat as an
administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), and then double-click Labfiles.
c. If you are using Visual Basic, in Windows Explorer, double-click Lab09, double-click VB, double-
click Ex2, and then double-click Starter.
d. If you are using Visual C#, in Windows Explorer, double-click Lab09, double-click CS, double-
click Ex2, and then double-click Starter.
e. Right-click ExSetup.bat, and then click Run as administrator.
f. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
g. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex2\Starter\OrdersDAL or
E:\Labfiles\Lab09\CS\Ex2\Starter\OrdersDAL folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab09\VB\Ex2\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab09\CS\Ex2\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.

Task 2: Host the OrdersWebService Web service by using SSL


1. Open IIS Manager as an administrator:
a. Click Start, in the Search programs and files box, type inetmgr, right-click inetmgr, and then
click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
2. Add a new self-signed certificate named OrdersWebService:
a. In the IIS Manager window, in the Connections pane, click 10265A-GEN-DEV.
b. In the 10265A-GEN-DEV Home pane, double-click Server Certificates.
c. In the Server Certificates window, in the Actions pane, click Create Self-Signed Certificate.
d. In the Create Self-Signed Certificate dialog box, type OrdersWebService and then click OK.
3. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate:
a. In the Connections pane, expand Sites, right-click Default Web Site, and then click Edit
Bindings.
b. In the Site Bindings dialog box, click https, and then click Edit.
c. In the Edit Site Binding dialog box, in the SSL certificate list, click OrdersWebService, and then
click OK.
d. In the Site Bindings dialog box, click Close.
4. Set the OrdersWebService Web service to require Secure Sockets Layer (SSL) security:
36 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

a. In the Connections pane, expand Default Web Site, and then click OrdersWebService.
b. In the /OrdersWebService Home pane, double-click SSL Settings.
c. In the SSL Settings pane, select the Require SSL check box.
d. In the Actions pane, click Apply.
5. Close IIS Manager:
On the File menu, click Exit.
6. Set the Project Url property for the OrdersWebService project to use HTTPS:
a. In Visual Studio, in Solution Explorer, right-click OrdersWebService, and then click Properties.
b. In the OrdersWebService window, click Web, in the Project Url box, type
https://localhost/OrdersWebService
c. On the File menu, click Close.

Task 3: Apply security demands


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2 - Allow
ContactAdmins to call GetAllContactsInRange task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Allow ContactAdmins to call
GetAllContactsInRange task.
3. Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetAllContactsInRange
comment, add the PrincipalPermission attribute to specify that users must be members of the
ContactAdmin role.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="ContactAdmin")>
Public Function GetAllContactsInRange(ByVal lowerBound As Integer, ByVal upperBound As
Integer) As System.Collections.Generic.IEnmerable(Of OrdersDAL.Contact) Implements
IOrdersService.GetAllContactsInRange

...
End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role="ContactAdmin")]
public IEnumerable<Contact> GetAllContactsInRange(int lowerBound, int upperBound)
{
...
}

4. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow ContactAdmins to call GetContactDetails task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Allow ContactAdmins to call
GetContactDetails task.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 37

5. Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetContactDetails
comment, add the PrincipalPermission attribute to specify that users must be members of the
ContactAdmin role.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="ContactAdmin")>

Public Function GetContactDetails(ByVal contactID As Integer) As OrdersDAL.Contact


Implements IOrdersService.GetContactDetails

...

End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role="ContactAdmin")]

public Contact GetContactDetails(int contactID)


{
...
}

6. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetAllOrdersInRange task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Allow OrderAdmins to call
GetAllOrdersInRange task.
7. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetAllOrdersInRange
comment, add the PrincipalPermission attribute to specify that users must be members of the
OrderAdmin role.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>

Public Function GetAllOrdersInRange(ByVal lowerBound As Integer, ByVal upperBound As


Integer) As System.Collections.Generic.IEnumerable(Of OrdersDAL.SalesOrderHeader)
Implements IOrdersService.GetAllOrdersInRange

...

End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]

public IEnumerable<SalesOrderHeader> GetAllOrdersInRange(int lowerBound, int upperBound)


{
...
38 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

8. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetOrderDetails task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Allow OrderAdmins to call
GetOrderDetails task.
9. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrderDetails comment,
add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin
role.
Your code should resemble the following code example.

[Visual Basic]
<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>
Public Function GetOrderDetails(ByVal orderID As Integer) As OrdersDAL.SalesOrderHeader
Implements IOrdersService.GetOrderDetails
...

End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]


public SalesOrderHeader GetOrderDetails(int orderID)
{
...
}

10. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetOrdersForContact task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Allow OrderAdmins to call
GetOrdersForContact task.
11. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForContact
comment, add the PrincipalPermission attribute to specify that users must be members of the
OrderAdmin role.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>
Public Function GetOrdersForContact(ByVal contactID As Integer) As
System.Collections.Generic.IEnumerable(Of OrdersDAL.SalesOrderHeader) Implements
IOrdersService.GetOrdersForContact

...
End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]


public IEnumerable<SalesOrderHeader> GetOrdersForContact(int contactID)
{
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 39

...
}

12. Locate the next comment in the OrderServiceImpl code file by double-clicking the TODO: Lab9, Ex2
- Allow OrderAdmins to call GetOrdersForProduct task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Allow OrderAdmins to call
GetOrdersForProduct task.
13. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForProduct
comment, add the PrincipalPermission attribute to specify that users must be members of the
OrderAdmin role.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>
Public Function GetOrdersForProduct(ByVal productID As Integer) As
System.Collections.Generic.IEnumerable(Of OrdersDAL.SalesOrderHeader) Implements
IOrdersService.GetOrdersForProduct

...
End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]


public IEnumerable<SalesOrderHeader> GetOrdersForProduct(int productID)
{
...
}

14. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 4: Modify the OrdersWebService Web service to use transport security with
message-level credentials
1. In the OrdersWebService project, open the web.config file:
In Solution Explorer, expand OrdersWebService, and then double-click web.config.
2. In the web.config file, locate the TODO: Lab 9, Ex2 - Use TransportWithMessageCredential
security mode comment. This comment is located in the bindings section.
3. Change the security mode from None to TransportWithMessageCredential.
Your code should resemble the following code example.

<security mode="TransportWithMessageCredential" />

4. In the web.config file, locate the TODO: Lab 9, Ex2 - Enable https, disable http comment. This
comment is located in the serviceBehaviors section.
5. Change the serviceMetadata property to enable HTTPS and disable HTTP.
Your code should resemble the following code example.
40 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />

6. Build the solution and correct any errors:


In the Build menu, click Build Solution. Correct any errors.

Task 5: Modify the OrderManagement application


1. Update the OrdersWebService service reference to use the new binding and security:
a. In Solution Explorer, expand the OrderManagement project, expand Service References, right-
click OWService, and then click Configure Service Reference.
b. In the OrderManagement.OWService - Service Reference Settings dialog box, in the Address
box, type https://localhost/OrdersWebService/OrdersWebService.svc and then click OK.
c. In the Security Alert dialog box, click Yes.
2. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
3. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Lab9, Ex2 - Add credentials in getContacts_Click task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Add credentials in getContacts_Click task.
4. Immediately after the TODO: Lab9, Ex2 - Add credentials in getContacts_Click comment, add
code to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
Your code should resemble the following code example.

[Visual Basic]

service.ClientCredentials.Windows.ClientCredential.UserName = Me.userName.Text
service.ClientCredentials.Windows.ClientCredential.Password = Me.password.Password

[Visual C#]

service.ClientCredentials.Windows.ClientCredential.UserName = this.userName.Text;
service.ClientCredentials.Windows.ClientCredential.Password = this.password.Password;

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrders_Click task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Add credentials in getOrders_Click task.
6. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrders_Click comment, add code
to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
Your code should resemble the following code example.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 41

[Visual Basic]

service.ClientCredentials.Windows.ClientCredential.UserName = Me.userName.Text
service.ClientCredentials.Windows.ClientCredential.Password = Me.password.Password

[Visual C#]

service.ClientCredentials.Windows.ClientCredential.UserName = this.userName.Text;
service.ClientCredentials.Windows.ClientCredential.Password = this.password.Password;

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click task in the
task list:
In the task list, double-click the TODO: Lab9, Ex2 - Add credentials in
getOrdersForContact_Click task.
8. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click comment,
add code to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
Your code should resemble the following code example.

[Visual Basic]

service.ClientCredentials.Windows.ClientCredential.UserName = Me.userName.Text
service.ClientCredentials.Windows.ClientCredential.Password = Me.password.Password

[Visual C#]

service.ClientCredentials.Windows.ClientCredential.UserName = this.userName.Text;
service.ClientCredentials.Windows.ClientCredential.Password = this.password.Password;

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click task in the
task list:
In the task list, double-click the TODO: Lab9, Ex2 - Add credentials in
getOrdersForProduct_Click task.
10. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click
comment, add code to perform the following tasks:
a. Assign the value of the userName.Text property to the
service.ClientCredentials.Windows.ClientCredential.UserName property.
b. Assign the value of the password.Password property to the
service.ClientCredentials.Windows.ClientCredential.Password property.
Your code should resemble the following code example.

[Visual Basic]

service.ClientCredentials.Windows.ClientCredential.UserName = Me.userName.Text
42 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

service.ClientCredentials.Windows.ClientCredential.Password = Me.password.Password

[Visual C#]

service.ClientCredentials.Windows.ClientCredential.UserName = this.userName.Text;
service.ClientCredentials.Windows.ClientCredential.Password = this.password.Password;

11. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 6: Create unit tests


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the OrderServiceImplTest code file by double-clicking the TODO: Lab9, Ex2 - Create a unit
test for the GetAllContactsInRange method task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Create a unit test for the
GetAllContactsInRange method task.
3. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllContactsInRange
method comment, add the following unit test code. This code calls the GetAllContactsInRange
method to fetch all contacts in the range 100 to 500, and verifies that the method returns the correct
number of rows. This method specifies the user name Fred and the password Pa$$w0rd. This user is
a member of the ContactAdmin role.
Your code should resemble the following code example.

[Visual Basic]
<TestMethod()>
Public Sub GetAllContactsInRangeTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"
Dim lowerBound As Integer = 100
Dim upperBound As Integer = 500
Dim expectedCount As Integer = 401
Dim expectedFirstName As String = "Jackie"
Dim actual As IEnumerable(Of Contact) =
service.GetAllContactsInRange(lowerBound, upperBound)
Assert.AreEqual(expectedCount, actual.Count())
Assert.AreEqual(expectedFirstName, actual.First().FirstName)

End Sub

[Visual C#]

[TestMethod()]
public void GetAllContactsInRangeTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred";
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 43

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int lowerBound = 100;


int upperBound = 500;
int expectedCount = 401;
string expectedFirstName = "Jackie";
IEnumerable<Contact> actual =
service.GetAllContactsInRange(lowerBound, upperBound);
Assert.AreEqual(expectedCount, actual.Count());
Assert.AreEqual(expectedFirstName, actual.First().FirstName);
}

4. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetAllOrdersInRange method task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Create a unit test for the
GetAllOrdersInRange method task.
5. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllOrdersInRange method
comment, add the following unit test code. This code calls the GetAllOrdersInRange method to
retrieve all orders in the range 43650 to 43700, and verifies that the method returns the correct
number of rows. This method specifies the user name Fred and the password Pa$$w0rd.

[Visual Basic]

<TestMethod()>
Public Sub GetAllOrdersInRangeTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim lowerBound As Integer = 43650


Dim upperBound As Integer = 43700
Dim expectedCount As Integer = 42
Dim expectedSalesOrderDetailCount As Integer = 12
Dim actual As IEnumerable(Of SalesOrderHeader) =
service.GetAllOrdersInRange(lowerBound, upperBound)
Assert.AreEqual(expectedCount, actual.Count())
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count())

End Sub

[Visual C#]

[TestMethod()]
public void GetAllOrdersInRangeTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int lowerBound = 43650;


44 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

int upperBound = 43700;


int expectedCount = 42;
int expectedSalesOrderDetailCount = 12;
IEnumerable<SalesOrderHeader> actual =
service.GetAllOrdersInRange(lowerBound, upperBound);
Assert.AreEqual(expectedCount, actual.Count());
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count());
}

6. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetContactDetails method task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Create a unit test for the GetContactDetails
method task.
7. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetContactDetails method
comment, add the following unit test code. This code calls the GetContactDetails method to retrieve
the details of contact 13, and verifies that the method returns the correct data. This method specifies
the user name Fred and the password Pa$$w0rd.

[Visual Basic]

<TestMethod()>
Public Sub GetContactDetailsTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim contactID As Integer = 13


Dim expectedFirstName As String = "Robert"
Dim expectedLastName As String = "Ahlering"
Dim actual As Contact = service.GetContactDetails(contactID)
Assert.AreEqual(expectedFirstName, actual.FirstName)
Assert.AreEqual(expectedLastName, actual.LastName)

End Sub

[Visual C#]

[TestMethod()]
public void GetContactDetailsTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int contactID = 13;


string expectedFirstName = "Robert";
string expectedLastName = "Ahlering";
Contact actual = service.GetContactDetails(contactID);
Assert.AreEqual(expectedFirstName, actual.FirstName);
Assert.AreEqual(expectedLastName, actual.LastName);
}
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 45

8. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetOrderDetails method task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Create a unit test for the GetOrderDetails method
task.
9. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrderDetails method
comment, add the following unit test code. This code calls the GetOrderDetails method to retrieve
the details of order 71780, and verifies that the method returns the correct data. This method
specifies the user name Bert and the password Pa$$w0rd. This user is a member of the OrderAdmin
role.

[Visual Basic]

<TestMethod()>
Public Sub GetOrderDetailsTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim orderID As Integer = 71780


Dim expectedSalesOrderID As Integer = 71780
Dim expectedAccountNumber As String = "10-4020-000340"
Dim expectedSalesOrderDetailsCount As Integer = 29
Dim actual As SalesOrderHeader = service.GetOrderDetails(orderID)
Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID)
Assert.AreEqual(expectedAccountNumber, actual.AccountNumber)
Assert.AreEqual(expectedSalesOrderDetailsCount,
actual.SalesOrderDetails.Count)

End Sub

[Visual C#]

[TestMethod()]
public void GetOrderDetailsTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int orderID = 71780;


int expectedSalesOrderID = 71780;
string expectedAccountNumber = "10-4020-000340";
int expectedSalesOrderDetailsCount = 29;
SalesOrderHeader actual = service.GetOrderDetails(orderID);
Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID);
Assert.AreEqual(expectedAccountNumber, actual.AccountNumber);
Assert.AreEqual(expectedSalesOrderDetailsCount,
actual.SalesOrderDetails.Count);
}

10. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetOrdersForContact method task in the task list:
46 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

In the task list, double-click the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForContact
method task.
11. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForContact method
comment, add the following unit test code. This code calls the GetOrdersForContact method to
retrieve the orders for contact 100, and verifies that the method returns the correct data. This method
specifies the user name Bert and the password Pa$$w0rd.

[Visual Basic]

<TestMethod()>

Public Sub GetOrdersForContactTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim contactID As Integer = 100


Dim expectedOrdersCount As Integer = 4
Dim expectedSalesOrderDetailCount As Integer = 3

Dim actual As IEnumerable(Of SalesOrderHeader) =


service.GetOrdersForContact(contactID)
Assert.AreEqual(expectedOrdersCount, actual.Count())
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count())

End Sub

[Visual C#]

[TestMethod()]
public void GetOrdersForContactTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int contactID = 100;


int expectedOrdersCount = 4;
int expectedSalesOrderDetailCount = 3;
IEnumerable<SalesOrderHeader> actual =
service.GetOrdersForContact(contactID);
Assert.AreEqual(expectedOrdersCount, actual.Count());
Assert.AreEqual(expectedSalesOrderDetailCount,
actual.First().SalesOrderDetails.Count());
}

12. Locate the final comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,
Ex2 - Create a unit test for the GetOrdersForProduct method task in the task list:
In the task list, double-click the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForProduct
method task.
Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework 47

13. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForProduct method
comment, add the following unit test code. This code calls the GetOrdersForProduct method to
retrieve the orders that contain product 709, and verifies that the method returns the correct data.
This method specifies the user name Bert and the password Pa$$w0rd.

[Visual Basic]

<TestMethod()>
Public Sub GetOrdersForProductTest()

service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"

Dim productID As Integer = 709


Dim expectedOrderCount As Integer = 188
Dim expectedOrderDetailsCount As Integer = 12
Dim actual As IEnumerable(Of SalesOrderHeader) =
service.GetOrdersForProduct(productID)
Assert.AreEqual(expectedOrderCount, actual.Count())
Assert.AreEqual(expectedOrderDetailsCount,
actual.First().SalesOrderDetails.Count)

End Sub
[Visual C#]

[TestMethod()]
public void GetOrdersForProductTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";

service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";

int productID = 709;


int expectedOrderCount = 188;
int expectedOrderDetailsCount = 12;
IEnumerable<SalesOrderHeader> actual =
service.GetOrdersForProduct(productID);
Assert.AreEqual(expectedOrderCount, actual.Count());
Assert.AreEqual(expectedOrderDetailsCount,
actual.First().SalesOrderDetails.Count);
}

14. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 7: Build and test the application


1. Run all of the tests in the solution and verify that they are successful:
On the Test menu, point to Run, and then click All Tests in Solution.
2. Start the OrderManagement application without debugging:
On the Debug menu, click Start Without Debugging.
3. In the Order Management window, on the Contacts tab, click Get to retrieve the details of contact 1
from the Web service. No credentials have been supplied; therefore, an exception is thrown. In the
message box displaying the message "Access is denied", click OK.
48 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework

4. Test the application by using the two sets of credentials in the following table. You should only be
able to use the Contacts tab when you are authenticated as Fred, and you should only be able to use
the General Orders, Orders By Contact, and Orders By Product tabs when you are authenticated
as Bert. You should not be able to access any data if you omit or specify incorrect credentials.

Username Password Role

Fred Pa$$w0rd ContactAdmin

Bert Pa$$w0rd OrderAdmin

5. Close Visual Studio:


On the File menu, click Exit.
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 1

Module 10
Lab Answer Key: Handling Updates in an N-Tier Solution by
Using the Entity Framework
Contents:
Exercise 1: Handling Updates in the Data Access Tier 2
Exercise 2: Detecting and Handling Order Conflicts 18
2 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

Lab 10: Handling Updates in an N-Tier Solution


by Using the Entity Framework
Exercise 1: Handling Updates in the Data Access Tier
Task 1: Prepare the environment for the lab
1. Log on to the 10265A-GEN-DEV-10 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator to install Internet Information Services
(IIS)and create the user accounts for the application:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), and then double-click Labfiles.
c. Right-click EnvSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running.
3. In the E:\Labfiles folder, run AWReset.bat to create the AdventureWorks database:
a. Using Windows Explorer, move to the E:\Labfiles folder.
b. Double-click AWReset.bat.
c. Wait for the batch file to finish running.

Task 2: Create the virtual directory for the Web service


In the E:\Labfiles\Lab10\CS\Ex1\Starter folder (if you are using Microsoft Visual C#) or the
E:\Labfiles\Lab10\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic), run ExSetup.bat as
an administrator. This script adds the required virtual directories to IIS:
a. If you are using Visual Basic, in Windows Explorer, double-click Lab10, double-click VB, double-
click Ex1, and then double-click Starter.
b. If you are using Visual C#, in Windows Explorer, double-click Lab10, double-click CS, double-
click Ex1, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.

Task 3: Enable SSL for the OrdersWebService Web service


1. Open IIS Manager as an administrator:
a. Click Start, and in the Search programs and files box, type inetmgr
b. In the list of programs, right-click inetmgr, and then click Run as administrator.
c. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.

2. Add a new self-signed certificate named OrdersWebService:

a. In the Internet Information Services (IIS) Manager window, in the Connections pane, click
10265A-GEN-DEV.
b. In the 10265A-GEN-DEV Home pane, double-click Server Certificates.
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 3

c. In the Actions pane, click Create Self-Signed Certificate.


d. In the Create Self-Signed Certificate dialog box, type OrdersWebService and then click OK.

3. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate:
a. In the Connections pane, expand Sites, right-click Default Web Site, and then click Edit
Bindings.
b. In the Site Bindings dialog box, click Add.
c. In the Add Site Binding dialog box, in the Type list, click https.
d. In the SSL certificate list, click OrdersWebService, and then click OK.
e. In the Site Bindings dialog box, click Close.
4. Configure the OrdersWebService application to require Secure Sockets Layer (SSL) security:
a. In the Connections pane, expand Default Web Site, and then click OrdersWebService.
b. In the /OrdersWebService Home pane, double-click SSL Settings.
c. In the SSL Settings pane, select the Require SSL check box.
d. In the Actions pane, click Apply.

5. Close IIS Manager:


On the File menu, click Exit.

Task 4: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010 as an administrator:
a. Click Start, point to All Programs, click Microsoft Visual Studio 2010, right-click Microsoft
Visual Studio 2010, and then click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab10\VB\Ex1\Starter\OrdersDAL or
E:\Labfiles\Lab10\CS\Ex1\Starter\OrdersDAL folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab10\VB\Ex1\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab10\CS\Ex1\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.

Task 5: Modify the entity model and rebuild the self-tracking entities
1. Open the AdventureWorks Entity Data Model (EDM) in the ADO.NET Entity Data Model Designer
(Entity Designer):
In Solution Explorer, in the OrdersDAL project, right-click AdventureWorksModel.edmx, and then
click Open.

2. In the AdventureWorks EDM model, set the StoreGeneratedPattern property of the SubTotal field
of the SalesOrderHeader entity to None:

a. In the Entity Designer pane, in the SalesOrderHeader entity, right-click SubTotal, and then
click Properties.
b. In the Properties pane, click StoreGeneratedPattern, and in the drop-down list, click None.

3. Save the AdventureWorks EDM model:


4 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

a. On the File menu, click Save AdventureWorksModel.edmx.


b. If a Security Warning dialog box appears, click OK.

4. Delete the existing Text Template Transformation Toolkit (T4) template files:
a. In Solution Explorer, in the OrdersDAL project, right-click AdventureWorksModel.Context.tt,
and then click Delete.
b. In the Microsoft Visual Studio dialog box, click OK.
c. In Solution Explorer, in the OrdersClientLibrary project, right-click AdventureWorksModel.tt,
and then click Delete.
d. In the Microsoft Visual Studio dialog box, click OK.

5. Re-create the self-tracking entities (STEs) by using the T4 templates:

a. In the Entity Designer pane, right-click the designer surface, and then click Add Code
Generation Item.
b. In the Add New Item - OrdersDAL dialog box, click ADO.NET Self-Tracking Entity Generator,
in the Name box, type AdventureWorksModel.tt and then click Add.
c. If a Security Warning dialog box appears, click OK.
d. On the File menu, click Save AdventureWorksModel.edmx.
e. If a Security Warning dialog box appears, click OK.

6. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project:

a. In Solution Explorer, in the OrdersDAL project, right-click AdventureWorksModel.tt, and then


click Cut.
b. In Solution Explorer, right-click the OrdersClientLibrary project, and then click Paste.
7. Close the AdventureWorks EDM model:
On the File menu, click Close.
8. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

9. Open the AdditionalMethods code file by double-clicking the TODO: Ex1 - Add
CalculateOrderTotal method to recalculate the sub total task in the task list. This task is located in
the partial SalesOrderHeader class:
In the task list, double-click the TODO: Ex1 - Add CalculateOrderTotal method to recalculate
the sub total task.
10. Immediately after the TODO: Ex1 - Add CalculateOrderTotal method to recalculate the sub total
comment, add a void method called CalculateOrderTotal that recalculates the value of the SubTotal
property of the SalesOrderHeader object. For each SalesOrderDetail object, the total is calculated
according the following formula:
LineTotal = UnitPrice * (1 - UnitPriceDiscount) * Quantity
Your code should resemble the following code example.

[Visual Basic]

Public Sub CalculateOrderTotal()

Dim subTotal As Decimal = 0

For Each detail As SalesOrderDetail In Me.SalesOrderDetails


Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 5

Dim lineTotal As Decimal = detail.UnitPrice *


(1 - detail.UnitPriceDiscount) * detail.OrderQty
subTotal += lineTotal

Next detail
End Sub

[Visual C#]

public void CalculateOrderTotal()


{
decimal subTotal = 0M;

foreach (SalesOrderDetail detail in this.SalesOrderDetails)


{
decimal lineTotal = detail.UnitPrice *
(1 - detail.UnitPriceDiscount) * detail.OrderQty;
subTotal += lineTotal;
}
this.SubTotal = subTotal;
}

11. Locate the next comment in the AdditionalMethods file by double-clicking the TODO: Ex1 - Validate
an order object and verify that it contains order details task in the task list. This task is located in
the partial SalesOrderHeader class:
In the task list, double-click the TODO: Ex1 - Validate an order object and verify that it
contains order details task.
12. Immediately after the TODO: Ex1 - Validate an order object and verify that it contains order
details comment, add a method called Validate that returns a Boolean value. This method should
return true if the SalesOrderHeader object contains at least one SalesOrderDetail object; otherwise,
it should return false.
Your code should resemble the following code example.

[Visual Basic]

Public Function validate() As Boolean


' Validation: Ensure that the order contains at least
' one detail line.
If Me.SalesOrderDetails Is Nothing Or
Me.SalesOrderDetails.Count() = 0 Then

' If there are no details, return false


' and do not save the order as it is meaningless.
Return False

End If
Return True
End Function

[Visual C#]

public bool Validate()


{
6 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

// Validation: Ensure that the order contains at least


// one detail line.

if (this.SalesOrderDetails == null ||
this.SalesOrderDetails.Count() == 0)
{
// If there are no details, return false
// and do not save the order as it is meaningless.
return false;
}

return true;
}

13. Save the AdditionalMethods file:

a. If you are using Visual Basic, on the File menu, click Save AdditionalMethods.vb.
b. If you are using Visual C#, on the File menu, click Save AdditionalMethods.cs.

Task 6: Define the PlaceOrder, CancelOrder, and AmendOrder methods in the


OrdersService project
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

2. Open the IOrdersService code file by double-clicking the TODO: Ex1 - PlaceOrder task in the task
list. This task is located in the IOrdersService interface:
In the task list, double-click the TODO: Ex1 - PlaceOrder task.
3. Immediately after the TODO: Ex1 - PlaceOrder comment, define a method called PlaceOrder that
returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method with
the OperationContract attribute and the FaultContract attribute with a type parameter of
ServiceFault.
Your code should resemble the following code example.

[Visual Basic]

<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function PlaceOrder(ByVal newOrder As SalesOrderHeader) As Boolean

[Visual C#]

[FaultContract(typeof(ServiceFault))]
[OperationContract]
bool PlaceOrder(SalesOrderHeader newOrder);

4. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex1 -
AmendOrder task in the task list. This task is located in the IOrdersService interface:
In the task list, double-click the TODO: Ex1 - AmendOrder task.
5. Immediately after the TODO: Ex1 - AmendOrder comment, define a method called AmendOrder
that returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 7

with the OperationContract attribute and the FaultContract attribute with a type parameter of
ServiceFault.
Your code should resemble the following code example.

[Visual Basic]

<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function AmendOrder(ByVal order As SalesOrderHeader) As Boolean

[Visual C#]

[FaultContract(typeof(ServiceFault))]
[OperationContract]
bool AmendOrder(SalesOrderHeader order);

6. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex1 - CancelOrder
task in the task list. This task is located in the IOrdersService interface:
In the task list, double-click the TODO: Ex1 - CancelOrder task.
7. Immediately after the TODO: Ex1 - CancelOrder comment, define a method called CancelOrder
that returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method
with the OperationContract attribute and the FaultContract attribute with a type parameter of
ServiceFault.
Your code should resemble the following code example.

[Visual Basic]

<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function CancelOrder(ByVal order As SalesOrderHeader) As Boolean

[Visual C#]

[FaultContract(typeof(ServiceFault))]
[OperationContract]
bool CancelOrder(SalesOrderHeader Order);

8. Save the IOrdersService file:

a. If you are using Visual Basic, on the File menu, click Save IOrdersService.vb.
b. If you are using Visual C#, on the File menu, click Save IOrdersService.cs.

Task 7: Implement the PlaceOrder, CancelOrder, and AmendOrder methods in the


OrdersService project
1. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
8 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

2. Open the OrdersServiceImpl file by double-clicking the TODO: Ex1 -


updateOrderEntityCollectionAndSaveChangesToDatabase task in the task list. This task is located
in the OrdersServiceImpl class:
In the task list, double-click the TODO: Ex1 -
updateOrderEntityCollectionAndSaveChangesToDatabase task.
3. Immediately after the TODO: Ex1 - updateOrderEntityCollectionAndSaveChangesToDatabase
comment, define a method called updateOrderEntityCollectionAndSaveChangesToDatabase that
returns a Boolean value and takes a string object called operationName as the first parameter and a
SalesOrderHeader object called order as the second parameter. In the method, write code to
perform the following tasks:
a. Create a new AdventureWorksEntities context object.
b. Call the ApplyChanges method on the SalesOrderHeaders entity set object in this context,
passing the order object as a parameter.
c. Call the SaveChanges method of the context object. Return true if the number of changes that
are saved is greater than zero; otherwise, return false.
d. If any exceptions are thrown, record the details by calling the logException method, and then
throw a new FaultException exception. Specify the value of the operationName parameter and
the order ID for the order in the log message and the fault.
Your code should resemble the following code example.

[Visual Basic]

Private Function
updateOrderEntityCollectionAndSaveChangesToDatabase(ByVal operationName As String, ByVal
order As SalesOrderHeader) As Boolean

Using context As New AdventureWorksEntities()

Try
' Add or update the SalesOrderHeader object in the
' SalesOrderHeaders collection of the context.

context.SalesOrderHeaders.ApplyChanges(order)

' Save the changes recorded in the context.


' The value returned indicates the number of
' changes actually saved.

Dim numChanges As Integer = context.SaveChanges()

' If SaveChanges saved no changes then return False,


' otherwise return True
Return (numChanges > 0)

Catch ex As Exception

' Log the details of the exception

Dim eventMessage As String = String.Format(


"Failure in {0} for order: {1}",
operationName, order.SalesOrderID)
logException(ex, eventMessage)

Throw New FaultException(String.Format("Failure in {0}",


operationName))
End Try
End Using
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 9

End Function

[Visual C#]

private bool
updateOrderEntityCollectionAndSaveChangesToDatabase(string operationName,
SalesOrderHeader order)
{

using (AdventureWorksEntities context


= new AdventureWorksEntities())

{
try

{
// Add or update the SalesOrderHeader object in the
// SalesOrderHeaders collection of the context.

context.SalesOrderHeaders.ApplyChanges(order);

// Save the changes recorded in the context.


// The value returned indicates the number of
// changes actually saved.

int numChanges = context.SaveChanges();

// If SaveChanges saved no changes then return False,


// otherwise return True.

return (numChanges > 0);


}
catch (Exception ex)

{
// Log the details of the exception

string eventMessage = string.Format(


"Failure in {0} for order: {1}",
operationName, order.SalesOrderID);
logException(ex, eventMessage);

throw new FaultException(string.Format("Failure in {0}",


operationName));

4. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
PlaceOrder implementation task in the task list. This task is located in the OrdersServiceImpl class:
In the task list, double-click the TODO: Ex1 - PlaceOrder implementation task.
5. Immediately after the TODO: Ex1 - PlaceOrder implementation comment, define a method called
PlaceOrder that returns a Boolean value and takes a SalesOrderHeader object called newOrder as a
parameter. Mark the method with the PrincipalPermission attribute and specify that this method
10 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

can only be run by members of the OrderAdmin security role. In the method, write code to perform
the following tasks:

a. Call the Validate method on the SalesOrderHeader object. If the validation fails, create a
ServiceFault object with the message "Order has no details" and throw a new FaultException
exception that wraps this ServiceFault object together with the message "Orders must have
details".
b. Call the CalculateOrderTotal method on the SalesOrderHeader object.
c. Call the updateOrderEntityCollectionAndSaveChangesToDatabase method, passing the string
"PlaceOrder" as the first parameter and the newOrder object as the second parameter, and
return the result of the method call.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>
Public Function PlaceOrder(ByVal newOrder As SalesOrderHeader) As Boolean _
Implements IOrdersService.PlaceOrder

' Validate the order before saving.


If Not newOrder.validate() Then

' Throw a ServiceFault if the order is invalid and


' do not save the changes
Dim sf As New ServiceFault()
sf.ExceptionMessage = "Order has no details"
Throw New FaultException(Of ServiceFault)(
sf, "Orders must have details")
End If

' Update the sub total in the order to reflect any changes
' made to order details
newOrder.CalculateOrderTotal()

' Update the entity in the SalesOrderHeader collection and


' save the changes to the database
Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"PlaceOrder", newOrder)

End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]


public bool PlaceOrder(SalesOrderHeader newOrder)
{
// Validate the order before saving.

if (!newOrder.Validate())

{
// Throw a ServiceFault if the order is invalid
// and do not save the changes

ServiceFault sf = new ServiceFault();


sf.ExceptionMessage = "Order has no details";
throw new FaultException<ServiceFault>(sf,
"Orders must have details");
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 11

}
// Update the sub total in the order to reflect
// any changes made to order details
newOrder.CalculateOrderTotal();

// Update the entity in the SalesOrderHeader collection


// and save the changes to the database
return this.updateOrderEntityCollectionAndSaveChangesToDatabase(
"PlaceOrder", newOrder);
}

6. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
AmendOrder implementation task in the task list. This task is located in the OrdersServiceImpl
class:
In the task list, double-click the TODO: Ex1 - AmendOrder implementation task.
7. Immediately after the TODO: Ex1 - AmendOrder implementation comment, define a method
called AmendOrder that returns a Boolean value and takes a SalesOrderHeader object called order
as a parameter. Mark the method with the PrincipalPermission attribute and specify that this
method can only be run by members of the OrderAdmin security role. In the method, write code to
perform the following tasks:

a. Call the Validate method on the SalesOrderHeader object. If the validation fails, create a
ServiceFault object with the message "Order has no details" and throw a new FaultException
exception that wraps this ServiceFault object together with the message "Orders must have
details".
b. Call the CalculateOrderTotal method on the SalesOrderHeader object.
c. Call the updateOrderEntityCollectionAndSaveChangesToDatabase method, passing the string
"AmendOrder" as the first parameter and the order object as the second parameter, and return
the result of the method call.

Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>
Public Function AmendOrder(ByVal order As SalesOrderHeader) As Boolean _
Implements IOrdersService.AmendOrder

' Validate the order before saving.


If Not order.validate() Then
' Throw a ServiceFault if the order is invalid and
' do not save the changes
Dim sf As New ServiceFault()
sf.ExceptionMessage = "Order has no details"
Throw New FaultException(Of ServiceFault)(
sf, "Orders must have details")
End If

' Update the sub total in the order to reflect any changes
' made to order details
order.CalculateOrderTotal()

' Update the entity in the SalesOrderHeader collection and


' save the changes to the database
Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"AmendOrder", order)

End Function
12 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]


public bool AmendOrder(SalesOrderHeader order)
{
// Validate the order before saving.
if (!order.Validate())
{
// Throw a ServiceFault if the order is invalid
// and do not save the changes
ServiceFault sf = new ServiceFault();
sf.ExceptionMessage = "Order has no details";
throw new FaultException<ServiceFault>(sf,
"Orders must have details");
}
// Update the sub total in the order to reflect any
// changes made to order details.
order.CalculateOrderTotal();

// Update the entity in the SalesOrderHeader collection


// and save the changes to the database.
return this.updateOrderEntityCollectionAndSaveChangesToDatabase(
"AmendOrder", order);

8. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -
CancelOrder implementation task in the task list. This task is located in the OrdersServiceImpl
class:
In the task list, double-click the TODO: Ex1 - CancelOrder implementation task.
9. Immediately after the TODO: Ex1 - CancelOrder implementation comment, define a method called
CancelOrder that returns a Boolean value and takes a SalesOrderHeader object called order as a
parameter. Mark the method with the PrincipalPermission attribute and specify that this method
can only be run by members of the OrderAdmin security role. In the method, write code to perform
the following tasks:
a. Call the MarkAsDeleted method on the order object.
b. Call the updateOrderEntityCollectionAndSaveChangesToDatabase method, passing the string
"CancelOrder" as the first parameter and the order object as the second parameter, and return
the result of the method call.
Your code should resemble the following code example.

[Visual Basic]

<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>

Public Function CancelOrder(ByVal order As SalesOrderHeader) As Boolean _

Implements IOrdersService.CancelOrder

' Mark the order as deleted so it will be removed from


' the entity collection and deleted from the database

order.MarkAsDeleted()

' Update the entity in the SalesOrderHeader collection


Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 13

' and save the changes to the database

Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"CancelOrder", order)

End Function

[Visual C#]

[PrincipalPermission(SecurityAction.Demand, Role = "OrderAdmin")]


public bool CancelOrder(SalesOrderHeader order)
{
// Mark the order as deleted so it will be removed from
// the entity collection and deleted from the database
order.MarkAsDeleted();

// Update the entity in the SalesOrderHeader collection


// and save the changes to the database
return this.updateOrderEntityCollectionAndSaveChangesToDatabase(
"CancelOrder", order);
}

10. Save the OrdersServiceImpl file:

a. If you are using Visual Basic, on the File menu, click Save OrdersServiceImpl.vb.
b. If you are using Visual C#, on the File menu, click Save OrdersServiceImpl.cs.

11. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 8: Update the WPF client application


1. In the OrderManagement project, open the OrderManagementWindow.xaml file:
In Solution Explorer, in the OrderManagement project, double-click
OrderManagementWindow.xaml.
2. Click the Orders By Contact tab. The functionality that the controls on this tab provide has been
extended to enable a user to create, edit, and delete orders for a contact, as follows:

a. The New Order button creates a new order. Pressing INSERT in the TreeView control also
creates a new order.
b. Pressing DELETE in the TreeView control deletes an order.
c. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an
order; you cannot delete items from an order.

3. Update the service reference in the OrderManagement project:

a. In the OrderManagement project, expand the Service References folder, right-click OWService,
and then click Update Service Reference.
b. If a Security Alert dialog box appears, click Yes.

4. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
14 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

5. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Ex1 - Save the order to the database task in the task list. This task is located in the
editOrder method, which is called when the user has made changes to an order and wants to save
them:
In the task list, double-click the TODO: Ex1 - Save the order to the database task.
6. Immediately after the TODO: Ex1 - Save the order to the database comment, write code to
perform the following tasks:

a. Call the AmendOrder method of the service object, passing the order object as a parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation object to "Order
saved". This object is a status bar item that appears at the bottom of the window.
c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order
not saved".

Your code should resemble the following code example.

[Visual Basic]

' Save the order to the database

If service.AmendOrder(order) Then
Me.statusOfLastOperation.Content = "Order saved"

Else
Me.statusOfLastOperation.Content = "Order not saved"

End If

[Visual C#]

// Save the order to the database


if (service.AmendOrder(order))
{
this.statusOfLastOperation.Content = "Order saved";
}
else
{
this.statusOfLastOperation.Content = "Order not saved";
}

7. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking


the TODO: Ex1 - Delete the order from the database task in the task list. This task is located in the
deleteOrder method, which is called when the user wants to cancel an order:
In the task list, double-click the TODO: Ex1 - Delete the order from the database task.
8. Immediately after the TODO: Ex1 - Delete the order from the database comment, write code to
perform the following tasks:

a. Call the CancelOrder method of the service object, passing the order object as a parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation object to "Order
deleted".
c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order
not deleted".
Your code should resemble the following code example.
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 15

[Visual Basic]

' Delete the order from the database

If service.CancelOrder(order) Then
Me.statusOfLastOperation.Content = "Order deleted"

Else
Me.statusOfLastOperation.Content = "Order not deleted"

End If

[Visual C#]

// Delete the order from the database

if (service.CancelOrder(order))
{
this.statusOfLastOperation.Content = "Order deleted";
}

else
{
this.statusOfLastOperation.Content = "Order not deleted";
}

9. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking


the TODO: Ex1 - Add the new order to the database task in the task list. This task is located in the
addOrder method, which is called when the user wants to place a new order for a contact:
In the task list, double-click the TODO: Ex1 - Add the new order to the database task.
10. Immediately after the TODO: Ex1 - Add the new order to the database comment, write code to
perform the following tasks:
a. Call the PlaceOrder method of the service object, passing the order object as a parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation object to "New
order saved".
c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order
not saved".
d. Update the user interface (UI) by simulating the user double-clicking the getOrdersForContact
button.

Your code should resemble the following code example.

[Visual Basic]
' Add the new order to the database

If service.PlaceOrder(newOrder) Then
Me.statusOfLastOperation.Content = "New order saved"

Else
Me.statusOfLastOperation.Content = "Order not saved"

End If
' Add the new order to the display by simulating the user
' clicking the getOrdersForContact button

Me.getOrdersForContact.RaiseEvent(
16 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

New RoutedEventArgs(Button.ClickEvent, Me))


[Visual C#]

// Add the new order to the database.


if (service.PlaceOrder(newOrder))
{
this.statusOfLastOperation.Content = "New order saved";
}
else
{
this.statusOfLastOperation.Content = "Order not saved";
}

// Add the new order to the display by simulating the user


// clicking the getOrdersForContact button.

this.getOrdersForContact.RaiseEvent(
new RoutedEventArgs(Button.ClickEvent, this));

11. Save the code file behind the OrderManagementWindow.xaml window:


a. If you are using Visual Basic, on the File menu, click Save OrderManagementWindow.xaml.vb.
b. If you are using Visual C#, on the File menu, click Save OrderManagementWindow.xaml.cs.

Task 9: Add a unit test for the PlaceOrder, CancelOrder, and AmendOrder methods
1. Update the service reference in the OrdersServiceTest project:
a. In the OrdersServiceTest project, expand the Service References folder, right-click OWService,
and then click Update Service Reference.
b. If a Security Alert dialog box appears, click Yes.

2. Review the task list:


a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

3. Open the OrdersServiceImplTest file by double-clicking the TODO: Ex1 - Implement a test for
PlaceOrder, AmendOrder, CancelOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method:
In the task list, double-click the TODO: Ex1 - Implement a test for PlaceOrder, AmendOrder,
CancelOrder task.
4. Immediately after the TODO: Ex1 - Implement a test for PlaceOrder, AmendOrder, CancelOrder
comment, add the following test code. This code performs the following tasks:

a. It connects to the Web service as the user Bert with a password of Pa$$w0rd. This user is a
member of the OrderAdmin role.
b. It places a new order and verifies that the service has added the order correctly. It uses the
CreateOrder helper method to create a new order and populate a SalesOrderHeader object.
c. It modifies the order that was just added and verifies that the service has modified the order
correctly.
d. It deletes the order that was just added and verifies that the service has deleted the order
correctly.

[Visual Basic]

service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"
service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 17

Dim productID As Integer = 709


Dim expectedOrderCount As Integer = 188
Dim expectedOrderDetailsCount As Integer = 2

' Create a new order.


Dim order As SalesOrderHeader = CreateOrder()
order.CalculateOrderTotal()
service.PlaceOrder(order)

Dim actual As IEnumerable(Of SalesOrderHeader) =


service.GetOrdersForProduct(productID)

' Check the Order Header was added.


Assert.AreEqual(expectedOrderCount + 1, actual.Count())

' Check the Order Details were added.


Dim addedOrder As SalesOrderHeader = actual.Last()
Assert.AreEqual(expectedOrderDetailsCount,
addedOrder.SalesOrderDetails.Count)
' Modify the order.
Dim expectedComment As String = "Modified"
addedOrder.Comment = expectedComment
service.AmendOrder(addedOrder)
actual = service.GetOrdersForProduct(productID)
Dim modifiedOrder As SalesOrderHeader = actual.Last()
Assert.AreEqual(expectedComment, modifiedOrder.Comment)

' Delete the order.


service.CancelOrder(modifiedOrder)
actual = service.GetOrdersForProduct(productID)
Assert.AreEqual(expectedOrderCount, actual.Count())

[Visual C#]

service.ClientCredentials.Windows.ClientCredential.UserName = "Bert";
service.ClientCredentials.Windows.ClientCredential.Password
= "Pa$$w0rd";

int productID = 709;


int expectedOrderCount = 188;
int expectedOrderDetailsCount = 2;

// Create a new order.


SalesOrderHeader order = CreateOrder();
order.CalculateOrderTotal();
service.PlaceOrder(order);
IEnumerable<SalesOrderHeader> actual = service.GetOrdersForProduct(productID);

// Check the Order Header was added.


Assert.AreEqual(expectedOrderCount+1, actual.Count());

// Check the Order Details were added.


SalesOrderHeader addedOrder = actual.Last();
Assert.AreEqual(expectedOrderDetailsCount, addedOrder.SalesOrderDetails.Count);

// Modify the order.


string expectedComment = "Modified";
addedOrder.Comment = expectedComment;
service.AmendOrder(addedOrder);
actual = service.GetOrdersForProduct(productID);
18 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

SalesOrderHeader modifiedOrder = actual.Last();


Assert.AreEqual(expectedComment, modifiedOrder.Comment);

// Delete the order.


service.CancelOrder(modifiedOrder);
actual = service.GetOrdersForProduct(productID);
Assert.AreEqual(expectedOrderCount, actual.Count());

5. Save the OrdersServiceImplTest file:

a. If you are using Visual Basic, on the File menu, click Save OrdersServiceImplTest.vb.
b. If you are using Visual C#, on the File menu, click Save OrdersServiceImplTest.cs.

Task 10: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution. Correct any errors.
2. Run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
3. Verify that all of the tests succeed.
4. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
5. In the Order Management window perform the following tasks:
a. Click the Orders By Contact tab.
b. In the Username box, type Bert
c. In the Password box, type Pa$$w0rd
d. In the Contact ID box, type 200 and then click Get. Verify that you can add a new order, modify
an existing order, and delete an order. Note that you can add line items to an order, but you
cannot remove them after an order has been saved. However, you can amend existing line items.
Note the following features of the application and the data in the database:
i. Make sure that you use a valid product ID, for example, 905 or 906.
ii. Make sure that you use a discount of less than 1.0, for example, 0.05.
iii. The New Order button creates a new order. Pressing INSERT in the TreeView control also
creates a new order.
iv. Pressing DELETE in the TreeView control deletes an order.
v. Pressing ENTER in the TreeView control edits an order. You can only change or add items to
an order; you cannot delete items from an order.

6. Close the application.


7. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Detecting and Handling Order Conflicts


Task 1: Open the starter project for this exercise
1. In the E:\Labfiles\Lab10\CS\Ex2\Starter folder (if you are using Visual C#) or the
E:\Labfiles\Lab10\VB\Ex2\Starter folder (if you are using Visual Basic), run ExSetup.bat as an
administrator:
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 19

a. If you are using Visual Basic, in Windows Explorer, double-click Lab10, double-click VB, double-
click Ex2, and then double-click Starter.
b. If you are using Visual C#, in Windows Explorer, double-click Lab10, double-click CS, double-
click Ex2, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.

2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab10\VB\Ex2\Starter\OrdersDAL or


E:\Labfiles\Lab10\CS\Ex2\Starter\OrdersDAL folder:

a. On the File menu, point to Open, and then click Project/Solution.


b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab10\VB\Ex2\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab10\CS\Ex2\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.

Task 2: Modify the entity model and rebuild the self-tracking entities
1. Open the AdventureWorks EDM in the Entity Designer:
In Solution Explorer, expand the OrdersDAL project, right-click AdventureWorksModel.edmx,
and then click Open.
2. In the AdventureWorks EDM model, set the Concurrency Mode property of the RevisionNumber
field of the SalesOrderHeader entity to Fixed:

a. In the Entity Designer pane, in the SalesOrderHeader entity, right-click RevisionNumber, and
then click Properties.
b. In the Properties pane, click Concurrency Mode, and in the drop-down list, click Fixed.
3. Save the AdventureWorks EDM model:

a. On the File menu, click Save AdventureWorksModel.edmx.


b. If a Security Warning dialog box appears, click OK.

4. Delete the existing T4 template files:

a. In Solution Explorer, in the OrdersDAL project, right-click AdventureWorksModel.Context.tt,


and then click Delete.
b. In the Microsoft Visual Studio dialog box, click OK.
c. In Solution Explorer, expand the OrdersClientLibrary project, right-click
AdventureWorksModel.tt, and then click Delete.
d. In the Microsoft Visual Studio dialog box, click OK.

5. Re-create the STEs by using the T4 templates:


a. In the Entity Designer pane, right-click the designer surface, and then click Add Code
Generation Item.
b. In the Add New Item - OrdersDAL dialog box, click ADO.NET Self-Tracking Entity Generator,
in the Name box, type AdventureWorksModel.tt and then click Add.
c. If a Security Warning dialog box appears, click OK.
d. On the File menu, click Save AdventureWorksModel.edmx.
20 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

e. If a Security Warning dialog box appears, click OK.

6. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project:

a. In Solution Explorer, in the OrdersDAL project, right-click AdventureWorksModel.tt, and then


click Cut.
b. In Solution Explorer, right-click the OrdersClientLibrary project, and then click Paste.

7. Close the AdventureWorks EDM model:


On the File menu, click Close.

Task 3: Add the types that are required to serialize faults to the client
1. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

2. Open the IOrdersService file by double-clicking the TODO: Ex2 - Create


OptimisticConcurrencyExceptionReason enumeration task in the task list. This task is located in
the IOrdersService file:
In the task list, double-click the TODO: Ex2 - Create OptimisticConcurrencyExceptionReason
enumeration task.
3. Immediately after the TODO: Ex2 - Create OptimisticConcurrencyExceptionReason enumeration
comment, add an enumeration called OptimisticConcurrencyExceptionReason with three values
called None, ItemAlreadyDeleted, and ItemAlreadyAddedOrUpdated. Mark the enumeration with
the DataContract attribute, and mark each value with the EnumMember attribute.
Your code should resemble the following code example.

[Visual Basic]
<DataContract()>
Public Enum OptimisticConcurrencyExceptionReason
<EnumMember()>
None = 0
<EnumMember()>
ItemAlreadyDeleted = 1
<EnumMember()>
ItemAlreadyAddedOrUpdated = 2
End Enum

[Visual C#]
[DataContract]
public enum OptimisticConcurrencyExceptionReason
{
[EnumMember]
None = 0,

[EnumMember]
ItemAlreadyDeleted = 1,

[EnumMember]
ItemAlreadyAddedOrUpdated = 2
}

4. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Create
ConcurrencyFault class task in the task list:
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 21

In the task list, double-click the TODO: Ex2 - Create ConcurrencyFault class task.
5. Immediately after the TODO: Ex2 - Create ConcurrencyFault class comment, add a class called
ConcurrencyFault with two public fields. The first field is called Reason and is of type
OptimisticConcurrencyExceptionReason. The second field is called ConflictingValues and is of
type Dictionary; both the key and the value are strings. Mark the class with the DataContract
attribute, and mark each field with the DataMember attribute. Additionally, mark the class with the
ServiceKnownType attribute and specify the type of the OptimisticConcurrencyException
enumeration; this enables Windows Communication Foundation (WCF) to serialize and deserialize the
Reason field correctly.
Your code should resemble the following code example.

[Visual Basic]
<DataContract()>
<ServiceKnownType(GetType(OptimisticConcurrencyExceptionReason))>
Public Class ConcurrencyFault

<DataMember()>
Public Reason As OptimisticConcurrencyExceptionReason

<DataMember()>
Public ConflictingValues As Dictionary(Of String, String)

End Class

[Visual C#]

[DataContract]

[ServiceKnownType(typeof(OptimisticConcurrencyExceptionReason))]

public class ConcurrencyFault


{
[DataMember]
public OptimisticConcurrencyExceptionReason Reason;

[DataMember]
public Dictionary<string, string> ConflictingValues;
}

6. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Define
ConflictResolutionStrategy enumeration task in the task list. This task is located in the
OrdersService namespace:
In the task list, double-click the TODO: Ex2 - Define ConflictResolutionStrategy enumeration
task.
7. Immediately after the TODO: Ex2 - Define ConflictResolutionStrategy enumeration comment,
add an enumeration called ConflictResolutionStrategy with three values called None, ClientWins,
and StoreWins. Mark the enumeration with the DataContract attribute, and mark each value with
the EnumMember attribute.
Your code should resemble the following code example.

[Visual Basic]

<DataContract()>
Public Enum ConflictResolutionStrategy
22 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

<EnumMember()>
None = 0

<EnumMember()>
ClientWins = 1

<EnumMember()>
StoreWins = 2

End Enum

[Visual C#]

[DataContract]
public enum ConflictResolutionStrategy
{
[EnumMember]
None = 0,
[EnumMember]
ClientWins = 1,

[EnumMember]
StoreWins = 2
}

8. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConflictResolutionStrategy as a known serializable type for the service task in the task list. This
task is located just before the IOrdersService interface:
In the task list, double-click the TODO: Ex2 - Add ConflictResolutionStrategy as a known
serializable type for the service task.
9. Delete the TODO: Ex2 - Add ConflictResolutionStrategy as a known serializable type for the
service comment, and replace it with the ServiceKnownType attribute with a parameter of type
ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
<ServiceContract(Name:="OrdersWebService", Namespace:="http:microsoft.com")>
<ServiceKnownType(GetType(ConflictResolutionStrategy))>
Public Interface IordersService
...

[Visual C#]

[ServiceContract (Name="OrdersWebService",
Namespace="http://microsoft.com")]
[ServiceKnownType(typeof(ConflictResolutionStrategy))]
public interface IordersService
...

10. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConcurrencyFault to the list of faults thrown by PlaceOrder task in the task list. This task is
located just before the PlaceOrder method:
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 23

In the task list, double-click the TODO: Ex2 - Add ConcurrencyFault to the list of faults
thrown by PlaceOrder task.
11. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by PlaceOrder
comment, and replace it with the FaultContract attribute with a parameter of type
ConcurrencyFault.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]

<FaultContract(GetType(ServiceFault))>
<FaultContract(GetType(ConcurrencyFault))>
<OperationContract()>
...
Function PlaceOrder(ByVal newOrder As SalesOrderHeader) As Boolean

[Visual C#]

[FaultContract(typeof(ServiceFault))]
[FaultContract(typeof(ConcurrencyFault))]
[OperationContract]
...
bool PlaceOrder(SalesOrderHeader newOrder);

12. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
parameters to specify the conflict resolution strategy to use in PlaceOrder task in the task list.
This task is located just before the PlaceOrder method:
In the task list, double-click the TODO: Ex2 - Add parameters to specify the conflict
resolution strategy to use in PlaceOrder task.
13. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in
PlaceOrder comment, and modify the parameter list of the PlaceOrder method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
<FaultContract(GetType(ServiceFault))>
<FaultContract(GetType(ConcurrencyFault))>
<OperationContract()>
Function PlaceOrder(ByVal newOrder As SalesOrderHeader,
ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean

[Visual C#]
[FaultContract(typeof(ServiceFault))]
[FaultContract(typeof(ConcurrencyFault))]
[OperationContract]
bool PlaceOrder(SalesOrderHeader newOrder,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy);
24 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

14. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConcurrencyFault to the list of faults thrown by AmendOrder task in the task list. This task is
located just before the AmendOrder method:
In the task list, double-click the TODO: Ex2 - Add ConcurrencyFault to the list of faults
thrown by AmendOrder task.
15. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by AmendOrder
comment, and replace it with the FaultContract attribute with a parameter of type
ConcurrencyFault.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]

<FaultContract(GetType(ServiceFault))>
<FaultContract(GetType(ConcurrencyFault))>
<OperationContract()>
...
Function AmendOrder(ByVal order As SalesOrderHeader) As Boolean

[Visual C#]

[FaultContract(typeof(ServiceFault))]
[FaultContract(typeof(ConcurrencyFault))]
[OperationContract]
...
bool AmendOrder(SalesOrderHeader Order);

16. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
parameters to specify the conflict resolution strategy to use in AmendOrder task in the task list.
This task is located just before the AmendOrder method:
In the task list, double-click the TODO: Ex2 - Add parameters to specify the conflict
resolution strategy to use in AmendOrder task.
17. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in
AmendOrder comment, and modify the parameter list of the AmendOrder method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
<FaultContract(GetType(ServiceFault))>
<FaultContract(GetType(ConcurrencyFault))>
<OperationContract()>

Function AmendOrder(ByVal order As SalesOrderHeader,


ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean

[Visual C#]
[FaultContract(typeof(ServiceFault))]
[FaultContract(typeof(ConcurrencyFault))]
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 25

[OperationContract]
bool AmendOrder(SalesOrderHeader Order,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy);

18. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
ConcurrencyFault to the list of faults thrown by CancelOrder task in the task list. This task is
located just before the CancelOrder method:
In the task list, double-click the TODO: Ex2 - Add ConcurrencyFault to the list of faults
thrown by CancelOrder task.
19. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by CancelOrder
comment, and replace it with the FaultContract attribute with a parameter of type
ConcurrencyFault.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
<FaultContract(GetType(ServiceFault))>
<FaultContract(GetType(ConcurrencyFault))>
<OperationContract()>
...
Function CancelOrder(ByVal order As SalesOrderHeader) As Boolean

[Visual C#]
[FaultContract(typeof(ServiceFault))]
[FaultContract(typeof(ConcurrencyFault))]
[OperationContract]
...
bool CancelOrder(SalesOrderHeader Order);

20. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add
parameters to specify the conflict resolution strategy to use in CancelOrder task in the task list.
This task is located just before the CancelOrder method:
In the task list, double-click the TODO: Ex2 - Add parameters to specify the conflict
resolution strategy to use in CancelOrder task.
21. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in
CancelOrder comment, and modify the parameter list of the CancelOrder method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
<FaultContract(GetType(ServiceFault))>
<FaultContract(GetType(ConcurrencyFault))>
<OperationContract()>
Function CancelOrder(ByVal order As SalesOrderHeader,
ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean
26 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

[Visual C#]

[FaultContract(typeof(ServiceFault))]
[FaultContract(typeof(ConcurrencyFault))]
[OperationContract]
bool CancelOrder(SalesOrderHeader Order,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy);

22. Save the IOrdersService file:

a. If you are using Visual Basic, on the File menu, click Save IOrdersService.vb.
b. If you are using Visual C#, on the File menu, click Save IOrdersService.cs.

Task 4: Modify the service implementation to detect and handle concurrency issues
1. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

2. Open the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Save changes to the database,
and possibly attempt to resolve any concurrency errors task in the task list. This task is located in
the saveChangesToDatabase method:
In the task list, double-click the TODO: Ex2 - Save changes to the database, and possibly
attempt to resolve any concurrency errors task.
3. Immediately after the TODO: Ex2 - Save changes to the database, and possibly attempt to
resolve any concurrency errors comment, write code to perform the following tasks:
a. Call the SaveChanges method of the context object in a try block.
b. If the number of changes is greater than zero, return true; otherwise, return false.
c. Create a catch block to handle OptimisticConcurrencyException exceptions.
d. If the resolveConcurrencyException parameter is false, re-throw the exception.
e. If the resolveConcurrencyException parameter is true, check the value of the resolutionStrategy
parameter:

i. If the value of the resolutionStrategy parameter is StoreWins, call the Refresh method of the
context object to refresh the contents of the changedObject object from the database, and
then return false.
Note that the changedObject object is a parameter that is passed to the
saveChangesToDatabase method. It references the object that is updated, inserted, or
deleted from the database.

ii. If the value of the resolutionStrategy parameter is ClientWins, call the Refresh method of
the context object to refresh the contents of the changedObject object from the client. If
the number of changes is greater than zero, return true; otherwise, return false.
iii. For any other value of the resolutionStrategy parameter, throw a new Exception exception to
report an invalid conflict resolution strategy.

Your code should resemble the following code example.

[Visual Basic]

Try
' Save the changes recorded in the context.
' The value returned indicates the number of
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 27

' changes actually saved.


Dim numChanges As Integer = context.SaveChanges()

' If SaveChanges saved no changes then return False,


' otherwise return True
Return (numChanges > 0)
Catch ocEx As OptimisticConcurrencyException
' If an concurrency exception occurs, then determine
' whether it should be handled.
If resolveConcurrencyException Then

' Attempt to resolve the conflict by using the


' specified strategy.
Select Case resolutionStrategy

' If the selected strategy is StoreWins, then


' refresh the context from the database.
' This action overwrites the changes made to the
' context, which are lost, so the method
' returns false.

Case ConflictResolutionStrategy.StoreWins
context.Refresh(RefreshMode.StoreWins, changedObject)
Return False

' If the selected strategy is ClientWins, then


' overwrite conflicting changes in the context
' with the values in changedObject and call
' SaveChanges again.
' If no exception occurs, return true if SaveChanges
' indicates that at least one object was saved.

Case ConflictResolutionStrategy.ClientWins
context.Refresh(RefreshMode.ClientWins, changedObject)
Return (context.SaveChanges() > 0)

' This can happen if the client apps specifies that


' conflicts should be resolved
' and it specifies a conflict resolution of strategy
' of None.

Case Else
Throw New Exception(
"Invalid conflict resolution strategy specified")
End Select

Else
' If the concurrency exception should not be handled,
' then rethrow the exception.
Throw ocEx
End If
End Try

[Visual C#]

try
{
// Save the changes recorded in the context.
// The value returned indicates the number of
// changes actually saved.
int numChanges = context.SaveChanges();
28 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

// If SaveChanges saved no changes then return False,


// otherwise return True
return (numChanges > 0);
}

catch (OptimisticConcurrencyException ocEx)


{
// If an concurrency exception occurs, then determine
// whether it should be handled.
if (resolveConcurrencyException)
{
// Attempt to resolve the conflict by using the
// specified strategy.
switch (resolutionStrategy)
{
// If the selected strategy is StoreWins, then
// refresh the context from the database.
// This action overwrites the changes made to the
// context, which are lost, so the method
// returns false.
case ConflictResolutionStrategy.StoreWins:
context.Refresh(RefreshMode.StoreWins,
changedObject);
return false;

// If the selected strategy is ClientWins, then


// overwrite conflicting changes in the context
// with the values in changedObject and call
// SaveChanges again.
// If no exception occurs, return true if SaveChanges
// indicates that at least one object was saved.
case ConflictResolutionStrategy.ClientWins:
context.Refresh(RefreshMode.ClientWins,
changedObject);
return (context.SaveChanges() > 0);
// This can happen if the client apps specifies that
// conflicts should be resolved
// and it specifies a conflict resolution of strategy
// of None.
default:
throw new Exception(
"Invalid conflict resolution strategy specified");
}
}

else
{
// If the concurrency exception should not be handled,
// then rethrow the exception.
throw ocEx;
}
}

4. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a
ConcurrencyFault object, and populate it with the conflicting values task in the task list. This task
is located in the determineCauseOfOptimisticConcurrencyException method. This method runs
when a concurrency fault occurs when saving an order to the database. The purpose of this method is
to determine the cause of the concurrency exception:
In the task list, double-click the TODO: Ex2 - Create a ConcurrencyFault object, and populate
it with the conflicting values task.
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 29

5. Immediately after the TODO: Ex2 - Create a ConcurrencyFault object, and populate it with the
conflicting values comment, write code to perform the following tasks:

a. Create a new ConcurrencyFault object.


b. Use a Language-Integrated Query (LINQ) to Entities query to determine whether the order
object is still in the database.
c. If the order is not found, set the Reason property of the ConcurrencyFault object to
ItemAlreadyDeleted, and set the ConflictingValues property of the ConcurrencyFault object
to null (Nothing in Visual Basic).
d. If the order has been updated by another user, set the Reason property of the
ConcurrencyFault object to ItemAlreadyAddedOrUpdated, and add the properties of the
order in the database to a new Dictionary object in the ConflictingValues property of the
ConcurrencyFault object.
e. Return the populated ConcurrencyFault object.
Your code should resemble the following code example.

[Visual Basic]

Dim cf As New ConcurrencyFault()

' Determine whether the order is still in the database.


Dim checkOrder As IEnumerable(Of SalesOrderHeader) =
From o In context.SalesOrderHeaders
Where o.SalesOrderID = order.SalesOrderID
Select o

If checkOrder.Count() < 1 Then


' If the item has already been deleted, then there are no
' conflicting values to retrieve from the database.
cf.Reason =
OptimisticConcurrencyExceptionReason.ItemAlreadyDeleted
cf.ConflictingValues = Nothing

Else

' If the order has been added or updated by another user, then
' use the details currently saved in the database
' and add them to the ConcurrencyFault object.
cf.Reason =
OptimisticConcurrencyExceptionReason.ItemAlreadyAddedOrUpdated

Dim storedOrder As SalesOrderHeader = checkOrder.First()


cf.ConflictingValues = New Dictionary(Of String, String)()
cf.ConflictingValues.Add("SalesOrderID",
storedOrder.SalesOrderID.ToString())
cf.ConflictingValues.Add("AccountNumber",
If(storedOrder.AccountNumber Is Nothing, Nothing,
storedOrder.AccountNumber.ToString()))
cf.ConflictingValues.Add("BillToAddressID",
storedOrder.BillToAddressID.ToString())
cf.ConflictingValues.Add("Comment",
If(storedOrder.Comment Is Nothing, Nothing,
storedOrder.Comment.ToString()))
cf.ConflictingValues.Add("ContactID",
storedOrder.ContactID.ToString())
cf.ConflictingValues.Add("CreditCardApprovalCode",
If(storedOrder.CreditCardApprovalCode Is Nothing, Nothing,
storedOrder.CreditCardApprovalCode.ToString()))
cf.ConflictingValues.Add("CreditCardID",
If(storedOrder.CreditCardID Is Nothing, Nothing,
30 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

storedOrder.CreditCardID.ToString()))
cf.ConflictingValues.Add("CurrencyRateID",
If(storedOrder.CurrencyRateID Is Nothing, Nothing,
storedOrder.CurrencyRateID.ToString()))
cf.ConflictingValues.Add("CustomerID",
storedOrder.CustomerID.ToString())
cf.ConflictingValues.Add("DueDate",
storedOrder.DueDate.ToString())
cf.ConflictingValues.Add("Freight",
storedOrder.Freight.ToString())
cf.ConflictingValues.Add("ModifiedDate",
storedOrder.ModifiedDate.ToString())
cf.ConflictingValues.Add("OnlineOrderFlag",
storedOrder.OnlineOrderFlag.ToString())
cf.ConflictingValues.Add("OrderDate",
storedOrder.OrderDate.ToString())
cf.ConflictingValues.Add("PurchaseOrderNumber",
If(storedOrder.PurchaseOrderNumber Is Nothing, Nothing,
storedOrder.PurchaseOrderNumber.ToString()))
cf.ConflictingValues.Add("SalesOrderNumber",
storedOrder.SalesOrderNumber.ToString())
cf.ConflictingValues.Add("SalesPersonID",
If(storedOrder.SalesPersonID Is Nothing, Nothing,
storedOrder.SalesPersonID.ToString()))
cf.ConflictingValues.Add("ShipDate",
If(storedOrder.ShipDate Is Nothing, Nothing,
storedOrder.ShipDate.ToString()))
cf.ConflictingValues.Add("ShipMethodID",
storedOrder.ShipMethodID.ToString())
cf.ConflictingValues.Add("ShipToAddressID",
storedOrder.ShipToAddressID.ToString())
cf.ConflictingValues.Add("Status",
storedOrder.Status.ToString())
cf.ConflictingValues.Add("SubTotal",
storedOrder.SubTotal.ToString())
cf.ConflictingValues.Add("TaxAmt",
storedOrder.TaxAmt.ToString())
cf.ConflictingValues.Add("TerritoryID",
If(storedOrder.TerritoryID Is Nothing, Nothing,
storedOrder.TerritoryID.ToString()))
cf.ConflictingValues.Add("TotalDue",
storedOrder.TotalDue.ToString())
End If

Return cf

[Visual C#]

ConcurrencyFault cf = new ConcurrencyFault();

// Determine whether the order is still in the database.


IEnumerable<SalesOrderHeader> checkOrder =
from o in context.SalesOrderHeaders
where o.SalesOrderID == order.SalesOrderID
select o;

if (checkOrder.Count() < 1)
{
// If the item has already been deleted, then there are no
// conflicting values to retrieve from the database.
cf.Reason =
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 31

OptimisticConcurrencyExceptionReason.ItemAlreadyDeleted;
cf.ConflictingValues = null;
}
else
{
// If the order has been added or updated by another user, then
// use the details currently saved in the database
// and add them to the ConcurrencyFault object.

cf.Reason =
OptimisticConcurrencyExceptionReason.ItemAlreadyAddedOrUpdated;

SalesOrderHeader storedOrder = checkOrder.First();


cf.ConflictingValues = new Dictionary<string, string>();
cf.ConflictingValues.Add("SalesOrderID",
storedOrder.SalesOrderID.ToString());
cf.ConflictingValues.Add("AccountNumber",
storedOrder.AccountNumber == null ? null :
storedOrder.AccountNumber.ToString());
cf.ConflictingValues.Add("BillToAddressID",
storedOrder.BillToAddressID.ToString());
cf.ConflictingValues.Add("Comment",
storedOrder.Comment == null ? null :
storedOrder.Comment.ToString());
cf.ConflictingValues.Add("ContactID",
storedOrder.ContactID.ToString());
cf.ConflictingValues.Add("CreditCardApprovalCode",
storedOrder.CreditCardApprovalCode == null ? null :
storedOrder.CreditCardApprovalCode.ToString());
cf.ConflictingValues.Add("CreditCardID",
storedOrder.CreditCardID == null ? null :
storedOrder.CreditCardID.ToString());
cf.ConflictingValues.Add("CurrencyRateID",
storedOrder.CurrencyRateID == null ? null :
storedOrder.CurrencyRateID.ToString());
cf.ConflictingValues.Add("CustomerID",
storedOrder.CustomerID.ToString());
cf.ConflictingValues.Add("DueDate",
storedOrder.DueDate.ToString());
cf.ConflictingValues.Add("Freight",
storedOrder.Freight.ToString());
cf.ConflictingValues.Add("ModifiedDate",
storedOrder.ModifiedDate.ToString());
cf.ConflictingValues.Add("OnlineOrderFlag",
storedOrder.OnlineOrderFlag.ToString());
cf.ConflictingValues.Add("OrderDate",
storedOrder.OrderDate.ToString());
cf.ConflictingValues.Add("PurchaseOrderNumber",
storedOrder.PurchaseOrderNumber == null ? null :
storedOrder.PurchaseOrderNumber.ToString());
cf.ConflictingValues.Add("SalesOrderNumber",
storedOrder.SalesOrderNumber.ToString());
cf.ConflictingValues.Add("SalesPersonID",
storedOrder.SalesPersonID == null ? null :
storedOrder.SalesPersonID.ToString());
cf.ConflictingValues.Add("ShipDate",
storedOrder.ShipDate == null ? null :
storedOrder.ShipDate.ToString());
cf.ConflictingValues.Add("ShipMethodID",
storedOrder.ShipMethodID.ToString());
cf.ConflictingValues.Add("ShipToAddressID",
storedOrder.ShipToAddressID.ToString());
cf.ConflictingValues.Add("Status",
storedOrder.Status.ToString());
32 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

cf.ConflictingValues.Add("SubTotal",
storedOrder.SubTotal.ToString());
cf.ConflictingValues.Add("TaxAmt",
storedOrder.TaxAmt.ToString());
cf.ConflictingValues.Add("TerritoryID",
storedOrder.TerritoryID == null ? null :
storedOrder.TerritoryID.ToString());
cf.ConflictingValues.Add("TotalDue",
storedOrder.TotalDue.ToString());
}
// Return the populated ConcurrencyFault object.

return cf;

6. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the updateOrderEntityCollectionAndSaveChangesToDatabase task in the task list.
This task is located just before the updateOrderEntityCollectionAndSaveChangesToDatabase
method:
In the task list, double-click the TODO: Ex2 - Add parameters to the
updateOrderEntityCollectionAndSaveChangesToDatabase task.
7. Delete the TODO: Ex2 - Add parameters to the
updateOrderEntityCollectionAndSaveChangesToDatabase comment, and modify the parameter
list of the updateOrderEntityCollectionAndSaveChangesToDatabase method to include two
additional parameters. The first new parameter is a Boolean parameter called
resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type
ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Private Function updateOrderEntityCollectionAndSaveChangesToDatabase(
ByVal operationName As String,
ByVal order As SalesOrderHeader,
ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean

...

[Visual C#]

private bool updateOrderEntityCollectionAndSaveChangesToDatabase(


string operationName,
SalesOrderHeader order,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy)
...

8. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Log the
details of the concurrency exception task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method:
In the task list, double-click the TODO: Ex2 - Log the details of the concurrency exception
task.
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 33

9. Immediately after the TODO: Ex2 - Log the details of the concurrency exception comment, add
code that creates a string containing an error message that includes the operation name and the sales
order ID. Write the message to the log file by calling the logException method.
Your code should resemble the following code example.

[Visual Basic]

Dim eventMessage As String = String.Format(


"Optimistic concurrency failure in {0} for order: {1}",
operationName, order.SalesOrderID)
logException(ocEx, eventMessage)

[Visual C#]

string eventMessage = string.Format(


"Optimistic concurrency failure in {0} for order: {1}",
operationName, order.SalesOrderID);

logException(ocEx, eventMessage);

10. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a
ConcurrencyFault object and throw a WCF FaultException that encapsulates the
ConcurrencyFault object task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method:
In the task list, double-click the TODO: Ex2 - Create a ConcurrencyFault object and throw a
WCF FaultException that encapsulates the ConcurrencyFault object task.
11. Immediately after the TODO: Ex2 - Create a ConcurrencyFault object and throw a WCF
FaultException that encapsulates the ConcurrencyFault object comment, add code that creates a
ConcurrencyFault object by calling the determineCauseOfOptimisticConcurrencyException
method and throws a new FaultException exception of type ConcurrencyFault containing this
ConcurrencyFault object.
Your code should resemble the following code example.

[Visual Basic]

Dim cf As ConcurrencyFault =
determineCauseOfOptimisticConcurrencyException(order, context)

Throw New FaultException(Of ConcurrencyFault)(cf,


String.Format("Concurrency failure in {0}", operationName))

[Visual C#]
ConcurrencyFault cf =
determineCauseOfOptimisticConcurrencyException(order, context);

throw new FaultException<ConcurrencyFault>(cf,


string.Format("Concurrency failure in {0}", operationName));

12. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Log the
details of the update exception task in the task list. This task is located in the
pdateOrderEntityCollectionAndSaveChangesToDatabase method:
34 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

In the task list, double-click the TODO: Ex2 - Log the details of the update exception task.
13. Immediately after the TODO: Ex2 - Log the details of the update exception comment, add code
that creates a string containing an error message that includes the operation name and the sales
order ID. Write the message to the log file by calling the logException method.
Your code should resemble the following code example.

[Visual Basic]
Dim eventMessage As String = String.Format(
"Update failure in {0} for order: {1}",
operationName, order.SalesOrderID)
logException(uEx, eventMessage)

[Visual C#]
string eventMessage = string.Format(
"Update failure in {0} for order: {1}",
operationName, order.SalesOrderID);
logException(uEx, eventMessage);

14. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a
ServiceFault object and throw a WCF FaultException task in the task list. This task is located in the
updateOrderEntityCollectionAndSaveChangesToDatabase method:
In the task list, double-click the TODO: Ex2 - Create a ServiceFault object and throw a WCF
FaultException task.
15. Immediately after the TODO: Ex2 - Create a ServiceFault object and throw a WCF FaultException
comment, add code that creates a ServiceFault object by using the message from the
InnerException property of the UpdateException exception and throws a new FaultException
exception of type ServiceFault by using this ServiceFault object.
Your code should resemble the following code example.

[Visual Basic]

Dim sf As New ServiceFault() With {


.ExceptionMessage = String.Format(
"Update exception occurred in {0}: {1}",
operationName, uEx.InnerException.Message)
}

Throw New FaultException(Of ServiceFault)(sf,


String.Format("Failure in {0}", operationName))

[Visual C#]

ServiceFault sf = new ServiceFault


{
ExceptionMessage = string.Format(
"Update exception occurred in {0}: {1}",
operationName, uEx.InnerException.Message)

};

throw new FaultException<ServiceFault>(sf,


string.Format("Failure in {0}", operationName));
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 35

16. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the PlaceOrder method task in the task list. This task is located just before the
PlaceOrder method:
In the task list, double-click the TODO: Ex2 - Add parameters to the PlaceOrder method task.
17. Delete the TODO: Ex2 - Add parameters to the PlaceOrder method comment, and modify the
parameter list of the PlaceOrder method to include two additional parameters. The first new
parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is
called resolutionStrategy and is of type ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Public Function PlaceOrder(ByVal newOrder As SalesOrderHeader,
ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean _
Implements IOrdersService.PlaceOrder
...

[Visual C#]
public bool PlaceOrder(SalesOrderHeader newOrder,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy)
...

18. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass
parameters from the PlaceOrder method task in the task list. This task is located in the PlaceOrder
method:
In the task list, double-click the TODO: Ex2 - Pass parameters from the PlaceOrder method
task.
19. Modify the parameter list of the call to the
updateOrderEntityCollectionAndSaveChangesToDatabase method to include two additional
parameters. The first new parameter is the resolveConcurrencyException object. The second new
parameter is the resolutionStrategy object.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"PlaceOrder", newOrder,
resolveConcurrencyException, resolutionStrategy)

[Visual C#]
return
this.updateOrderEntityCollectionAndSaveChangesToDatabase(
"PlaceOrder", newOrder,
resolveConcurrencyException, resolutionStrategy);

20. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the AmendOrder method task in the task list. This task is located just before the
AmendOrder method:
36 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

In the task list, double-click the TODO: Ex2 - Add parameters to the AmendOrder method
task.
21. Delete the TODO: Ex2 - Add parameters to the AmendOrder method comment, and modify the
parameter list of the AmendOrder method to include two additional parameters. The first new
parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is
called resolutionStrategy and is of type ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Public Function AmendOrder(ByVal order As SalesOrderHeader,
ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean _
Implements IOrdersService.AmendOrder

[Visual C#]
public bool AmendOrder(SalesOrderHeader Order,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy)
...

22. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass
parameters from the AmendOrder method task in the task list. This task is located in the
AmendOrder method:
In the task list, double-click the TODO: Ex2 - Pass parameters from the AmendOrder method
task.
23. Modify the parameter list of the call to the
updateOrderEntityCollectionAndSaveChangesToDatabase method to include two additional
parameters. The first new parameter is the resolveConcurrencyException object. The second new
parameter is the resolutionStrategy object.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"AmendOrder", order,
resolveConcurrencyException, resolutionStrategy)

[Visual C#]
return
this.updateOrderEntityCollectionAndSaveChangesToDatabase(
"AmendOrder", order,
resolveConcurrencyException, resolutionStrategy);

24. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add
parameters to the CancelOrder method task in the task list. This task is located just before the
CancelOrder method:
In the task list, double-click the TODO: Ex2 - Add parameters to the CancelOrder method
task.
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 37

25. Delete the TODO: Ex2 - Add parameters to the CancelOrder method comment, and modify the
parameter list of the CancelOrder method to include two additional parameters. The first new
parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is
called resolutionStrategy and is of type ConflictResolutionStrategy.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Public Function CancelOrder(ByVal order As SalesOrderHeader,
ByVal resolveConcurrencyException As Boolean,
ByVal resolutionStrategy As ConflictResolutionStrategy) _
As Boolean _
Implements IOrdersService.CancelOrder
...

[Visual C#]
public bool CancelOrder(SalesOrderHeader Order,
bool resolveConcurrencyException,
ConflictResolutionStrategy resolutionStrategy)
...

26. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass
parameters from the CancelOrder method task in the task list. This task is located in the
CancelOrder method:
In the task list, double-click the TODO: Ex2 - Pass parameters from the CancelOrder method
task.
27. Modify the parameter list of the call to the
updateOrderEntityCollectionAndSaveChangesToDatabase method to include two additional
parameters. The first new parameter is the resolveConcurrencyException object. The second new
parameter is the resolutionStrategy object.
Your code should resemble the following code example where the new code is highlighted in bold.

[Visual Basic]
Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"CancelOrder", order,
resolveConcurrencyException, resolutionStrategy)

[Visual C#]
return
this.updateOrderEntityCollectionAndSaveChangesToDatabase(
"CancelOrder", order,
resolveConcurrencyException, resolutionStrategy);

28. Save the OrdersServiceImpl file:


a. If you are using Visual Basic, on the File menu, click Save OrdersServiceImpl.vb.
b. If you are using Visual C#, on the File menu, click Save OrdersServiceImpl.cs.

29. Build the OrdersService project and correct any errors:


In Solution Explorer, right-click the OrdersService project, and then click Build. Correct any
errors.
38 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

Task 5: Update the WPF client application


1. Update the service reference in the OrderManagement project:

a. In the OrderManagement project, expand the Service References folder, right-click OWService,
and then click Update Service Reference.
b. If a Security Alert dialog box appears, click Yes.
2. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

3. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Ex2 - Try to save the order to the database task in the task list. This task is located in the
editOrder method:
In the task list, double-click the TODO: Ex2 - Try to save the order to the database task.
4. Immediately after the TODO: Ex2 - Try to save the order to the database comment, write code to
perform the following tasks:
a. Call the AmendOrder method of the service object, passing the order object as the first
parameter, false as the second parameter, and specifying a conflict resolution strategy of None
as the third parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation status bar item
to "Order saved".
c. If the call returns false, set the Content property of the statusOfLastOperation status bar item
to "Order not saved".
Your code should resemble the following code example.

[Visual Basic]

If service.AmendOrder(order, False, ConflictResolutionStrategy.None)

Then
Me.statusOfLastOperation.Content = "Order saved"
Else
Me.statusOfLastOperation.Content = "Order not saved"

End If

[Visual C#]

if (service.AmendOrder(order, false, ConflictResolutionStrategy.None))


{
this.statusOfLastOperation.Content = "Order saved";
}

else
{
this.statusOfLastOperation.Content = "Order not saved";
}

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Ex2 - Handle a concurrency exception in AmendOrder task in the task
list. This task is located in the editOrder method:
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 39

In the task list, double-click the TODO: Ex2 - Handle a concurrency exception in AmendOrder
task.
6. Immediately after the TODO: Ex2 - Handle a concurrency exception in AmendOrder comment,
write code to perform the following tasks:

a. If the concurrency fault was caused by another user deleting the item, set the Content property
of the statusOfLastOperation status bar item to "Order already cancelled by another user".
b. If the concurrency fault was caused by another user amending the item, set the Content property
of the statusOfLastOperation status bar item to "Order changed by another user", and then
prompt the user by using a message box to determine whether he or she still wants to save the
changes.
c. If the user replies "Yes", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of ClientWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order saved".
d. If the user replies "No", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of StoreWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order not saved".

Your code should resemble the following code example.

[Visual Basic]

If cf.Detail.Reason =
OptimisticConcurrencyExceptionReason.ItemAlreadyDeleted Then

Me.statusOfLastOperation.Content =
"Order already cancelled by another user"

' Otherwise, the order has probably been amended since it was
' retrieved by the user

Else
' Display the status of the operation
Me.statusOfLastOperation.Content =
"Order changed by another user"
' Ask the user whether they still want to cancel the order
If (MessageBox.Show(
"Order was updated by another user. Do you still want to save?",
"Save Error",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No) = MessageBoxResult.Yes) Then

' If yes, then call AmendOrder and specify ClientWins


' for the conflict resolution strategy
service.AmendOrder(order, True,
ConflictResolutionStrategy.ClientWins)
Me.statusOfLastOperation.Content = "Order saved"
Else
' Otherwise specify the StoreWins strategy
service.AmendOrder(order, True,
ConflictResolutionStrategy.StoreWins)
Me.statusOfLastOperation.Content = "Order not saved"
End If
End If
40 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

[Visual C#]

if (cf.Detail.Reason ==
OptimisticConcurrencyExceptionReason.ItemAlreadyDeleted)
{
this.statusOfLastOperation.Content =
"Order already cancelled by another user";
}
// Otherwise, the order has probably been amended since it was
// retrieved by the user
else
{
// Display the status of the operation
this.statusOfLastOperation.Content =
"Order changed by another user";

// Ask the user whether they still want to cancel the order
if (MessageBox.Show(
"Order was updated by another user. Do you still want to save?",
"Save Error",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No) == MessageBoxResult.Yes)
{
// If yes, then call AmendOrder and specify ClientWins
// for the conflict resolution strategy
service.AmendOrder(order, true,
ConflictResolutionStrategy.ClientWins);
this.statusOfLastOperation.Content = "Order saved";
}
else
{
// Otherwise specify the StoreWins strategy
service.AmendOrder(order, true,
ConflictResolutionStrategy.StoreWins);
this.statusOfLastOperation.Content = "Order not saved";
}
}

7. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking


the TODO: Ex2 - Try to delete the order from the database task in the task list. This task is located
in the deleteOrder method:
In the task list, double-click the TODO: Ex2 - Try to delete the order from the database task.
8. Immediately after the TODO: Ex2 - Try to delete the order from the database comment, write
code to perform the following tasks:
a. Call the CancelOrder method of the service object, passing the order object as the first
parameter, false as the second parameter, and specifying a conflict resolution strategy of None
as the third parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation status bar item
to "Order deleted".
c. If the call returns false, set the Content property of the statusOfLastOperation status bar item
to "Order not deleted".

Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 41

If service.CancelOrder(order, False,
ConflictResolutionStrategy.None) Then
Me.statusOfLastOperation.Content = "Order deleted"

Else
Me.statusOfLastOperation.Content = "Order not deleted"
End If

[Visual C#]

if (service.CancelOrder(order, false,
ConflictResolutionStrategy.None))
{
this.statusOfLastOperation.Content = "Order deleted";
}
else
{
this.statusOfLastOperation.Content = "Order not deleted";
}

9. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking


the TODO: Ex2 - Handle a concurrency exception in CancelOrder task in the task list. This task is
located in the deleteOrder method:
In the task list, double-click the TODO: Ex2 - Handle a concurrency exception in CancelOrder
task.
10. Immediately after the TODO: Ex2 - Handle a concurrency exception in CancelOrder comment,
write code to perform the following tasks:
a. If the concurrency fault was caused by another user deleting the item, set the Content property
of the statusOfLastOperation status bar item to "Order already cancelled by another user".
b. If the concurrency fault was caused by another user amending the item, set the Content property
of the statusOfLastOperation status bar item to "Order changed by another user", and then
prompt the user by using a message box to determine whether he or she still wants to cancel the
order.
c. If the user replies "Yes", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of ClientWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order cancelled".
d. If the user replies "No", call the AmendOrder method of the service object, passing the order
object as the first parameter, true as the second parameter, and specifying a conflict resolution
strategy of StoreWins as the third parameter, and then set the Content property of the
statusOfLastOperation status bar item to "Order not cancelled".

Your code should resemble the following code example.

[Visual Basic]
If cf.Detail.Reason =
OptimisticConcurrencyExceptionReason.ItemAlreadyDeleted Then

Me.statusOfLastOperation.Content =
"Order already cancelled by another user"

' Otherwise, the order has probably been amended since it was
' retrieved by the user
42 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

Else

' Display the status of the operation


Me.statusOfLastOperation.Content =
"Order changed by another user"

' Ask the user whether they still want to cancel the order
If MessageBox.Show(
"Order was updated by another user. Do you still want to cancel?",
"Cancelation Error",
MessageBoxButton.YesNo, MessageBoxImage.Question,
MessageBoxResult.No) = MessageBoxResult.Yes Then

' If yes, then call CancelOrder and specify ClientWins for


' the conflict resolution strategy
service.CancelOrder(order, True,
ConflictResolutionStrategy.ClientWins)
Me.statusOfLastOperation.Content = "Order canceled"
Else

' Otherwise specify the StoreWins strategy


service.CancelOrder(order, True,
ConflictResolutionStrategy.StoreWins)
Me.statusOfLastOperation.Content = "Order not canceled"
End If
End If

[Visual C#]
if (cf.Detail.Reason ==
OptimisticConcurrencyExceptionReason.ItemAlreadyDeleted)
{
this.statusOfLastOperation.Content =
"Order already cancelled by another user";
}
// Otherwise, the order has probably been amended since it was
// retrieved by the user
else
{
// Display the status of the operation
this.statusOfLastOperation.Content =
"Order changed by another user";

// Ask the user whether they still want to cancel the order
if (MessageBox.Show(
"Order was updated by another user. Do you still want to cancel?",
"Cancelation Error",
MessageBoxButton.YesNo, MessageBoxImage.Question,
MessageBoxResult.No) == MessageBoxResult.Yes)
{
// If yes, then call CancelOrder and specify ClientWins for
// the conflict resolution strategy
service.CancelOrder(order, true,
ConflictResolutionStrategy.ClientWins);
this.statusOfLastOperation.Content = "Order canceled";
}

else
{
// Otherwise specify the StoreWins strategy
service.CancelOrder(order, true,
ConflictResolutionStrategy.StoreWins);
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 43

this.statusOfLastOperation.Content = "Order not canceled";


}
}

11. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking
the TODO: Ex2 - Try to save the new order to the database task in the task list. This task is located
in the addOrder method:
In the task list, double-click the TODO: Ex2 - Try to save the new order to the database task.
12. Immediately after the TODO: Ex2 - Try to save the new order to the database comment, write
code to perform the following tasks:

a. Call the PlaceOrder method of the service object, passing the order object as the first
parameter, false as the second parameter, and specifying a conflict resolution strategy of None
as the third parameter.
b. If the call returns true, set the Content property of the statusOfLastOperation status bar item
to "New order saved".
c. If the call returns false, set the Content property of the statusOfLastOperation status bar item
to "Order not saved".

Your code should resemble the following code example.

[Visual Basic]
If service.PlaceOrder(newOrder, False, ConflictResolutionStrategy.None) Then
Me.statusOfLastOperation.Content = "New order saved"
Else
Me.statusOfLastOperation.Content = "Order not saved"
End If

[Visual C#]
if (service.PlaceOrder(newOrder, false, ConflictResolutionStrategy.None))
{
this.statusOfLastOperation.Content = "New order saved";
}
else
{
this.statusOfLastOperation.Content = "Order not saved";
}

13. Save the OrderManagementWindow.xaml code-behind file:

a. If you are using Visual Basic, on the File menu, click Save OrderManagementWindow.xaml.vb.
b. If you are using Visual C#, on the File menu, click Save OrderManagementWindow.xaml.cs.

Task 6: Update the unit tests for the PlaceOrder, CancelOrder, and AmendOrder
methods
1. Update the service reference in the OrdersServiceTest project:

a. In the OrdersServiceTest project, expand the Service References folder, right-click OWService,
and then click Update Service Reference.
b. If a Security Alert dialog box appears, click Yes.

2. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
44 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

b. If the task list is showing User Tasks, in the Categories list, click Comments.

3. Open the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass additional
parameters to PlaceOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method:
In the task list, double-click the TODO: Ex2 - Pass additional parameters to PlaceOrder task.
4. Immediately after the TODO: Ex2 - Pass additional parameters to PlaceOrder comment, modify
the call to the PlaceOrder method to include two additional parameters. The first new parameter to
the PlaceOrder method is true, and the second new parameter specifies a conflict resolution strategy
of StoreWins.
Your code should resemble the following code example.

[Visual Basic]
service.PlaceOrder(order, True, ConflictResolutionStrategy.StoreWins)

[Visual C#]
service.PlaceOrder(order, true, ConflictResolutionStrategy.StoreWins);

5. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass
additional parameters to AmendOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method:
In the task list, double-click the TODO: Ex2 - Pass additional parameters to AmendOrder task.
6. Immediately after the TODO: Ex2 - Pass additional parameters to AmendOrder comment, modify
the call to the AmendOrder method to include two additional parameters. The second parameter to
the AmendOrder method is true, and the third parameter specifies a conflict resolution strategy of
StoreWins.
Your code should resemble the following code example.

[Visual Basic]
service.AmendOrder(addedOrder, True, ConflictResolutionStrategy.StoreWins)

[Visual C#]
service.AmendOrder(addedOrder, true, ConflictResolutionStrategy.StoreWins);

7. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass
additional parameters to CancelOrder task in the task list. This task is located in the
CreateUpdateDeleteOrderTest method:
In the task list, double-click the TODO: Ex2 - Pass additional parameters to CancelOrder task.
8. Immediately after the TODO: Ex2 - Pass additional parameters to CancelOrder comment, modify
the call to the CancelOrder method to include two additional parameters. The second parameter to
the CancelOrder method is true, and the third parameter specifies a conflict resolution strategy of
StoreWins.
Your code should resemble the following code example.

[Visual Basic]

service.CancelOrder(modifiedOrder, True,
Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework 45

ConflictResolutionStrategy.StoreWins)

[Visual C#]
service.CancelOrder(modifiedOrder, true,
ConflictResolutionStrategy.StoreWins);

9. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Try
to modify the original copy and test for a ConcurrencyFault task in the task list. This task is
located in the AmendOrderConcurrencyTest method:
In the task list, double-click the TODO: Ex2 - Try to modify the original copy and test for a
ConcurrencyFault task.
10. Immediately after the TODO: Ex2 - Try to modify the original copy and test for a
ConcurrencyFault comment, modify the Comment property of the addedOrder object. Next, call
the AmendOrder method and add a test to check that the service returns a concurrency fault, as the
following code example shows.

[Visual Basic]
addedOrder.Comment = "Original order"
Dim expected As Boolean = False

Try
service.AmendOrder(addedOrder, False,
ConflictResolutionStrategy.None)
Catch cf As FaultException(Of ConcurrencyFault)
expected = True
End Try

Assert.IsTrue(expected)

[Visual C#]
addedOrder.Comment = "Original order";
bool expected = false;
try
{
service.AmendOrder(addedOrder, false,
ConflictResolutionStrategy.None);
}
catch (FaultException<ConcurrencyFault> cf)
{
expected = true;
}
Assert.IsTrue(expected);

11. Save the OrdersServiceImplTest file:


a. If you are using Visual Basic, on the File menu, click Save OrdersServiceImplTest.vb.
b. If you are using Visual C#, on the File menu, click Save OrdersServiceImplTest.cs.

Task 7: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution. Correct any errors.
2. As an administrator, stop and restart IIS:
46 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework

a. Click Start, and in the Search programs and files box, type cmd. In the list of programs, right-
click cmd, and then click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
c. In the Command Prompt window, type iisreset and then press ENTER.
d. Wait for the command to finish running, and then close the Command Prompt window.

3. In the E:\Labfiles folder, run AWReset.bat to reset the AdventureWorks database to a known state:
a. Using Windows Explorer, move to the E:\Labfiles folder.
b. Double-click AWReset.bat.
c. Wait for the batch file to finish running.

4. In Visual Studio, run all of the tests in the solution:


On the Test menu, point to Run, and then click All Tests in Solution.
5. Verify that all of the tests succeed.
6. Start the application without debugging:
On the Debug menu, click Start Without Debugging.
7. In the Order Management window, click the Orders By Contact tab, and then in the Username box,
type Bert
8. In the Password box, type Pa$$w0rd
9. In the Contact ID box, type 200 and then click Get. Verify that you can still add a new order, modify
an existing order, and delete an order:

a. Make sure that you use a valid product ID, for example, 905 or 906.
b. Make sure that you use a discount of less than 1.0, for example, 0.05.
c. The New Order button creates a new order. Pressing INSERT in the TreeView control also
creates a new order.
d. Pressing DELETE in the TreeView control deletes an order.
e. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an
order; you cannot delete items from an order.

10. If time allows, start a second instance of the application, and attempt to make conflicting changes to
the same orders in each instance. Verify that the application detects the conflicts and resolves them.
Some possible suggestions include:
Changing the order quantity for the same order in both instances.
Deleting an order in one instance, and attempting to modify the order quantity in the second
instance.
Deleting the same order in both instances.
11. Close the application.
12. Close Visual Studio:
On the File menu, click Exit.
Lab Answer Key: Building Occasionally Connected Solutions 1

Module 11
Lab Answer Key: Building Occasionally Connected Solutions
Contents:
Exercise 1: Modifying the Orders Application to Use Offline XML Data 2
Exercise 2: Modifying the Orders Application to Synchronize Locally
Cached Data 30
2 Lab Answer Key: Building Occasionally Connected Solutions

Lab 11: Building Occasionally Connected


Solutions
Exercise 1: Modifying the Orders Application to Use Offline XML Data
Task 1: Prepare the environment for the lab
1. Log on to the 10265A-GEN-DEV-11 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures Internet Information
Services(IIS) and creates the required users and groups:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), and then double-click Labfiles.
c. Right-click EnvSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running.
3. In the E:\Labfiles folder, run AWReset.bat:
a. In Windows Explorer, double-click Labfiles, and then double-click AWReset.bat.
b. Wait for the batch file to finish running, and then close Windows Explorer.
4. In the E:\Labfiles\Lab11\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic), or
E:\Labfiles\Lab11\CS\Ex1\Starter folder (if you are using Microsoft Visual C#), run ExSetup.bat as an
administrator. This script adds the required virtual directories to IIS:
a. If you are using Visual Basic, in Windows Explorer, double-click Lab11, double-click VB,
double-click Ex1, and then double-click Starter.
b. If you are using Visual C#, in Windows Explorer, double-click Lab11, double-click CS, double-
click Ex1, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
5. Open IIS Manager as an administrator:
a. Click Start, and in the Search programs and files box, type inetmgr
b. In the list of programs, right-click inetmgr, and then click Run as administrator.
c. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.

6. Add a new self-signed certificate named OrdersWebService:


a. In the Internet Information Services (IIS) Manager window, in the Connections pane, click
10265A-GEN-DEV.
b. In the 10265A-GEN-DEV Home pane, double-click Server Certificates.
c. In the Actions pane, click Create Self-Signed Certificate.
d. In the Create Self-Signed Certificate dialog box, type OrdersWebService and then click OK.

7. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate:
Lab Answer Key: Building Occasionally Connected Solutions 3

a. In the Connections pane, expand Sites, right-click Default Web Site, and then click Edit
Bindings.
b. In the Site Bindings dialog box, click Add.
c. In the Add Site Binding dialog box, in the Type list, click https.
d. In the SSL certificate list, click OrdersWebService, and then click OK.
e. In the Site Bindings dialog box, click Close.

8. Configure the OrdersWebService application to require Secure Sockets Layer (SSL) security:
a. In the Connections pane, expand Default Web Site, and then click OrdersWebService.
b. In the /OrdersWebService Home pane, double-click SSL Settings.
c. In the SSL Settings pane, select the Require SSL check box.
d. In the Actions pane, click Apply.

9. Close IIS Manager:


On the File menu, click Exit.

Task 2: Open the starter project for this exercise


1. Run Microsoft Visual Studio 2010 as an administrator:
a. Click Start, click All Programs, click Microsoft Visual Studio 2010, right-click Microsoft Visual
Studio 2010, and then click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab11\VB\Ex1\Starter\OrdersDAL (if you
are using Visual Basic) or E:\Labfiles\Lab11\CS\Ex1\Starter\OrdersDAL (if you are using Visual C#)
folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab11\VB\Ex1\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab11\CS\Ex1\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.

Task 3: Write code to construct XML file names


1. In Visual Studio, review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the
TODO: Add the System.Xml.Linq namespace task in the task list:
In the task list, double-click the TODO: Add the System.Xml.Linq namespace task.
3. Immediately after the comment, add a statement that brings the System.Xml.Linq namespace into
scope.
Your code should resemble the following code example.

[Visual Basic]

Imports System.Xml.Linq
4 Lab Answer Key: Building Occasionally Connected Solutions

[Visual C#]

using System.Xml.Linq;

4. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Define paths and filenames for XML files task in the task list:
In the task list, double-click the TODO: Define paths and filenames for XML files task.
5. Immediately after the comment, add code that defines three private string variables named filePath,
contactsFile, and ordersFile.
Your code should resemble the following code example.

[Visual Basic]

Private filePath As String


Private contactsFile As String
Private ordersFile As String

[Visual C#]

private string filePath;


private string contactsFile;
private string ordersFile;

6. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Set the file paths task in the task list. This task is located in the
OrderManagementWindow constructor:
In the task list, double-click the TODO: Set the file paths task.
7. Immediately after the comment, add code that sets the filePath variable to the path of the user's My
Documents folder and sets the contactsFile variable to this path concatenated with the file name
contacts.xml.
Your code should resemble the following code example.

[Visual Basic]

filePath = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments)
contactsFile = Path.Combine(filePath, "contacts.xml")

[Visual C#]

filePath = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments);
contactsFile = Path.Combine(filePath, @"contacts.xml");

8. Immediately after the next comment, add code that sets the ordersFile variable to the My Documents
path concatenated with the word orders. The remainder of the file name will be constructed at run
time.
Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Building Occasionally Connected Solutions 5

' The full name of the orders file is constructed depending on the query used to
retrieve them
ordersFile = Path.Combine(filePath, "orders")

[Visual C#]

// The full name of the orders file is constructed depending on the query used to
retrieve them
ordersFile = Path.Combine(filePath, @"orders");

Task 4: Write code to determine whether the Web service is available


1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Check that the Web service is available. Return True if it is, false
otherwise task in the task list:
In the task list, double-click the TODO: Check that the Web service is available. Return True if
it is, false otherwise task.
2. Immediately after the comment, add a private method called CheckWebServiceExists that takes an
OrdersWebServiceClient object as a parameter and returns a Boolean value. This method should try
to access the Orders Web service and return true or false to indicate whether a response was
obtained.
Your code should resemble the following code example.

[Visual Basic]

Private Function CheckWebServiceExists(ByVal service As OrdersWebServiceClient) As


Boolean

Dim result As Boolean = False


Dim response As Byte()
Dim client As New System.Net.WebClient()

Try
response = client.DownloadData(
service.Endpoint.Address.Uri.AbsoluteUri)

Catch ex As Exception
Return result

End Try

Dim str As String = Encoding.UTF8.GetString(response)

If str.IndexOf("xml") > -1 Then


result = True
End If

Return result

End Function

[Visual C#]
// Check that the Web service is available. Return True if it is, false otherwise.
6 Lab Answer Key: Building Occasionally Connected Solutions

private bool CheckWebServiceExists(OrdersWebServiceClient service)


{
bool result = false;
byte[] response;
System.Net.WebClient client = new System.Net.WebClient();
try
{
response = client.DownloadData(
service.Endpoint.Address.Uri.AbsoluteUri);
}
catch (Exception)
{
return result;
}
string str = Encoding.UTF8.GetString(response);
if (str.IndexOf("xml") > -1)
result = true;
return result;
}

Task 5: Write code to cache the contacts


1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the first TODO: Check whether the Web service is still operational task in the task
list. . This task is located in the getContacts_Click method:
In the task list, double-click the first TODO: Check whether the Web service is still operational
task.
2. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getContactsFromLocalCache method and return from the method.

Your code should resemble the following code example.

[Visual Basic]
If CheckWebServiceExists(service) = False Then
Me.getContactsFromLocalCache(rangeFrom, rangeTo)
return
End If

[Visual C#]

if (false == CheckWebServiceExists(service))
{
this.getContactsFromLocalCache(rangeFrom, rangeTo);
return;
}

3. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Cache the contacts task in the task list:
In the task list, double-click the TODO: Cache the contacts task. This task is located in the
getContacts_Click method.
4. Immediately after the comment, add a call to the SaveContactsToLocalCache method, passing the
contacts object.

Your code should resemble the following code example.


Lab Answer Key: Building Occasionally Connected Solutions 7

[Visual Basic]

If contacts IsNot Nothing Then


Me.SaveContactsToLocalCache(contacts)
End If

[Visual C#]

if (contacts != null)
{
this.SaveContactsToLocalCache(contacts);
}

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get contact information from the local cache file task in the task list:
In the task list, double-click the TODO: Get contact information from the local cache file task.
6. Delete the existing code in this method, and then add code that performs the following tasks:

a. Instantiate a new instance of the list of contacts and then update the message in the
statusMessage status bar item with the text "Fetching contacts "
b. If the contacts XML file exists, call the LoadContactsFromLocalCache method, display the data
in the contactsGrid grid, display the number of contacts retrieved in the numContactRows
label, and then update the message in the statusMessage status bar item with the text "Ready".
c. If there are no cached contacts, update the message in the statusMessage status bar item with
the text "No cached data available".

Your code should resemble the following code example.

[Visual Basic]

' Get contact information from the local cache file


Private Sub getContactsFromLocalCache(ByVal rangeFrom As Integer, ByVal rangeTo As
Integer)

Dim contacts As List(Of Contact) = Nothing


Me.statusMessage.Content = "Fetching contacts ..."

Try
If File.Exists(contactsFile) Then
contacts = Me.LoadContactsFromLocalCache(
rangeFrom, rangeTo)

' Display the data.


Me.contactsGrid.DataContext = contacts
Me.numContactRows.Content = String.Format("Rows: {0}",
If(contacts IsNot Nothing, contacts.Count(), 0))
Me.statusMessage.Content = "Ready"
End If

If contacts Is Nothing Or contacts.Count() = 0 Then


Me.statusMessage.Content = "No cached data available"
Me.contactsGrid.DataContext = Nothing
End If

Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
8 Lab Answer Key: Building Occasionally Connected Solutions

End Try

End Sub

[Visual C#]

// Get contact information from the local cache file


private void getContactsFromLocalCache(int rangeFrom, int rangeTo)
{
List<Contact> contacts = null;
this.statusMessage.Content = "Fetching contacts ...";

try
{
if (File.Exists(contactsFile))
{
contacts = this.LoadContactsFromLocalCache(
rangeFrom, rangeTo);

// Display the data.


this.contactsGrid.DataContext = contacts;
this.numContactRows.Content = string.Format(
"Rows: {0}", contacts != null ? contacts.Count() : 0);
this.statusMessage.Content = "Ready";
}

if (contacts == null || contacts.Count() == 0)


{
this.statusMessage.Content = "No cached data available";
this.contactsGrid.DataContext = null;
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format(
"Exception occurred: {0}", ex.Message));
}
}

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Save contact information to the local cache file task in the task list:
In the task list, double-click the TODO: Save contact information to the local cache file task.
8. Delete the existing code in this method, and then add code that performs the following tasks:

a. Construct an XElement object that contains the data in the contacts object that is passed to the
method.
b. Save the XElement object to the file specified by the contactsFile variable.

Your code should resemble the following code example.

[Visual Basic]

' Save contact information to the local cache file

Private Sub SaveContactsToLocalCache(ByVal contacts As List(Of Contact))

Dim xml As XElement = Nothing


Lab Answer Key: Building Occasionally Connected Solutions 9

Try
xml = New XElement("Contacts",
From c In contacts
Select New XElement("Contact",
New XElement("ContactID", c.ContactID),
New XElement("Title", c.Title),
New XElement("FirstName", c.FirstName),
New XElement("MiddleName", c.MiddleName),
New XElement("LastName", c.LastName),
New XElement("EmailAddress", c.EmailAddress),
New XElement("Phone", c.Phone)))
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try

Try
xml.Save(contactsFile)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try

End Sub

[Visual C#]

// Save contact information to the local cache file


private void SaveContactsToLocalCache(List<Contact> contacts)
{
XElement xml = null;

try
{
xml = new XElement("Contacts",
from c in contacts
select new XElement("Contact",
new XElement("ContactID", c.ContactID),
new XElement("Title", c.Title),
new XElement("FirstName", c.FirstName),
new XElement("MiddleName", c.MiddleName),
new XElement("LastName", c.LastName),
new XElement("EmailAddress", c.EmailAddress),
new XElement("Phone", c.Phone)));
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}

try
{
xml.Save(contactsFile);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}
}
10 Lab Answer Key: Building Occasionally Connected Solutions

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method. Read contact information from the local cache file
task in the task list:
In the task list, double-click the TODO: Helper method. Read contact information from the
local cache file task.
10. Delete the existing code in this method, and then add code that performs the following tasks:

a. Load the contents of the contacts file into an XDocument object.


b. Convert the XML into a list of Contact objects and return this list.

Your code should resemble the following code example.

[Visual Basic]

' Helper method. Read contact information from the local cache file
Private Function LoadContactsFromLocalCache(ByVal rangeFrom As Integer, ByVal rangeTo As
Integer) As List(Of Contact)

Dim doc As XDocument = Nothing

Try
doc = XDocument.Load(contactsFile)
Catch ex As Exception
MessageBox.Show(String.Format(
"Exception occurred: {0}", ex.Message))
End Try

Dim query = From contact In doc.Descendants("Contact")


Where Convert.ToInt32(contact.Element("ContactID").Value) _
>= rangeFrom And
Convert.ToInt32(contact.Element("ContactID").Value) _
<= rangeTo
Select New Contact With
{
.ContactID = Convert.ToInt32(
contact.Element("ContactID").Value),
.Title = contact.Element("Title").Value,
.FirstName = contact.Element("FirstName").Value,
.MiddleName = contact.Element("MiddleName").Value,
.LastName = contact.Element("LastName").Value,
.EmailAddress = contact.Element("EmailAddress").Value,
.Phone = contact.Element("Phone").Value
}

Dim contacts As New List(Of Contact)()


contacts.AddRange(query)
Return contacts

End Function

[Visual C#]
// Helper method. Read contact information from the local cache file
private List<Contact> LoadContactsFromLocalCache(int rangeFrom, int rangeTo)
{
XDocument doc = null;

try
{
doc = XDocument.Load(contactsFile);
}
Lab Answer Key: Building Occasionally Connected Solutions 11

catch (Exception ex)


{
MessageBox.Show(string.Format(
"Exception occurred: {0}", ex.Message));
}
var query = from contact in doc.Descendants("Contact")
where Convert.ToInt32(contact.Element("ContactID").Value)
>= rangeFrom &&
Convert.ToInt32(contact.Element("ContactID").Value)
<= rangeTo
select new Contact
{
ContactID = Convert.ToInt32(
contact.Element("ContactID").Value),
Title = contact.Element("Title").Value,
FirstName = contact.Element("FirstName").Value,
MiddleName = contact.Element("MiddleName").Value,
LastName = contact.Element("LastName").Value,
EmailAddress = contact.Element("EmailAddress").Value,
Phone = contact.Element("Phone").Value
};
List<Contact> contacts = new List<Contact>();
contacts.AddRange(query);
return contacts;
}

Task 6: Write code to cache the orders


1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the second TODO: Check whether the Web service is still operational task in the
task list. This task is located in the getOrders_Click method:
In the task list, double-click the second TODO: Check whether the Web service is still
operational task.
2. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getOrdersFromLocalCache method and return from the method.

Your code should resemble the following code example.

[Visual Basic]
If CheckWebServiceExists(service) = False Then
Me.getOrdersFromLocalCache(rangeFrom, rangeTo)
Return
End If

[Visual C#]

if (false == CheckWebServiceExists(service))
{
this.getOrdersFromLocalCache(rangeFrom, rangeTo);
return;
}

3. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the first TODO: Cache the orders task in the task list. This task is located in the
getOrders_Click method:
In the task list, double-click the first TODO: Cache the orders task.
12 Lab Answer Key: Building Occasionally Connected Solutions

4. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the
orders object and the name of the cache file as parameters. Construct the name of the cache file by
concatenating the text "General.xml" to the end of the value in the ordersFile string variable.

Your code should resemble the following code example.

[Visual Basic]

Me.SaveOrdersToLocalCache(orders, String.Format("{0}General.xml", ordersFile))

[Visual C#]

this.SaveOrdersToLocalCache(orders, string.Format("{0}General.xml", ordersFile));

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the third TODO: Check whether the Web service is still operational task in the
task list. This task is located in the getOrdersForContact_Click method:
In the task list, double-click the third TODO: Check whether the Web service is still
operational task.
6. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getOrdersForContactFromLocalCache method and return from the method.

Your code should resemble the following code example.

[Visual Basic]

If CheckWebServiceExists(service) = False Then


Me.getOrdersForContactFromLocalCache(contactID)
Return
End If

[Visual C#]

if (false == CheckWebServiceExists(service))
{
this.getOrdersForContactFromLocalCache(contactID);
return;
}

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the second TODO: Cache the orders task in the task list. This task is located in the
getOrdersForContact_Click method:
In the task list, double-click the second TODO: Cache the orders task.
8. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the
orders object and the name of the cache file as parameters. Construct the name of the cache file by
concatenating the text "contactN.xml" to the end of the value in the ordersFile string variable, where
N is the contactID of the contact.
Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Building Occasionally Connected Solutions 13

Me.SaveOrdersToLocalCache(orders, String.Format("{0}contact{1}.xml", ordersFile,


contactID))

[Visual C#]

this.SaveOrdersToLocalCache(orders, string.Format("{0}contact{1}.xml", ordersFile,


contactID));

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the fourth TODO: Check whether the Web service is still operational task in the
task list. This task is located in the getOrdersForProduct_Click method:
In the task list, double-click the fourth TODO: Check whether the Web service is still
operational task.
10. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service
does not exist, call the getOrdersForProductFromLocalCache method and return from the method.

Your code should resemble the following code example.

[Visual Basic]

If CheckWebServiceExists(service) = False Then


Me.getOrdersForProductFromLocalCache(productID)

Return

End If

[Visual C#]

if (false == CheckWebServiceExists(service))

this.getOrdersForProductFromLocalCache(productID);

return;

11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the third TODO: Cache the orders task in the task list. This task is located in the
getOrdersForProduct_Click method:
In the task list, double-click the TODO: Cache the orders task.
12. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the
orders object and the name of the cache file as parameters. Construct the name of the cache file by
concatenating the text "productN.xml" to the end of the value in the ordersFile string variable, where
N is the productID of the product.
Your code should resemble the following code example.

[Visual Basic]
14 Lab Answer Key: Building Occasionally Connected Solutions

Me.SaveOrdersToLocalCache(orders, String.Format("{0}product{1}.xml", ordersFile,


productID))

[Visual C#]
this.SaveOrdersToLocalCache(orders, string.Format("{0}product{1}.xml", ordersFile,
productID));

13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get general order information from a local cache file task in the task
list:
In the task list, double-click the TODO: Get general order information from a local cache file
task.
14. Delete the existing code in this method, and then add code that performs the following tasks:

a. Create a new empty list of orders and update the statusMessage status bar item with the text
"Fetching orders ".
b. If the general orders XML file exists, call the LoadOrdersFromLocalCache method to populate
the list of orders, call the displayOrders method to display the data in the ordersTree TreeView
control in the window, display the number of orders in the numOrderRows label, and then
update the statusMessage status bar item with the text "Ready".

Note: The general orders XML file has the name "xxxxGeneral.xml" where the value of the xxxx prefix is
specified by the ordersFile variable.

c. If there are no cached orders, update the statusMessage status bar item with the text "No
cached data available" and clear the ordersTree TreeView control.
Your code should resemble the following code example.

[Visual Basic]

Private Sub getOrdersFromLocalCache(ByVal rangeFrom As Integer, ByVal rangeTo As


Integer)
Dim orders As List(Of SalesOrderHeader) = Nothing
Me.statusMessage.Content = "Fetching orders ..."

Try
Dim fileName As String = String.Format(
"{0}General.xml", ordersFile)
If File.Exists(fileName) Then
orders = Me.LoadOrdersFromLocalCache(
rangeFrom, rangeTo, fileName)

' Display the data.


Me.displayOrders(orders, Me.ordersTree)
Me.numOrderRows.Content = String.Format("Rows: {0}",
orders.Count())
Me.statusMessage.Content = "Ready"
End If

If orders Is Nothing Or orders.Count() = 0 Then


Me.statusMessage.Content = "No cached data available"
Me.ordersTree.Items.Clear()
End If
Lab Answer Key: Building Occasionally Connected Solutions 15

Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try

End Sub

[Visual C#]

private void getOrdersFromLocalCache(int rangeFrom, int rangeTo)


{
List<SalesOrderHeader> orders = null;
this.statusMessage.Content = "Fetching orders ...";
try
{
string fileName = string.Format("{0}General.xml", ordersFile);
if (File.Exists(fileName))
{
orders = this.LoadOrdersFromLocalCache(
rangeFrom, rangeTo, fileName);
// Display the data.
this.displayOrders(orders, this.ordersTree);
this.numOrderRows.Content = string.Format("Rows: {0}",
orders.Count());
this.statusMessage.Content = "Ready";
}

if (orders == null || orders.Count() == 0)


{
this.statusMessage.Content = "No cached data available";
this.ordersTree.Items.Clear();
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}
}

15. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get order information for a specified contact from a local cache file
task in the task list:
In the task list, double-click the TODO: Get order information for a specified contact from a
local cache file task.
16. Delete the existing code in this method, and then add code that performs the following tasks:
a. Create a new empty list of orders and update the statusMessage status bar item with the text
"Fetching orders ".
b. If the XML file containing orders for the specified contact exists, call the
LoadOrdersForContactFromLocalCache method to populate the list of orders, call the
displayOrders method to display the data in the ordersForContractTree TreeView control in
the window, display the number of orders in the numOrderForContactRows label, and then
update the statusMessage status bar item with the text "Ready".
16 Lab Answer Key: Building Occasionally Connected Solutions

Note: The orders XML file has the name "contactN.xml" located in the folder specified by the ordersFile
variable where N is the contact ID.

c. If there are no cached orders, update the statusMessage status bar item with the text "No
cached data available" and clear the ordersForContractTree TreeView control.

Your code should resemble the following code example.

[Visual Basic]

Private Sub getOrdersForContactFromLocalCache(ByVal contactID As Integer)

Dim orders As List(Of SalesOrderHeader) = Nothing


Me.statusMessage.Content = "Fetching orders ..."

Try
Dim fileName As String = String.Format(
"{0}contact{1}.xml", ordersFile, contactID)
If File.Exists(fileName) Then
orders = Me.LoadOrdersForContactFromLocalCache(
contactID, fileName)

' Display the data.


Me.displayOrders(orders, Me.ordersForContactTree)
Me.numOrderForContactRows.Content =
String.Format("Rows: {0}", orders.Count())
Me.statusMessage.Content = "Ready"
Else
Me.statusMessage.Content = "No cached data available"
Me.ordersForContactTree.Items.Clear()
End If

Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try

End Sub

[Visual C#]

private void getOrdersForContactFromLocalCache(int contactID)


{
List<SalesOrderHeader> orders = null;
this.statusMessage.Content = "Fetching orders ...";
try
{
string fileName = string.Format(
"{0}contact{1}.xml", ordersFile, contactID);
if (File.Exists(fileName))
{
orders = this.LoadOrdersForContactFromLocalCache(
contactID, fileName);

// Display the data.


this.displayOrders(orders, this.ordersForContactTree);
this.numOrderForContactRows.Content =
string.Format("Rows: {0}", orders.Count());
this.statusMessage.Content = "Ready";
}
Lab Answer Key: Building Occasionally Connected Solutions 17

else
{
this.statusMessage.Content = "No cached data available";
this.ordersForContactTree.Items.Clear();
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}
}

17. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Get order information for a specified product from a local cache file
task in the task list:
In the task list, double-click the TODO: Get order information for a specified product from a
local cache file task.
18. Delete the existing code in this method, and then add code that performs the following tasks:
a. Create a new empty list of orders and update the statusMessage status bar item with the text
"Fetching orders ".
b. If the XML file containing orders for the specified contact exists, call the
LoadOrdersForProductFromLocalCache method to populate the list of orders, call the
displayOrders method to display the data in the ordersForProductTree TreeView control in
the window, display the number of orders in the numOrderForProductRows label, and then
update the statusMessage status bar item with the text "Ready".

Note: The orders XML file has the name "productN.xml" located in the folder specified by the ordersFile
variable where N is the product ID.

c. If there are no cached orders, update the statusMessage status bar item with the text "No
cached data available" and clear the ordersForProductTree TreeView control.
Your code should resemble the following code example.

[Visual Basic]

Private Sub getOrdersForProductFromLocalCache(ByVal productID As Integer)

Dim orders As List(Of SalesOrderHeader) = Nothing


Me.statusMessage.Content = "Fetching orders ..."

Try

Dim fileName As String = String.Format(


"{0}product{1}.xml", ordersFile, productID)

If File.Exists(fileName) Then
orders = Me.LoadOrdersForProductFromLocalCache(
productID, fileName)

' Display the data.


Me.displayOrders(orders, Me.ordersForProductTree)
Me.numOrderForProductRows.Content =
String.Format("Rows: {0}", orders.Count())
Me.statusMessage.Content = "Ready"
18 Lab Answer Key: Building Occasionally Connected Solutions

Else
Me.ordersForProductTree.Items.Clear()
Me.statusMessage.Content = "No cached data available"

End If

Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))

End Try

End Sub

[Visual C#]

private void getOrdersForProductFromLocalCache(int productID)


{
List<SalesOrderHeader> orders = null;
this.statusMessage.Content = "Fetching orders ...";
try
{
string fileName = string.Format(
"{0}product{1}.xml", ordersFile, productID);
if (File.Exists(fileName))
{
orders = this.LoadOrdersForProductFromLocalCache(
productID, fileName);

// Display the data.


this.displayOrders(orders, this.ordersForProductTree);
this.numOrderForProductRows.Content =
string.Format("Rows: {0}", orders.Count());
this.statusMessage.Content = "Ready";
}
else
{
this.ordersForProductTree.Items.Clear();
this.statusMessage.Content = "No cached data available";
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}
}

19. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Save order information to the specified cache file task in the task list:
In the task list, double-click the TODO: Save order information to the specified cache file task.
20. Delete the existing code in this method, and then add code that performs the following tasks:

a. Construct an XElement object that contains the data in the orders object that is passed to the
method.
b. Save the XElement object to the file specified by the fileName variable.
Lab Answer Key: Building Occasionally Connected Solutions 19

Your code should resemble the following code example.

[Visual Basic]

Private Sub SaveOrdersToLocalCache(ByVal orders As List(Of SalesOrderHeader), ByVal


fileName As String)

Dim xml As XElement = Nothing

Try

xml = New XElement("Orders",


From o In orders
Select New XElement("Order",
New XAttribute("OrderNumber", o.SalesOrderID),
New XAttribute("ContactID", o.ContactID),
New XAttribute("AccountNumber", o.AccountNumber),
New XAttribute("Date", o.OrderDate),
New XAttribute("PurchaseOrderNumber",
If(o.PurchaseOrderNumber IsNot Nothing,
o.PurchaseOrderNumber, "")),
New XElement("OrderDetails",
From d In o.SalesOrderDetails
Select New XElement("Detail",
New XElement("Product", d.ProductID),
New XElement("Quantity", d.OrderQty),
New XElement("Price", d.UnitPrice),
New XElement("Discount", d.UnitPriceDiscount),
New XElement("LineCost", d.LineTotal)))))

Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))

End Try

Try
xml.Save(fileName)

Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))

End Try

End Sub

[Visual C#]

private void SaveOrdersToLocalCache(List<SalesOrderHeader> orders, string fileName)


{
XElement xml = null;
try
{
xml = new XElement("Orders",
from o in orders
select new XElement("Order",
new XAttribute("OrderNumber", o.SalesOrderID),
new XAttribute("ContactID", o.ContactID),
new XAttribute("AccountNumber", o.AccountNumber),
new XAttribute("Date", o.OrderDate),
20 Lab Answer Key: Building Occasionally Connected Solutions

new XAttribute("PurchaseOrderNumber",
o.PurchaseOrderNumber ?? ""),
new XElement("OrderDetails",
from d in o.SalesOrderDetails
select new XElement("Detail",
new XElement("Product", d.ProductID),
new XElement("Quantity", d.OrderQty),
new XElement("Price", d.UnitPrice),
new XElement("Discount", d.UnitPriceDiscount),
new XElement("LineCost", d.LineTotal)))));
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}

try
{
xml.Save(fileName);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}
}

21. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to load general order information from a local cache
file task in the task list:
In the task list, double-click the TODO: Helper method to load general order information
from a local cache file task.
22. Delete the existing code in this method, and then add code that performs the following tasks:
a. Load the contents of the orders file into an XDocument object. The name of the orders file is
held in the fileName variable.
b. Iterate through the XML content in the XDocument object and convert it into a list of Order
objects, and then return this list.

Your code should resemble the following code example.

[Visual Basic]

Private Function LoadOrdersFromLocalCache(ByVal rangeFrom As Integer, ByVal rangeTo As


Integer, ByVal fileName As String) As List(Of SalesOrderHeader)

Dim doc As XDocument = Nothing

Try
doc = XDocument.Load(fileName)
Catch ex As Exception

MessageBox.Show(String.Format("Exception occurred: {0}",


ex.Message))
End Try

Dim query = From order In doc.Descendants("Order")


Where Convert.ToInt32(order.Attribute("OrderNumber").Value) _
>= rangeFrom And
Lab Answer Key: Building Occasionally Connected Solutions 21

Convert.ToInt32(order.Attribute("OrderNumber").Value) _
<= rangeTo
Select New SalesOrderHeader With
{
.SalesOrderID = Convert.ToInt32(
order.Attribute("OrderNumber").Value),
.ContactID = Convert.ToInt32(
order.Attribute("ContactID").Value),
.AccountNumber = order.Attribute("AccountNumber").Value,
.OrderDate = Convert.ToDateTime(
order.Attribute("Date").Value),
.PurchaseOrderNumber = order.Attribute(
"PurchaseOrderNumber").Value,
.SalesOrderDetails = Me.getOrderDetailsFromCache(
order.Descendants("OrderDetails"))
}

Dim orders As New List(Of SalesOrderHeader)()


orders.AddRange(query)
Return orders

End Function

[Visual C#]

private List<SalesOrderHeader> LoadOrdersFromLocalCache(int rangeFrom, int rangeTo,


string fileName)
{
XDocument doc = null;
try
{
doc = XDocument.Load(fileName);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}

var query = from order in doc.Descendants("Order")


where Convert.ToInt32(order.Attribute("OrderNumber").Value)
>= rangeFrom &&
Convert.ToInt32(order.Attribute("OrderNumber").Value)
<= rangeTo
select new SalesOrderHeader
{
SalesOrderID = Convert.ToInt32(
order.Attribute("OrderNumber").Value),
ContactID = Convert.ToInt32(
order.Attribute("ContactID").Value),
AccountNumber = order.Attribute("AccountNumber").Value,
OrderDate = Convert.ToDateTime(
order.Attribute("Date").Value),
PurchaseOrderNumber = order.Attribute(
"PurchaseOrderNumber").Value,
SalesOrderDetails = this.getOrderDetailsFromCache(
order.Descendants("OrderDetails"))
};

List<SalesOrderHeader> orders = new List<SalesOrderHeader>();


orders.AddRange(query);
22 Lab Answer Key: Building Occasionally Connected Solutions

return orders;
}

23. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to load order information for a contact from a local
cache file task in the task list:
In the task list, double-click the TODO: Helper method to load order information for a
contact from a local cache file task.
24. Delete the existing code in this method, and then add code that performs the following tasks:

a. Load the contents of the orders file into an XDocument object. The name of the orders file is
held in the fileName variable.
b. Iterate through the XML content in the XDocument object and convert it into a list of Order
objects, and then return this list.

Note: Use the getOrderDetailsFromCache method to retrieve the order details for each order from the
local cache.

Your code should resemble the following code example.

[Visual Basic]

Private Function LoadOrdersForContactFromLocalCache(ByVal contactID As Integer, ByVal


fileName As String) As List(Of SalesOrderHeader)

Dim doc As XDocument = Nothing

Try
doc = XDocument.Load(fileName)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try

Dim query = From order In doc.Descendants("Order")


Where Convert.ToInt32(
order.Attribute("ContactID").Value) = contactID
Select New SalesOrderHeader With
{
.SalesOrderID = Convert.ToInt32(
order.Attribute("OrderNumber").Value),
.ContactID = Convert.ToInt32(
order.Attribute("ContactID").Value),
.AccountNumber = order.Attribute("AccountNumber").Value,
.OrderDate = Convert.ToDateTime(
order.Attribute("Date").Value),
.PurchaseOrderNumber =
order.Attribute("PurchaseOrderNumber").Value,
.SalesOrderDetails = Me.getOrderDetailsFromCache(
order.Descendants("OrderDetails"))
}

Dim orders As New List(Of SalesOrderHeader)()


orders.AddRange(query)
Return orders

End Function
Lab Answer Key: Building Occasionally Connected Solutions 23

[Visual C#]

private List<SalesOrderHeader> LoadOrdersForContactFromLocalCache(int contactID, string


fileName)
{
XDocument doc = null;
try
{
doc = XDocument.Load(fileName);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}

var query = from order in doc.Descendants("Order")


where Convert.ToInt32(
order.Attribute("ContactID").Value) == contactID
select new SalesOrderHeader
{
SalesOrderID = Convert.ToInt32(
order.Attribute("OrderNumber").Value),
ContactID = Convert.ToInt32(
order.Attribute("ContactID").Value),
AccountNumber = order.Attribute("AccountNumber").Value,
OrderDate = Convert.ToDateTime(
order.Attribute("Date").Value),
PurchaseOrderNumber =
order.Attribute("PurchaseOrderNumber").Value,
SalesOrderDetails = this.getOrderDetailsFromCache(
order.Descendants("OrderDetails"))
};

List<SalesOrderHeader> orders = new List<SalesOrderHeader>();


orders.AddRange(query);
return orders;
}

25. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to load order information for a product from a local
cache file task in the task list:
In the task list, double-click the TODO: Helper method to load order information for a
product from a local cache file task.
26. Delete the code in this method, and then add code that performs the following tasks:
a. Load the contents of the contacts file into an XDocument object. The name of the orders file is
held in the fileName variable.
b. Iterate through the XML content in the XDocument object and convert it into a list of Order
objects, and then return this list.

Note: Use the getOrderDetailsForProductFromCache method to retrieve the order details for each
order from the local cache.

Your code should resemble the following code example.


24 Lab Answer Key: Building Occasionally Connected Solutions

[Visual Basic]

Private Function LoadOrdersForProductFromLocalCache(ByVal productID As Integer, ByVal


fileName As String) As List(Of SalesOrderHeader)

Dim doc As XDocument = Nothing

Try
doc = XDocument.Load(fileName)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try

Dim query = From order In doc.Descendants("Order")


Select New SalesOrderHeader With
{
.SalesOrderID = Convert.ToInt32(
order.Attribute("OrderNumber").Value),
.ContactID = Convert.ToInt32(
order.Attribute("ContactID").Value),
.AccountNumber = order.Attribute("AccountNumber").Value,
.OrderDate = Convert.ToDateTime(
order.Attribute("Date").Value),
.PurchaseOrderNumber =
order.Attribute("PurchaseOrderNumber").Value,
.SalesOrderDetails = Me.
getOrderDetailsForProductFromCache(
order.Descendants("OrderDetails"), productID)
}

Dim orders As New List(Of SalesOrderHeader)()

For Each order As SalesOrderHeader In query


If order.SalesOrderDetails.Count > 0 Then
orders.Add(order)
End If
Next order

Return orders

End Function

[Visual C#]

private List<SalesOrderHeader> LoadOrdersForProductFromLocalCache(int productID, string


fileName)
{
XDocument doc = null;
try
{
doc = XDocument.Load(fileName);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Exception occurred: {0}",
ex.Message));
}

var query = from order in doc.Descendants("Order")


select new SalesOrderHeader
Lab Answer Key: Building Occasionally Connected Solutions 25

{
SalesOrderID = Convert.ToInt32(
order.Attribute("OrderNumber").Value),
ContactID = Convert.ToInt32(
order.Attribute("ContactID").Value),
AccountNumber = order.Attribute("AccountNumber").Value,
OrderDate = Convert.ToDateTime(
order.Attribute("Date").Value),
PurchaseOrderNumber =
order.Attribute("PurchaseOrderNumber").Value,
SalesOrderDetails = this.
getOrderDetailsForProductFromCache(
order.Descendants("OrderDetails"), productID)
};

List<SalesOrderHeader> orders = new List<SalesOrderHeader>();

foreach (SalesOrderHeader order in query)


{

if (order.SalesOrderDetails.Count > 0)
{
orders.Add(order);
}

return orders;
}

27. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by
double-clicking the TODO: Helper method to retrieve order details for an order task in the task
list:
In the task list, double-click the TODO: Helper method to retrieve order details for an order
task.
28. Delete the existing code in this method, and uncomment the code at end of the method definition so
that the method receives an enumerable list of XElement objects containing order information as a
parameter.
29. Add code to the method that performs the following tasks:

a. Instantiate a trackable collection of SalesOrderDetail objects.


b. Extract the details of each order from the list of XElement objects and store them in the list of
SalesOrderDetail objects.
c. Add each SalesOrderDetail object to the trackable collection and then return this collection.

Your code should resemble the following code example.

[Visual Basic]

Private Function getOrderDetailsFromCache(ByVal order As IEnumerable(Of XElement)) As


TrackableCollection(Of SalesOrderDetail)

Dim orderDetails As New TrackableCollection(Of SalesOrderDetail)()

Dim query = From details In order.Descendants("Detail")


Select New SalesOrderDetail With
{
.ProductID = Convert.ToInt32(
details.Element("Product").Value),
26 Lab Answer Key: Building Occasionally Connected Solutions

.OrderQty = Convert.ToInt16(
details.Element("Quantity").Value),
.UnitPrice = Convert.ToDecimal(
details.Element("Price").Value),
.UnitPriceDiscount = Convert.ToDecimal(
details.Element("Discount").Value),
.LineTotal = Convert.ToDecimal(
details.Element("LineCost").Value)
}

Dim list As List(Of SalesOrderDetail) = query.ToList()

For Each od As SalesOrderDetail In list


orderDetails.Add(od)
Next od

Return orderDetails

End Function

[Visual C#]

private TrackableCollection<SalesOrderDetail>
getOrderDetailsFromCache(IEnumerable<XElement> order)
{
TrackableCollection<SalesOrderDetail> orderDetails =
new TrackableCollection<SalesOrderDetail>();

var query = from details in order.Descendants("Detail")


select new SalesOrderDetail
{
ProductID = Convert.ToInt32(
details.Element("Product").Value),
OrderQty = Convert.ToInt16(
details.Element("Quantity").Value),
UnitPrice = Convert.ToDecimal(
details.Element("Price").Value),
UnitPriceDiscount = Convert.ToDecimal(
details.Element("Discount").Value),
LineTotal = Convert.ToDecimal(
details.Element("LineCost").Value)
};

List<SalesOrderDetail> list = query.ToList();


foreach (SalesOrderDetail od in list)
{
orderDetails.Add(od);
}

return orderDetails;
}

30. Locate the next comment in the code file behind the OrderManagementWindow.xaml window file by
double-clicking the TODO: Helper method to retrieve order details for an order for a specified
product task in the task list:
In the task list, double-click the TODO: Helper method to retrieve order details for an order
for a specified product task.
Lab Answer Key: Building Occasionally Connected Solutions 27

31. Delete the existing code in this method, and uncomment the code at end of the method definition so
that the method receives an enumerable list of XElement objects containing order information and
the productID value as parameters.
32. Add code to the method that performs the following tasks:

a. Instantiate a trackable collection of SalesOrderDetail objects.


b. Extract the order details from the list of XElement objects and store them in the list of
SalesOrderDetail objects.
c. Add each SalesOrderDetail object to the trackable collection and then return it.

Your code should resemble the following code example.

[Visual Basic]

Private Function getOrderDetailsForProductFromCache(ByVal order As IEnumerable(Of


XElement), ByVal productID As Integer) As TrackableCollection(Of SalesOrderDetail)

Dim orderDetails As New TrackableCollection(Of SalesOrderDetail)()


Dim query = From details In order.Descendants("Detail")
Where Convert.ToInt32(
details.Element("Product").Value) = productID
Select New SalesOrderDetail With
{
.ProductID = Convert.ToInt32(
details.Element("Product").Value),
.OrderQty = Convert.ToInt16(
details.Element("Quantity").Value),
.UnitPrice = Convert.ToDecimal(
details.Element("Price").Value),
.UnitPriceDiscount = Convert.ToDecimal(
details.Element("Discount").Value),
.LineTotal = Convert.ToDecimal(
details.Element("LineCost").Value)
}

Dim list As List(Of SalesOrderDetail) = query.ToList()

For Each od As SalesOrderDetail In list


orderDetails.Add(od)
Next od

Return orderDetails

End Function

[Visual C#]

private TrackableCollection<SalesOrderDetail>
getOrderDetailsForProductFromCache(IEnumerable<XElement> order, int productID)
{
TrackableCollection<SalesOrderDetail> orderDetails =
new TrackableCollection<SalesOrderDetail>();

var query = from details in order.Descendants("Detail")


where Convert.ToInt32(
details.Element("Product").Value) == productID
select new SalesOrderDetail
{
ProductID = Convert.ToInt32(
details.Element("Product").Value),
28 Lab Answer Key: Building Occasionally Connected Solutions

OrderQty = Convert.ToInt16(
details.Element("Quantity").Value),
UnitPrice = Convert.ToDecimal(
details.Element("Price").Value),
UnitPriceDiscount = Convert.ToDecimal(
details.Element("Discount").Value),
LineTotal = Convert.ToDecimal(
details.Element("LineCost").Value)
};

List<SalesOrderDetail> list = query.ToList();


foreach (SalesOrderDetail od in list)
{
orderDetails.Add(od);
}

return orderDetails;
}

Task 7: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Start the application:
On the Debug menu, click Start Without Debugging.
3. In the Username box, type Fred and in the Password box, type Pa$$w0rd
4. Retrieve contacts from 1 to 412:
a. On the Contacts tab, move the To slider to 412, and then click Get.
b. Verify that Fred can access the contact information.
5. Open Windows Explorer, move to the C:\Users\Admin\My Documents folder, and then verify that a
new XML file named contacts.xml has been created:
a. Click Start, and then click Computer.
b. Move to the C:\Users\Admin\My Documents folder.
c. In the Admin dialog box, click Continue.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Move to the My Documents folder and verify that the contacts.xml file is present.
6. Open the contacts.xml file in Windows Internet Explorer to verify that the contact data that is
displayed in the application has been written to the file:
a. Right-click contacts.xml, point to Open with, and then click Internet Explorer.
b. In Internet Explorer, review the data.
c. Close Internet Explorer.
7. In the Order Management application, in the Username box, type Bert and in the Password box,
type Pa$$w0rd
8. On the General Orders tab, retrieve orders from 1 to 43784:
a. On the General Orders tab, move the To slider to 43784, and then click Get.
b. Verify that Bert can access order information.
9. In Windows Explorer, verify that a new XML file named ordersGeneral.xml has been created.
Lab Answer Key: Building Occasionally Connected Solutions 29

10. Open the ordersGeneral.xml file in Internet Explorer to verify that the contact data that is displayed in
the application has been written to the file:
a. Right-click ordersGeneral.xml, point to Open with, and then click Internet Explorer.
b. In Internet Explorer, review the data.
c. Close Internet Explorer.
11. On the Orders By Contact tab, retrieve orders for contact 1:
In the Order Management application, on the Orders By Contact tab, in the Contact ID box,
type 1 and then click Get.
12. In Windows Explorer, verify that a new XML file named orderscontact1.xml has been created.
13. On the Orders By Product tab, retrieve orders for product 776:
In the Order Management application, on the Orders By Product tab, in the Product ID box,
type 776 and then click Get.
14. In Windows Explorer, verify that a new XML file named ordersproduct776.xml has been created.
15. Close Windows Explorer, and then close the Order Management application.
16. Open IIS Manager, and then stop the Orders Web service:
a. Click Start, and then click Control Panel.
b. In Control Panel, click System and Security, and then click Administrative Tools.
c. In the Administrative Tools window, right-click Internet Information Services (IIS) Manager,
and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. In the Connections pane, right-click 10265A-GEN-DEV (10265A-GEN-DEV\Admin), and then
click Stop.
17. In Visual Studio, start the Order Management application:
In Visual Studio, on the Debug menu, click Start Without Debugging.
18. In the Order Management application, in the Username box, type Fred and in the Password box,
type Pa$$w0rd
19. Retrieve contacts from 1 to 412, and then verify that you can access the cached data:
a. On the Contacts tab, move the To slider to 412, and then click Get.
b. Verify that Fred can access the cached contact information.
20. In the Order Management application, in the Username box, type Bert and in the Password box,
type Pa$$w0rd

Note: It is not actually necessary to specify the credentials of a user when retrieving information from the
local cache; these credentials are only required by the Web service.

21. On the General Orders tab, retrieve orders from 1 to 43784, and then verify that you can access the
cached data:
a. On the General Orders tab, move the To slider to 43784, and then click Get.
b. Verify that Bert can access order information.
22. On the Orders By Contact tab, retrieve orders for contact 1, and then verify that you can access the
cached data:
30 Lab Answer Key: Building Occasionally Connected Solutions

In the Order Management application, on the Orders By Contact tab, in the Contact ID box,
type 1 and then click Get.
23. On the Orders By Product tab, retrieve orders for product 776, and then verify that you can access
the cached data:
In the Order Management application, on the Orders By Product tab, in the Product ID box,
type 776 and then click Get.
24. On the Orders By Product tab, retrieve orders for product 777, and then verify that there is no
cached data available:
In the Order Management application, on the Orders By Product tab, in the Product ID box,
type 777 and then click Get.
25. Close the application.
26. In IIS Manager, start the Orders Web service:
a. Switch to the Internet Information Services (IIS) Manager window.
b. In the Connections pane, right-click 10265A-GEN-DEV (10265A-GEN-DEV\Admin), and then
click Start.
c. Close the Internet Information Services (IIS) Manager window.
27. In Visual Studio, run all of the tests in the solution:
On the Test menu, point to Run, and then click All Tests in Solution.
28. Verify that all of the tests succeed.
29. Save and close the solution, and then close Visual Studio:
a. On the File menu, click Save All.
b. On the File menu, click Exit.

Exercise 2: Modifying the Orders Application to Synchronize Locally Cached Data


Task 1: Open the starter project for this exercise
1. Open Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab11\VB\Ex2\Starter\OrdersDAL (if you
are using Visual Basic) or E:\Labfiles\Lab11\CS\Ex2\Starter\OrdersDAL (if you are using Visual C#)
folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab11\VB\Ex2\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab11\CS\Ex2\Starter\OrdersDAL folder, click OrdersDAL.sln, and then click
Open.

Task 2: Add local data caching to the Orders application


1. Add a new local database cache named AWCache to the OrdersDAL project:
a. In Solution Explorer, right-click OrdersDAL, point to Add, and then click New Item.
Lab Answer Key: Building Occasionally Connected Solutions 31

b. In the Add New Item - OrdersDAL dialog box, in the templates list, click Local Database Cache,
in the Name box, type AWCache and then click Add.
2. Configure the server connection to connect to the AdventureWorks database on the local computer:
a. In the Configure Data Synchronization dialog box, under Server connection, click New.
b. In the Add Connection dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress
c. In the Select or enter a database name list, click AdventureWorks, and then click OK.
3. Configure the client connection to connect to the AdventureWorksLocal.sdf Microsoft SQL Server
Compact 3.5 database in the E:\Labfiles\Lab11\VB\Ex2\Starter (if you are using Visual Basic) or
E:\Labfiles\Lab11\CS\Ex2\Starter (if you are using Visual C#) folder:
a. In the Configure Data Synchronization dialog box, under Client connection, click New.
b. In the Add Connection dialog box, in the Connection Properties section, click Browse.
c. If you are using Visual Basic, in the Select SQL Server Compact 3.5 Database File dialog box,
move to the E:\Labfiles\Lab11\VB\Ex2\Starter folder, click AdventureWorksLocal.sdf, and
then click Open.
d. If you are using Visual C#, in the Select SQL Server Compact 3.5 Database File dialog box,
move to the E:\Labfiles\Lab11\CS\Ex2\Starter folder, click AdventureWorksLocal.sdf, and
then click Open.
e. In the Add Connection dialog box, click OK.
4. Add the Contact (Person), SalesOrderDetail (Sales), and SalesOrderHeader (Sales) tables to the
synchronized database:
a. Below the Cached Tables list, click Add.
b. In the Configure Tables for Offline Use dialog box, in the Tables list, select the Contact
(Person), SalesOrderDetail (Sales), and SalesOrderHeader (Sales) check boxes, and then click
OK.
5. Configure synchronization to not use SQL Server Change Tracking, and then initiate the first-time
synchronization:
a. In the Configure Data Synchronization dialog box, clear the Use SQL Server change tracking
check box, and then click OK.
b. In the Generate SQL Scripts dialog box, click OK.
c. Wait while the local database cache is synchronized with the AdventureWorks database.

6. Rebuild the solution:


On the Build menu, click Rebuild Solution.

Task 3: Configure synchronization


1. Add a new class named AWCacheSyncAgent to the OrdersDAL project:
a. In Solution Explorer, right-click OrdersDAL, point to Add, and then click New Item.
b. In the Add New Item - OrdersDAL dialog box, in the templates list, click Class, in the Name
box, type AWCacheSyncAgent and then click Add.

2. Modify the AWCacheSyncAgent class to be a public partial class.

Your code should resemble the following code example.

[Visual Basic]

Public Partial Class AWCacheSyncAgent


32 Lab Answer Key: Building Occasionally Connected Solutions

End Class

[Visual C#]

namespace OrdersDAL
{
public partial class AWCacheSyncAgent
{
}
}

3. Add a private partial method called OnInitialized to the class. This method should take no
parameters and not return a value. In this method, add code that sets the SyncDirection property of
each synchronized table to be Bidirectional. You can access the synchronized tables by using the
_person_ContactSyncTable, _sales_SalesOrderDetailSyncTable, and
_sales_SalesOrderHeaderSyncTable fields in the AWCacheSyncAgent class.

Note: If you are using Visual Basic, do not declare the OnInitialized method as Partial.

Your code should resemble the following code example.

[Visual Basic]

Public Partial Class AWCacheSyncAgent


Private Sub OnInitialized()
Me._person_ContactSyncTable.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional
Me._sales_SalesOrderDetailSyncTable.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional
Me._sales_SalesOrderHeaderSyncTable.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional
End Sub
End Class

[Visual C#]

namespace OrdersDAL
{
public partial class AWCacheSyncAgent
{
partial void OnInitialized()
{
this._person_ContactSyncTable.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional;
this._sales_SalesOrderDetailSyncTable.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional;
this._sales_SalesOrderHeaderSyncTable.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional;
}
}
}
Lab Answer Key: Building Occasionally Connected Solutions 33

Task 4: Add synchronization code to the OrdersService project


1. Add a reference to the Microsoft.Synchronization.Data version 2.0.0.0 assembly to the
OrdersService project:

a. In Solution Explorer, right-click OrdersService, and then click Add Reference.


b. In the Add Reference dialog box, on the .NET tab, click Microsoft.Synchronization.Data
Version 2.0.0.0, and then click OK.

2. Review the task list:

a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.

3. Open the code file for the IOrdersService interface by double-clicking the first TODO: Synchronize
the AdventureWorksLocal SQL Server CE database with the AdventureWorks database in SQL
Server Express task in the task list. This task is located in the IOrdersService code file:
In the task list, double-click the first TODO: Synchronize the AdventureWorksLocal SQL Server
CE database with the AdventureWorks database in SQL Server Express task.
4. Below the summary and returns comments, declare a method named SyncWithServer that takes no
parameters and returns a Boolean value.
Your code should resemble the following code example.

[Visual Basic]

Function SyncWithServer() As Boolean

[Visual C#]

bool SyncWithServer();

5. Open the code file for the OrdersServiceImpl class by double-clicking the TODO: Namespace
containing types required by Synchronization Services task in the task list:
In the task list, double-click the TODO: Namespace containing types required by
Synchronization Services task.
6. After the comment, add a statement that brings the Microsoft.Synchronization.Data namespace
into scope.

Your code should resemble the following code example.

[Visual Basic]

Imports Microsoft.Synchronization.Data

[Visual C#]

using Microsoft.Synchronization.Data;
34 Lab Answer Key: Building Occasionally Connected Solutions

7. Locate the next comment by double-clicking the second TODO: Synchronize the
AdventureWorksLocal SQL Server CE database with the AdventureWorks database in SQL
Server Express task in the task list:
In the task list, double-click the second TODO: Synchronize the AdventureWorksLocal SQL
Server CE database with the AdventureWorks database in SQL Server Express task.
8. Below the summary and returns comments, implement the SyncWithServer method. In this method,
use the AWCacheSyncAgent object to synchronize the local SQL Server Compact data with SQL
Server Express.

Your code should resemble the following code example.

[Visual Basic]

Public Function SyncWithServer() As Boolean _


Implements IOrdersService.SyncWithServer

Try
Dim awSyncAgent As New AWCacheSyncAgent()
Dim awSyncStats As SyncStatistics = awSyncAgent.Synchronize()
Return True
Catch
Return False
End Try

End Function

[Visual C#]

public bool SyncWithServer()


{
try
{
AWCacheSyncAgent awSyncAgent = new AWCacheSyncAgent();
SyncStatistics awSyncStats = awSyncAgent.Synchronize();
return true;
}
catch
{
return false;
}
}

Task 5: Add synchronization code to the user interface


1. Locate the SyncDatabase_Click method in the code file behind the OrderManagementWindow.xaml
window by double-clicking the TODO: Add code here to synchronize data between SQL Server
Express and AdventureWorksLocal.sdf task in the task list:
In the task list, double-click the TODO: Add code here to synchronize data between SQL
Server Express and AdventureWorksLocal.sdf task.
2. Add code to this method to call the SyncWithServer method and display the success of the call to
the user.

Your code should resemble the following code example.

[Visual Basic]
Lab Answer Key: Building Occasionally Connected Solutions 35

If service.SyncWithServer() Then
MessageBox.Show("Synchronization Successful", "Synchronization",
MessageBoxButton.OK, MessageBoxImage.Information)
Else
MessageBox.Show("Synchronization Failed", "Synchronization",
MessageBoxButton.OK, MessageBoxImage.Error)
End If

[Visual C#]

if (service.SyncWithServer())
{
MessageBox.Show("Synchronization Successful", "Synchronization",
MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
MessageBox.Show("Synchronization Failed", "Synchronization",
MessageBoxButton.OK, MessageBoxImage.Error);
}

Task 6: Build and test the application


1. Build the solution and correct any errors:
On the Build menu, click Build Solution. Correct any errors.
2. Start the application without debugging:
On the Debug menu, click Start Without Debugging.
3. Synchronize the data with SQL Server Express:
a. In the Order Management window, click Synchronize.
b. In the Synchronization message box, click OK.
4. Retrieve the orders for the contact with a Contact ID value of 1:
a. In the Order Management window, on the Orders By Contact tab, in the Contact ID box, type 1
b. Click Get.
5. Display the edit window for the details of the second order that this contact made:
Expand order number 46389, click the child row, and then press ENTER.
6. Change the quantity of the order to 4 and the discount to 0.5:
a. In the Quantity column, type 4.
b. In the Discount column, type 0.5 and then press ENTER.
c. In the Edit Order for Contact 1 window, click OK.
7. Synchronize the data with SQL Server Express:
a. In the Order Management window, click Synchronize.
b. In the Synchronization message box, click OK.
8. In Visual Studio, using Server Explorer, examine the data in the AdventureWorks SQL Server Express
database and verify that the changes were synchronized:
36 Lab Answer Key: Building Occasionally Connected Solutions

a. Click Server Explorer, expand Data Connections, expand 10265a-gen-


dev\sqlexpress.AdventureWorks.dbo, expand Tables, right-click SalesOrderDetail (Sales),
and then click Show Table Data.
b. Search for the order with a SalesOrderID value of 46389 and verify that the value in the
OrderQty column is 4 and the value in the UnitPriceDiscount column is 0.5.

9. In Server Explorer, locate the details for order 44132, change the OrderQty value to 99, and then
press ENTER.
10. Return to the Order Management application and synchronize the data with SQL Server Express
again:

a. In the Order Management window, click Synchronize.


b. In the Synchronization message box, click OK.

11. Requery the orders for contact 1 and verify that order 44132 has been updated:
a. On the Orders By Contact tab, in the Contact ID box, type 1
b. Click Get.
c. Expand the row for order 44132 and verify that the value in the Qty field is 99.
12. Close the Order Management application.
13. Save and close the solution, and then close Visual Studio:

a. On the File menu, click Save All.


b. On the File menu, click Exit.
Lab Answer Key: Querying Data by Using WCF Data Services 1

Module 12
Lab Answer Key: Querying Data by Using WCF Data Services
Contents:
Exercise 1: Exposing Order Data as a WCF Data Service 2
Exercise 2: Consuming a WCF Data Service 6
Exercise 3: Restricting Access to Data That a WCF Data Service Exposes 10
Exercise 4: Implementing a Business Operation in a WCF Data Service 14
2 Lab Answer Key: Querying Data by Using WCF Data Services

Lab 12: Creating and Using WCF Data Services


Exercise 1: Exposing Order Data as a WCF Data Service
Task 1: Prepare the environment for the lab
1. Log on to the 10265A-GEN-DEV-12 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), and then double-click Labfiles.
c. Right-click EnvSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running.

Task 2: Prepare the AdventureWorks database for the lab


In the E:\Labfiles folder, run AWReset.bat:
a. In the E:\Labfiles folder, double-click AWReset.bat.
b. Wait for the batch file to finish running.

Task 3: Open the starter project


1. In the E:\Labfiles\Lab12\VB\Ex1\Starter or E:\Labfiles\Lab12\CS\Ex1\Starter folder, run ExSetup.bat as
an administrator:
a. In Windows Explorer, double-click Lab12, double-click VB or CS, double-click Ex1, and then
double-click Starter.
b. Right-click ExSetup.bat, and then click Run as administrator.
c. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
d. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open Microsoft Visual Studio 2010 as an administrator:
a. Click Start, point to All Programs, click Microsoft Visual Studio 2010, right-click Microsoft
Visual Studio 2010, and then click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
3. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex1\Starter or
E:\Labfiles\Lab12\CS\Ex1\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab12\VB\Ex1\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab12\CS\Ex1\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.
Lab Answer Key: Querying Data by Using WCF Data Services 3

Task 4: Add a data service to the ShippingDataServiceSite project


1. Add a Windows Communication Foundation (WCF) data service named ShippingDataService to the
ShippingDataServiceSite project:
a. In Solution Explorer, right-click ShippingDataServiceSite, point to Add, and then click New
Item.
b. In the Add New Item - ShippingDataServiceSite dialog box, click WCF Data Service, in the
Name box, type ShippingDataService and then click Add.
2. In the Code Editor, add statements to bring the ShippingDataServiceLibrary and
System.Diagnostics namespaces into scope.
Your code should resemble the following code example.

[Visual Basic]
Imports ShippingDataServiceLibrary
Imports System.Diagnostics

[Visual C#]
using ShippingDataServiceLibrary;
using System.Diagnostics;

3. Add AdventureWorksEntities as the type of the DataService class:


Locate the comment TODO: replace [[class name]] with your data class name for Visual Basic
or TODO: put your data source class name here for Visual C#, delete the comment, and then
type AdventureWorksEntities
Your code should resemble the following code example.

[Visual Basic]
Public Class ShippingDataService
Inherits DataService(Of AdventureWorksEntities)

[Visual C#]
public class ShippingDataService : DataService<AdventureWorksEntities>

4. In the InitializeService method, write code that performs the following tasks:
a. Set the access permissions for the SalesOrderHeaders, Addresses, and Contacts entity sets to
AllRead.
b. Set the page size for the three entity sets to 10.
Your code should resemble the following code example.

[Visual Basic]
Public Shared Sub InitializeService(ByVal config As DataServiceConfiguration)

' Configure which entity sets to make visible


config.SetEntitySetAccessRule("SalesOrderHeaders",
EntitySetRights.AllRead)
config.SetEntitySetAccessRule("Addresses",
EntitySetRights.AllRead)
config.SetEntitySetAccessRule("Contacts",
EntitySetRights.AllRead)
config.DataServiceBehavior.MaxProtocolVersion =
4 Lab Answer Key: Querying Data by Using WCF Data Services

DataServiceProtocolVersion.V2

' Configure page sizes


config.SetEntitySetPageSize("SalesOrderHeaders", 10)
config.SetEntitySetPageSize("Addresses", 10)
config.SetEntitySetPageSize("Contacts", 10)

End Sub

[Visual C#]
public static void InitializeService(DataServiceConfiguration config)
{
// Configure which entity sets to make visible.
config.SetEntitySetAccessRule("SalesOrderHeaders",
EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Addresses",
EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Contacts",
EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V2;

// Configure page sizes.


config.SetEntitySetPageSize("SalesOrderHeaders", 10);
config.SetEntitySetPageSize("Addresses", 10);
config.SetEntitySetPageSize("Contacts", 10);
}

5. Write code that logs service exceptions to the event log. Your code must perform the following tasks:
a. Declare a private constant string named eventSource (_eventSource in Visual Basic) with a value
of Orders Service.
b. Declare a private constant string named eventLog (_eventLog in Visual Basic) with a value of
Application.
c. Define a private method called logException that takes an exception and a string as parameters,
checks that the eventSource (_eventSource in Visual Basic) object exists, and then writes an
entry to the event log.
d. Override the HandleException method to call the logException method, and then call the
HandleException method of the base class.

Your code should resemble the following code example.

[Visual Basic]

Protected Overloads Overrides Sub HandleException(ByVal args As HandleExceptionArgs)


logException(args.Exception, args.ResponseStatusCode.ToString())
MyBase.HandleException(args)
End Sub

''' <summary>
''' Record the details of exceptions in the Application event log.
''' </summary>
''' <param name="ex">
''' The exception thrown by the service. This method records the text
''' of the exception in the Application event log.
''' </param>
''' <param name="eventName">
''' Additional application-specific text provided by the service
''' operation that threw the exception.
Lab Answer Key: Querying Data by Using WCF Data Services 5

''' </param>

Private Sub logException(ByVal ex As Exception, ByVal responseCode As String)

If Not EventLog.SourceExists(_eventSource) Then


EventLog.CreateEventSource(_eventSource, _eventLog)
End If

Dim eventMessage As String = String.Format("{0}: {1}: {2}", responseCode,


ex.Message, System.Threading.Thread.CurrentPrincipal.Identity.Name)
EventLog.WriteEntry(_eventSource, eventMessage, EventLogEntryType.Error)
End Sub

''' <summary>
''' The name of the event source for logging exceptions to the event /// log
''' </summary>

Private Const _eventSource As String = "Orders Service"

''' <summary>
''' The name of the event log to use for recording exceptions.
''' </summary>

Private Const _eventLog As String = "Application"

[Visual C#]
protected override void HandleException(HandleExceptionArgs args)
{
logException(args.Exception, args.ResponseStatusCode.ToString());
base.HandleException(args);
}

/// <summary>
/// Record the details of exceptions in the application event log.
/// </summary>
/// <param name="ex">
/// The exception that the service throws. This method records the
/// text of the exception in the application event log.
/// </param>
/// <param name="eventName">
/// Additional application-specific text that the service provides.
/// The operation that threw the exception.
/// </param>
private void logException(Exception ex, string responseCode)
{
if (!EventLog.SourceExists(eventSource))
{
EventLog.CreateEventSource(eventSource, eventLog);
}

string eventMessage = string.Format("{0}: {1}: {2}",


responseCode, ex.Message,
System.Threading.Thread.CurrentPrincipal.Identity.Name);
EventLog.WriteEntry(eventSource, eventMessage,
EventLogEntryType.Error);
}

/// <summary>
/// The name of the event source for logging exceptions to the event /// log.
/// </summary>
private const string eventSource = "Orders Service";
6 Lab Answer Key: Querying Data by Using WCF Data Services

/// <summary>
/// The name of the event log to use for recording exceptions.
/// </summary>
private const string eventLog = "Application";

6. Save the ShippingDataService.svc file:


On the File menu, click Save ShippingDataService.svc.vb or Save ShippingDataService.svc.cs.

Task 5: Build and test the data service


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Browse to the service by using Windows Internet Explorer:
a. In Solution Explorer, right-click ShippingDataService.svc, and then click View in Browser.
b. If the Windows Security dialog box appears, in the User name box, type Student and in the
Password box, type Pa$$w0rd and then click OK.
c. Review the XML data that lists the available entity sets.
3. Explore the service by using Internet Explorer:

Note: You must right-click anywhere on the page, and then click View Source to view the data
in the following steps. Internet Explorer cannot display the XML data that the service returns.

a. In Internet Explorer, visit http://localhost/ShippingDataServiceSite/ShippingDataService.svc


/Contacts. Review the contact data.
b. In Internet Explorer, visit http://localhost/ShippingDataServiceSite/ShippingDataService.svc
/Contacts(7). Review the contact data.
c. In Internet Explorer, visit http://localhost/ShippingDataServiceSite/ShippingDataService.svc
/Contacts?$filter=LastName gt 'D'. Review the contact data.
4. Close Internet Explorer.
5. Close all instances of Notepad.
6. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Consuming a WCF Data Service


Task 1: Open the starter project
1. In the E:\Labfiles\Lab12\VB\Ex2\Starter or E:\Labfiles\Lab12\CS\Ex2\Starter folder, run ExSetup.bat as
an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, double-click Lab12, double-click VB or CS,
double-click Ex2, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
Lab Answer Key: Querying Data by Using WCF Data Services 7

2. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex2\Starter or


E:\Labfiles\Lab12\CS\Ex2\Starter folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. In the Open Project dialog box, move to the E:\Labfiles\Lab12\VB\Ex2\Starter or
E:\Labfiles\Lab12\CS\Ex2\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.

Task 2: Call the data service to retrieve SalesOrderHeader entities from a client Web
application
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Retrieve all
SalesOrderHeaders with the chosen shipMethodID in the task list. This task is located in the
SalesOrders method:
In the task list, double-click the comment TODO: Ex2 - Retrieve all SalesOrderHeaders with
the chosen shipMethodID.
3. In the SalesOrders method, immediately after the comment, write code that performs the following
tasks:
a. Assign a new AdventureWorksEntities object to the context variable.
b. Set the Credentials property of the context object to the current default network credentials.
c. Define a Language-Integrated Query (LINQ) query named orders that retrieves all of the
SalesOrderHeader entities with a ShipMethodID property equal to the value of the
shipMethodID variable.

Your code should resemble the following code example.

[Visual Basic]
' Obtain a new AdventureWorksEntities context object.
context = New AdventureWorksEntities(New Uri(Constants.serviceUrl))

' Set the credentials.


context.Credentials = CredentialCache.DefaultNetworkCredentials

' Define the LINQ query to return the matching


' SalesOrderHeader entities.
Dim orders = From sh In context.SalesOrderHeaders _
Where sh.ShipMethodID = shipMethodID _
Select sh

[Visual C#]
// Obtain a new AdventureWorksEntities context object.
context = new AdventureWorksEntities(new Uri(Constants.serviceUrl));

// Set the credentials.


context.Credentials = CredentialCache.DefaultNetworkCredentials;

// Define the LINQ query to return the matching


// SalesOrderHeader entities.
var orders = from sh in context.SalesOrderHeaders
where sh.ShipMethodID == shipMethodID
8 Lab Answer Key: Querying Data by Using WCF Data Services

select sh;

4. Save the HomeController file:


a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 3: Call the data service to retrieve the details of a specific SalesOrderHeader entity
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the
SalesOrderHeader with SalesOrderID = id in the task list. This task is located in the Details method:
In the task list, double-click the comment TODO: Ex2 - Return the SalesOrderHeader with
SalesOrderID = id.
3. In the Details method, immediately after the comment, write code that uses the AddQueryOption
method of the SalesOrderHeaders entity set to filter the entity set and return the
SalesOrderHeader entity with a SalesOrderID property equal to the value of the id variable.
Your code should resemble the following code example.

[Visual Basic]
' Use a query operation to filter the returned data.
Dim orderByID As DataServiceQuery(Of SalesOrderHeader) =
context.SalesOrderHeaders.AddQueryOption("$filter", "SalesOrderID eq " & id)

[Visual C#]
// Use a query operation to filter the returned data.
DataServiceQuery<SalesOrderHeader> orderByID =
context.SalesOrderHeaders.AddQueryOption("$filter",
"SalesOrderID eq " + id);

4. Save the HomeController file:


a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 4: Call the data service to retrieve the details of a specific Address entity
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the Address
with AddressID = id in the task list. This task is located in the AddressDetails method:
In the task list, double-click the comment TODO: Ex2 - Return the Address with AddressID =
id.
3. In the AddressDetails method, immediately after the comment, write code that uses the
AddQueryOption method of the Addresses entity set to filter the entity set and return the Address
entity with an AddressID property equal to the value of the id variable.
Your code should resemble the following code example.
Lab Answer Key: Querying Data by Using WCF Data Services 9

[Visual Basic]
' Use a query operation to filter the returned data.
Dim orderByID As DataServiceQuery(Of Address) =
context.Addresses.AddQueryOption("$filter", "AddressID eq " & id)

[Visual C#]
// Use a query operation to filter the returned data.
DataServiceQuery<Address> orderByID =
context.Addresses.AddQueryOption("$filter", "AddressID eq " + id);

4. Save the HomeController file:


a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 5: Call the data service to retrieve the details of a specific Contact entity
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the Contact
with ContactID = id in the task list. This task is located in the ContactDetails method:
In the task list, double-click the comment TODO: Ex2 - Return the Contact with ContactID =
id.
3. In the ContactDetails method, immediately after the comment, write code that uses the
AddQueryOption method of the Contacts entity set to filter the entity set and return the Contact
entity with a ContactID property equal to the value of the id variable.
Your code should resemble the following code example.

[Visual Basic]
' Use a query operation to filter the returned data.
Dim orderByID As DataServiceQuery(Of Contact) =
context.Contacts.AddQueryOption("$filter", "ContactID eq " & id)

[Visual C#]
//Use a query operation to filter the returned data.
DataServiceQuery<Contact> orderByID =
context.Contacts.AddQueryOption("$filter", "ContactID eq " + id);

4. Save the HomeController file:


a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 6: Build and test the client Web site


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Check that the ShippingDetailsSite project is set as the StartUp project:
10 Lab Answer Key: Querying Data by Using WCF Data Services

In Solution Explorer, if the ShippingDetailsSite project is not highlighted in bold text, right-click
the ShippingDetailsSite project, and then click Set as StartUp Project.
3. Start the application in Debug mode:
a. On the Debug menu, click Start Debugging.
b. If the Windows Security dialog box appears, in the User name box, type Student and in the
Password box, type Pa$$w0rd and then click OK.
4. Explore the service by using Internet Explorer:
a. On the Adventure Works Delivery Information page, in the Show Orders by Shipping
Method box, type 1 and then click Submit.
b. On the SalesOrders page, click any of the Details hyperlinks.
c. On the Details page, click one of the Contact Details hyperlinks.
5. Close Internet Explorer.
6. Close the solution:
On the File menu, click Close Solution.

Exercise 3: Restricting Access to Data That a WCF Data Service Exposes


Task 1: Open the starter project
1. In the E:\Labfiles\Lab12\VB\Ex3\Starter or E:\Labfiles\Lab12\CS\Ex3\Starter folder, run ExSetup.bat as
an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, double-click Lab12, double-click VB or CS,
double-click Ex3, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex3\Starter or
E:\Labfiles\Lab12\CS\Ex3\Starter folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. In the Open Project dialog box, move to the E:\Labfiles\Lab12\VB\Ex3\Starter or
E:\Labfiles\Lab12\CS\Ex3\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.

Task 2: Modify the data service to filter returned data based on the user's role
membership
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the ShippingDataService.svc file by double-clicking the comment TODO: Ex3 - Filter the query
results based on localgroup membership in the task list. This task is located in the
OnQuerySalesOrderHeaders method:
In the task list, double-click the comment TODO: Ex3 - Filter the query results based on
localgroup membership.
Lab Answer Key: Querying Data by Using WCF Data Services 11

3. Notice the QueryInterceptor attribute of the OnQuerySalesOrderHeaders method.


4. In the OnQuerySalesOrderHeaders method, immediately after the comment, write code that
performs the following tasks:
a. If the current user is in the WorldwideShipping role, allow the user to view all SalesOrderHeader
entities with non-null ShipDate properties.
b. If the current user is in the USShipping role, allow the user to view all SalesOrderHeader entities
with non-null ShipDate properties and with the ShipMethodID property equal to 1, 2, or 4.
c. Otherwise, return no SalesOrderHeader entities.

Your code should resemble the following code example.

[Visual Basic]
<QueryInterceptor("SalesOrderHeaders")> _
Public Function OnQuerySalesOrderHeaders() As Expression(Of Func(Of SalesOrderHeader,
Boolean))

' Filter the query results based on localgroup membership


If (HttpContext.Current.User.IsInRole("WorldwideShipping")) Then

' Everything
Return Function(o) o.ShipDate IsNot Nothing

ElseIf (HttpContext.Current.User.IsInRole("USShipping")) Then

' Just US Shipping methods


Return Function(o) o.ShipDate IsNot Nothing And (o.ShipMethodID = 1 Or
o.ShipMethodID = 2 Or o.ShipMethodID = 4)

Else

' Nothing at all


Return Function(o) o.ShipMethodID = -1

End If

End Function

[Visual C#]
[QueryInterceptor("SalesOrderHeaders")]
public Expression<Func<SalesOrderHeader, bool>> OnQuerySalesOrderHeaders()
{
// Filter the query results based on localgroup membership
if (HttpContext.Current.User.IsInRole("WorldwideShipping"))
{
// Everything
return o => o.ShipDate != null;
}
else if (HttpContext.Current.User.IsInRole("USShipping"))
{
// Just US Shipping methods
return o => o.ShipDate != null && (o.ShipMethodID == 1 ||
o.ShipMethodID == 2 || o.ShipMethodID == 4);
}
else
{
// Nothing at all
return o => o.ShipMethodID == -1;
}
12 Lab Answer Key: Querying Data by Using WCF Data Services

5. Save the ShippingDataService.svc file:


a. If you are using Visual Basic, on the File menu, click Save ShippingDataService.svc.vb.
b. If you are using Visual C#, on the File menu, click Save ShippingDataService.svc.cs.

Task 3: Modify the Web client application to support data paging


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Ex3 - Declare a
QueryOperationResponse object in the task list. This task is located in the SalesOrders method:
In the task list, double-click the comment TODO: Ex3 - Declare a QueryOperationResponse
object.
3. In the SalesOrders method, immediately after the comment, write code that declares a variable
based on the QueryOperationResponse generic type, called response. Specify SalesOrderHeader as
the type parameter, and initialize it to null.
Your code should resemble the following code example.

[Visual Basic]

' Handle result of query execution and paging


Dim response As QueryOperationResponse(Of SalesOrderHeader) = Nothing

[Visual C#]

// Handle result of query execution and paging


QueryOperationResponse<SalesOrderHeader> response = null;

4. Locate the next comment TODO: Ex3 - Get the first page of SalesOrderHeader records in the
SalesOrders method:
In the task list, double-click the comment TODO: Ex3 - Get the first page of SalesOrderHeader
records.
5. In the SalesOrders method, immediately after the comment, write code that assigns the result of
calling the Execute method on the SalesOrderHeaders entity set to the response variable.
Your code should resemble the following code example.

[Visual Basic]
response = DirectCast(context.SalesOrderHeaders.Execute(),
QueryOperationResponse(Of SalesOrderHeader))

[Visual C#]
response =
context.SalesOrderHeaders.Execute()
as QueryOperationResponse<SalesOrderHeader>;

6. Locate the next comment TODO: Ex3 - Get the next page of SalesOrderHeader records in the
SalesOrders method:
Lab Answer Key: Querying Data by Using WCF Data Services 13

In the task list, double-click the comment TODO: Ex3 - Get the next page of
SalesOrderHeader records.
7. In the SalesOrders method, immediately after the comment, write code that assigns the result of
calling the Execute method on the SalesOrderHeader entity set to the response variable. Use the
value of the next (_next in Visual Basic) variable to instantiate a Uri object to pass as a parameter to
the Execute method.
Your code should resemble the following code example.

[Visual Basic]
response = DirectCast(context.Execute(Of SalesOrderHeader)(New Uri(_next)),
QueryOperationResponse(Of SalesOrderHeader))

[Visual C#]
response =
context.Execute<SalesOrderHeader>(new Uri(next))
as QueryOperationResponse<SalesOrderHeader>;

8. Save the HomeController file:


a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 4: Build and test the data service and the client Web site
1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Check that the ShippingDetailsSite project is set as the StartUp project:
In Solution Explorer, if the ShippingDetailsSite project is not highlighted in bold text, right-click
the ShippingDetailsSite project, and then click Set as StartUp Project.
3. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
4. Test the application by using the three sets of credentials in the following table.

User name Password Role

Bill Pa$$w0rd USShipping

James Pa$$w0rd None

Mary Pa$$w0rd WorldwideShipping

5. Close Internet Explorer.


6. Close the solution:
On the File menu, click Close Solution.
14 Lab Answer Key: Querying Data by Using WCF Data Services

Exercise 4: Implementing a Business Operation in a WCF Data Service


Task 1: Open the starter project
1. In the E:\Labfiles\Lab12\VB\Ex4\Starter or E:\Labfiles\Lab12\CS\Ex4\Starter folder, run ExSetup.bat as
an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, double-click Lab12, double-click VB or CS,
double-click Ex4, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex4\Starter or
E:\Labfiles\Lab12\CS\Ex4\Starter folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. In the Open Project dialog box, move to the E:\Labfiles\Lab12\VB\Ex4\Starter or
E:\Labfiles\Lab12\CS\Ex4\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.

Task 2: Add a business operation to archive records in the data service


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the ShippingDataService.svc file by double-clicking the comment TODO: Ex4 - Configure
Service Operation in the task list. This task is located in the InitializeService method:
In the task list, double-click the comment TODO: Ex4 - Configure Service Operation.
3. In the InitializeService method, immediately after the comment, write code that calls the
SetServiceOperationAccessRule method of the config object and gives full permissions to the
ArchiveSalesOrders function.
Your code should resemble the following code example.

[Visual Basic]
config.SetServiceOperationAccessRule("ArchiveSalesOrders",
ServiceOperationRights.All)

[Visual C#]
config.SetServiceOperationAccessRule("ArchiveSalesOrders",
ServiceOperationRights.All);

4. Open the ShippingDataService.svc file by double-clicking the comment TODO: Ex4 - Define the
ArchiveSalesOrders operation in the task list. This task is located in the ArchiveSalesOrders
method:
In the task list, double-click the comment TODO: Ex4 - Define the ArchiveSalesOrders
operation.
5. Write code that performs the following tasks:
Lab Answer Key: Querying Data by Using WCF Data Services 15

a. Check the role membership of the current user. If the user is not in the WorldwideShipping role,
throw an UnauthorizedAccessException exception.
b. Calculate the archive date. This should be 365 days ago.
c. Define a LINQ query that selects all of the SalesOrderHeader entities that are older than 365
days and have a Status property equal to 8.
d. For each of the SalesOrderHeader entities that the query selects, set the Status property to 8,
and then create a new ArchivedSalesOrderHeader entity.
e. Save the changes.

Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' A business operation only availably to users in the "WorldWideShipping" role
''' Used to archive orders shipped over a year ago to the ArchivedSaleOrderHeader table
''' </summary>
<WebGet()> _
Public Sub ArchiveSalesOrders()

' Check the role membership.


If Not HttpContext.Current.User.IsInRole("WorldwideShipping") Then

Throw New UnauthorizedAccessException("You are not permitted to archive the


data")

End If

' Calculate the Archive date.


Dim archiveDate As DateTime = DateTime.Now.Subtract(
New TimeSpan(365, 0, 0, 0, 0))

' Define a query to select the records to archive.


Dim oldOrders = From old In
Me.CurrentDataSource.SalesOrderHeaders
Where old.ShipDate <= archiveDate And old.Status <> 8
Select old

' Perform the Archive operation.


For Each h As SalesOrderHeader In oldOrders

' Set the status to 8 to indicate archived status.


h.Status = 8

Me.CurrentDataSource.ArchivedSalesOrderHeaders.AddObject(
ArchivedSalesOrderHeader.CreateArchivedSalesOrderHeader(
h.SalesOrderID, h.SalesOrderNumber, h.CustomerID,
h.ContactID, h.ShipMethodID))

Next

' Save the changes - exceptions will go back to the client.


Me.CurrentDataSource.SaveChanges()

End Sub

[Visual C#]
/// <summary>
/// A business operation only available to users in the
/// "WorldwideShipping" role.
16 Lab Answer Key: Querying Data by Using WCF Data Services

/// Archives orders shipped over a year ago to the


/// ArchivedSalesOrderHeader table.
/// </summary>
[WebGet]
public void ArchiveSalesOrders()
{
// Check the role membership.
if (!HttpContext.Current.User.IsInRole("WorldwideShipping"))
{
throw new UnauthorizedAccessException("You are not permitted to archive the
data");
}

// Calculate the Archive date.


DateTime archiveDate = DateTime.Now.Subtract(
new TimeSpan(365, 0, 0, 0, 0));

// Define a query to select the records to archive.


var oldOrders = from old in
this.CurrentDataSource.SalesOrderHeaders
where old.ShipDate <= archiveDate && old.Status != 8
select old;

// Perform the Archive operation.

foreach (SalesOrderHeader h in oldOrders)

{
// Set the status to 8 to indicate archived status.
h.Status = 8;

this.CurrentDataSource.ArchivedSalesOrderHeaders.AddObject(
ArchivedSalesOrderHeader.CreateArchivedSalesOrderHeader(
h.SalesOrderID, h.SalesOrderNumber, h.CustomerID,
h.ContactID, h.ShipMethodID));
}

// Save the changes - exceptions will go back to the client.

this.CurrentDataSource.SaveChanges();
}

6. Save the ShippingDataService.svc file:


a. If you are using Visual Basic, on the File menu, click Save ShippingDataService.svc.vb.
b. If you are using Visual C#, on the File menu, click Save ShippingDataService.svc.cs.

Task 3: Call a business operation in the data service from a client Web application
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Ex4 - Define the
ArchiveOrders ActionResult in the task list. This task is located in the ArchiveOrders method:
In the task list, double-click the comment TODO: Ex4 - Define the ArchiveOrders
ActionResult.
3. Write code that performs the following tasks:
a. Create a new AsyncCallback object for the ArchiveCompleteCallback method.
Lab Answer Key: Querying Data by Using WCF Data Services 17

b. Create a new AdventureWorksEntities context object and assign the current network
credentials to the Credentials property.
c. Invoke the business operation asynchronously by using the static archiveUrl property of the
Constants class.
d. Handle any DataServiceQueryException exceptions by throwing a new ApplicationException
exception.
e. Return an ActionResult object by calling the Content method with a message Archive Complete
as a parameter.

Your code should resemble the following code example.

[Visual Basic]

<AcceptVerbs(HttpVerbs.Get)> _
Public Function ArchiveOrders() As ActionResult

Dim callBack As New AsyncCallback(AddressOf ArchiveCompleteCallBack)

context = New AdventureWorksEntities(


New Uri(Constants.archiveUrl))
context.Credentials = CredentialCache.DefaultNetworkCredentials

Try

Dim r = context.BeginExecute(Of
ShippingDataServiceLibrary.ArchivedSalesOrderHeader)(
New Uri(Constants.archiveUrl), callBack, Nothing)

Dim b = context.EndExecute(Of
ShippingDataServiceLibrary.ArchivedSalesOrderHeader)(r)

Catch ex As DataServiceQueryException

Throw New ApplicationException(


"You are not permitted to archive orders", ex)

End Try

' This does not update the ajax control on the index view.
Return Content("Archive Complete")

End Function

[Visual C#]

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult ArchiveOrders()
{
AsyncCallback callBack = new
AsyncCallback(ArchiveCompleteCallBack);
context = new AdventureWorksEntities(
new Uri(Constants.archiveUrl));
context.Credentials = CredentialCache.DefaultNetworkCredentials;

try
{
var r = context.BeginExecute<ArchivedSalesOrderHeader>(
new Uri(Constants.archiveUrl), callBack, null);
18 Lab Answer Key: Querying Data by Using WCF Data Services

var b = context.EndExecute<ArchivedSalesOrderHeader>(r);
}
catch (DataServiceQueryException ex)
{
throw new ApplicationException(
"You are not permitted to archive orders", ex);
}

// This does not update the ajax control on the index view.
return Content("Archive Complete");
}

Note: ASP.NET Model-View-Controller (MVC) 1.0 does not directly support asynchronous action methods.
Therefore, the code should use a blocking call to the EndExecute method instead of using the
ArchiveCompleteCallBack method. ASP.NET MVC 2 will enable you to write real asynchronous action
methods.

4. Save the HomeController file:


a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 4: Build and test the client Web site


1. Build the solution and correct any errors:
On the Build menu, click Build Solution.
2. Check that the ShippingDetailsSite project is set as the StartUp project:
In Solution Explorer, if the ShippingDetailsSite project is not highlighted in bold text, right-click
the ShippingDetailsSite project, and click Set as StartUp Project.
3. Start the application in Debug mode:
a. On the Debug menu, click Start Debugging.
b. In Internet Explorer, in the Windows Security dialog box, in the User Name box, type Mary
c. In the Password box, type Pa$$w0rd and then click OK.
4. Explore the service by using Internet Explorer:
a. On the Adventure Works Delivery Information page, click ArchiveShipping Orders.
b. Wait until the Archive Complete message appears in the browser.
5. Close Internet Explorer.
6. Close the solution:
On the File menu, click Close Solution.
Lab Answer Key: Updating Data by Using WCF Data Services 1

Module 13
Lab Answer Key: Updating Data by Using WCF Data Services
Contents:
Exercise 1: Updating Entities by Using a WCF Data Service 2
Exercise 2: Creating and Deleting Entities by Using a WCF Data Service 6
Exercise 3: Restricting Create, Update, and Delete Requests 17
2 Lab Answer Key: Updating Data by Using WCF Data Services

Lab 13: Updating Data by Using WCF Data


Services
Exercise 1: Updating Entities by Using a WCF Data Service
Task 1: Prepare the environment for the lab
1. Log on to the 10265A-GEN-DEV-13 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), and then double-click Labfiles.
c. Right-click EnvSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running.

Task 2: Prepare the AdventureWorks database for the lab


In the E:\Labfiles folder, run AWReset.bat:
a. In Windows Explorer, double-click AWReset.bat.
b. Wait for the batch file to finish running.

Task 3: Open the starter project


1. In the E:\Labfiles\Lab13\VB\Ex1\Starter or E:\Labfiles\Lab13\CS\Ex1\Starter folder, run ExSetup.bat as
an administrator:
a. In Windows Explorer, double-click Lab13, double-click VB or CS, double-click Ex1, and then
double-click Starter.
b. Right-click ExSetup.bat, and then click Run as administrator.
c. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
d. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open Microsoft Visual Studio 2010 as an administrator:
a. Click Start, point to All Programs, click Microsoft Visual Studio 2010, right-click Microsoft
Visual Studio 2010, and then click Run as administrator.
b. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
3. Open the existing solution, ShippingDataServiceSite.sln, in the E:\Labfiles\Lab13\VB\Ex1\Starter or
E:\Labfiles\Lab13\CS\Ex1\Starter folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab13\VB\Ex1\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.
c. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab13\CS\Ex1\Starter folder, click ShippingDataServiceSite.sln, and then click
Open.
Lab Answer Key: Updating Data by Using WCF Data Services 3

Task 4: Implement a controller action that performs a status update


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.

b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Add action method to
handle status update:
In the task list, double-click the TODO: Add action method to handle status update task.
3. Immediately after the comment, add a new method named HandleStatusUpdate that returns an
ActionResult object and accepts the following parameters:
a. A byte parameter called StatusList.
b. An integer parameter called id.
4. Make the HandleStatusUpdate method public, and then specify that if an exception is thrown, the
Error view should be displayed.
Your code should resemble the following code example.

[Visual Basic]
<HandleError()>
Public Function HandleStatusUpdate(ByVal StatusList As Byte, ByVal id As Integer) As
ActionResult
End Function

[Visual C#]
[HandleError]
public ActionResult HandleStatusUpdate(byte StatusList, int id)
{
}

5. In the HandleStatusUpdate method, write code that performs the following tasks:
a. Assign a new AdventureWorksEntities object to the context variable.
b. Set the Credentials property of the context object to the current default network credentials.
c. Define a Language-Integrated Query (LINQ) query named salesOrderHeaderToChange that
retrieves a single SalesOrderHeader object with a SalesOrderID property that is equal to the
value of the id variable.
Your code should resemble the following code example.

[Visual Basic]
context = New AdventureWorksEntities(New Uri(Constants.serviceUrl))
context.Credentials = CredentialCache.DefaultNetworkCredentials

Dim salesOrderHeaderToChange = (From salesOrderHeader In context.SalesOrderHeaders


Where salesOrderHeader.SalesOrderID = id
Select salesOrderHeader).Single()

[Visual C#]
context = new AdventureWorksEntities(new Uri(Constants.serviceUrl));
context.Credentials = CredentialCache.DefaultNetworkCredentials;

var salesOrderHeaderToChange = (from salesOrderHeader in context.SalesOrderHeaders


4 Lab Answer Key: Updating Data by Using WCF Data Services

where salesOrderHeader.SalesOrderID == id
select salesOrderHeader).Single();

6. In the HandleStatusUpdate method, write code that performs the following tasks:
a. Set the Status property of the salesOrderHeaderToChange object to the value of the StatusList
variable.
b. Call the UpdateObject method of the context object, passing the salesOrderHeaderToChange
object as a parameter.
c. Handle any DataServiceRequestException exceptions by throwing a new
ApplicationException exception.
d. Save the changes.
e. Return an ActionResult object by calling the RedirectToAction method with "Details/" + id
("Details/" & id in Visual Basic) values as a parameter.
Your code should resemble the following code example.

[Visual Basic]
<HandleError()>
Public Function HandleStatusUpdate(ByVal StatusList As Byte, ByVal id As Integer) As
ActionResult

context = New AdventureWorksEntities(New Uri(Constants.serviceUrl))


context.Credentials = CredentialCache.DefaultNetworkCredentials

Dim salesOrderHeaderToChange = (From salesOrderHeader In context.SalesOrderHeaders


Where salesOrderHeader.SalesOrderID = id
Select salesOrderHeader).Single()

salesOrderHeaderToChange.Status = StatusList
Try

context.UpdateObject(salesOrderHeaderToChange)
context.SaveChanges()

Catch ex As DataServiceRequestException

Throw New ApplicationException("An error occurred saving theStatus Update", ex)

End Try

Return RedirectToAction("Details/" & id)

End Function

[Visual C#]
[HandleError]
public ActionResult HandleStatusUpdate(byte StatusList, int id)
{
context = new AdventureWorksEntities(
new Uri(Constants.serviceUrl));
context.Credentials = CredentialCache.DefaultNetworkCredentials;

var salesOrderHeaderToChange = (from salesOrderHeader in


context.SalesOrderHeaders
where salesOrderHeader.SalesOrderID == id
select salesOrderHeader).Single();

salesOrderHeaderToChange.Status = StatusList;
Lab Answer Key: Updating Data by Using WCF Data Services 5

try
{
context.UpdateObject(salesOrderHeaderToChange);
context.SaveChanges();
}
catch (DataServiceRequestException ex)
{
throw new ApplicationException(
"An error occurred saving theStatus Update", ex);
}

return RedirectToAction("Details/" + id);


}

7. Save the HomeController file:

a. If you are using Visual Basic, on the File menu, click Save Controllers\HomeController.vb.
b. If you are using Visual C#, on the File menu, click Save Controllers\HomeController.cs.

Task 5: Enable modifications on the SalesOrderHeaders entity set


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the ShippingDataService file by double-clicking the TODO: Allow changes to
SalesOrderHeaders task in the task list. This task is located in the InitializeService method:
In the task list, double-click the TODO: Allow changes to SalesOrderHeaders task.
3. Immediately after the comment, modify the code that sets the access rule for the SalesOrderHeaders
entity to allow WriteReplace access and WriteMerge access.
Your code should resemble the following code example.

[Visual Basic]
...
config.SetEntitySetAccessRule("SalesOrderHeaders", (EntitySetRights.AllRead Or
EntitySetRights.WriteReplace Or EntitySetRights.WriteMerge))
...

[Visual C#]
...
config.SetEntitySetAccessRule("SalesOrderHeaders",(EntitySetRights.AllRead |
EntitySetRights.WriteReplace | EntitySetRights.WriteMerge));
...

4. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 6: Test the ShippingDetailsSite Web application


1. Check that the ShippingDetailsSite project is set as the startup project:
In Solution Explorer, if the ShippingDetailsSite project is not highlighted in bold text, right-click
the ShippingDetailsSite project, and then click Set as StartUp Project.
2. Browse to the ShippingDetailsSite Web site by using Windows Internet Explorer:
6 Lab Answer Key: Updating Data by Using WCF Data Services

a. On the Debug menu, click Start Without Debugging.


b. In the Windows Security dialog box, in the User name box, type Bill and in the Password box,
type Pa$$w0rd and then click OK.
3. Explore the service by using Internet Explorer:
a. On the Adventure Works Delivery Information page, click Sales Orders.
b. On the SalesOrders page, click any of the Details hyperlinks.
c. On the Details page, change the value in the Status box, and then click Submit.
d. On the Details page, click Home.
e. On the Adventure Works Delivery Information page, click Sales Orders. Notice the updated
Status value.
4. Close Internet Explorer.
5. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Creating and Deleting Entities by Using a WCF Data Service


Task 1: Open the starter project
1. In the E:\Labfiles\Lab13\VB\Ex2\Starter or E:\Labfiles\Lab13\CS\Ex2\Starter folder, run ExSetup.bat as
an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, double-click Lab13, double-click CS or VB,
double-click Ex2, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
2. Open the existing solution, ContactManagement.sln, in the
E:\Labfiles\Lab13\VB\Ex2\Starter\ContactManagement or
E:\Labfiles\Lab13\CS\Ex2\Starter\ContactManagement folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab13\VB\Ex2\Starter\ContactManagement folder, click
ContactManagement.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab13\CS\Ex2\Starter\ContactManagement folder, click
ContactManagement.sln, and then click Open.

Task 2: Connect to the ShippingDataService data service


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the MainWindow file by double-clicking the TODO: Add the namespaces containing the
types required to use a WCF Data Service task in the task list:
In the task list, double-click the TODO: Add the namespaces containing the types required to
use a WCF Data Service task.
Lab Answer Key: Updating Data by Using WCF Data Services 7

3. In the Code Editor window, bring the System.Data.Services.Client and System.Net namespaces into
scope.
Your code should resemble the following code example.

[Visual Basic]
Imports System.Data.Services.Client
Imports System.Net

[Visual C#]
using System.Data.Services.Client;
using System.Net;

4. Locate the next comment in the MainWindow file by double-clicking the TODO: Add the
namespace containing the types for the ShippingDataService task in the task list:
In the task list, double-click the TODO: Add the namespace containing the types for the
ShippingDataService task.
5. Immediately after the comment, add a statement to bring the
ContactManagement.ShippingDataService namespace into scope.
Your code should resemble the following code example.

[Visual Basic]
Imports ContactManagement.ShippingDataService

[Visual C#]
using ContactManagement.ShippingDataService;

6. Locate the next comment in the MainWindow file by double-clicking the TODO: Define a
DataServiceCollection for holding Contact data task in the task list:
In the task list, double-click the TODO: Define a DataServiceCollection for holding Contact
data task.
7. Immediately after the comment, write code that declares a private field called contactInfo based on
the DataServiceCollection generic type. Specify Contact as the type parameter for the
DataServiceCollection type, and assign it the value null (Nothing in Visual Basic).
Your code should resemble the following code example.

[Visual Basic]
private contactInfo As DataServiceCollection(Of Contact) = Nothing

[Visual C#]
private DataServiceCollection<Contact> contactInfo = null;

8. Locate the next comment in the MainWindow file by double-clicking the TODO: Define an
AdventureWorksEntities context for accessing the service task in the task list:
In the task list, double-click the TODO: Define an AdventureWorksEntities context for
accessing the service task.
8 Lab Answer Key: Updating Data by Using WCF Data Services

9. Immediately after the comment, write code that declares a variable called context of type
AdventureWorksEntities and assign it the value null.
Your code should resemble the following code example.

[Visual Basic]

Dim context As AdventureWorksEntities = Nothing

[Visual C#]

AdventureWorksEntities context = null;

10. Locate the next comment in the MainWindow file by double-clicking the TODO: Connect to the
ShippingDataService task in the task list:
In the task list, double-click the TODO: Connect to the ShippingDataService task.
11. Immediately after the comment, write code that assigns the value that is returned by calling the
ConnectToContext method to the context variable.
Your code should resemble the following code example.

[Visual Basic]
context = Me.ConnectToContext()

[Visual C#]
context = this.ConnectToContext();

12. Locate the next comment in the MainWindow file by double-clicking the TODO: Connect to the
ShippingDataService task in the task list:
In the task list, double-click the TODO: Connect to the ShippingDataService task.
13. Immediately after the comment, write code that creates a new AdventureWorksEntities object
named context.
Your code should resemble the following code example.

[Visual Basic]

Dim context As New AdventureWorksEntities(New Uri(Constants.serviceUrl))

[Visual C#]
AdventureWorksEntities context = new AdventureWorksEntities(new
Uri(Constants.serviceUrl));

14. Locate the next comment in the MainWindow file by double-clicking the TODO: Set the credentials
for accessing the service task in the task list:
In the task list, double-click the TODO: Set the credentials for accessing the service task.
15. Immediately after the comment, write code to set the Credentials property of the context object to
the current default network credentials.
Lab Answer Key: Updating Data by Using WCF Data Services 9

Your code should resemble the following code example.

[Visual Basic]
context.Credentials = CredentialCache.DefaultCredentials

[Visual C#]
context.Credentials = CredentialCache.DefaultCredentials;

16. Locate the next comment in the MainWindow file by double-clicking the TODO: Return the
connected context task in the task list:
In the task list, double-click the TODO: Return the connected context task.
17. Immediately after the comment, write code to return the context variable.
Your code should resemble the following code example.

[Visual Basic]
Return context

[Visual C#]
return context;

18. Save the MainWindow file:


a. If you are using Visual Basic, on the File menu, click Save MainWindow.xaml.vb.
b. If you are using Visual C#, on the File menu, click Save MainWindow.xaml.cs.

Task 3: Call the data service to retrieve contacts in a specified range


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Fetch all contacts
in the range specified by the user on the form task in the task list. This task is located in the
getContacts_Click method:
In the task list, double-click the TODO: Fetch all contacts in the range specified by the user
on the form task.
3. In the getContacts_Click method, immediately after the comment, write code that performs the
following tasks:
a. Assign the value of the contactIDFrom.Text property to an integer variable called lowerBound.
b. Assign the value of the contactIDTo.Text property to an integer variable called upperBound.
c. Create a DataServiceCollection object that the current context tracks, and assign it to the
contactInfo variable by defining a LINQ query that retrieves an enumerable collection of Contact
objects with a ContactID property greater than or equal to the value of the lowerBound variable
and less than or equal to the upperBound variable.
d. Define a new ObservableCollection object of type Contact by using the contactInfo property
as a parameter, and assign it to the this.contactsGrid.DataContext property
(Me.contactsGrid.DataContext property in Visual Basic).
10 Lab Answer Key: Updating Data by Using WCF Data Services

e. Handle any DataServiceQueryException exceptions or Exception exceptions by displaying a


message box with the following text: Error Fetching Contacts.
Your code should resemble the following code example.

[Visual Basic]
Private Sub getContacts_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)

Try

Dim lowerBound As Integer = Int32.Parse(contactIDFrom.Text)


Dim upperBound As Integer = Int32.Parse(contactIDTo.Text)
contactInfo =
New DataServiceCollection(Of Contact) _
(From contact In context.Contacts Where
contact.ContactID >= lowerBound And
contact.ContactID <= upperBound
Select contact)

Me.contactsGrid.DataContext =
New ObservableCollection(Of Contact)(contactInfo)

Catch dsqEx As DataServiceQueryException

MessageBox.Show(dsqEx.InnerException.Message,
"Error Fetching Contacts", MessageBoxButton.OK,
MessageBoxImage.Error)

Catch ex As Exception

MessageBox.Show(ex.Message, "Error Fetching Contacts",


MessageBoxButton.OK, MessageBoxImage.Error)

End Try

End Sub

[Visual C#]
private void getContacts_Click(object sender, RoutedEventArgs e)
{
try
{
int lowerBound = Int32.Parse(contactIDFrom.Text);
int upperBound = Int32.Parse(contactIDTo.Text);
contactInfo = new DataServiceCollection<Contact>
(from contact in context.Contacts where
contact.ContactID >= lowerBound &&
contact.ContactID <= upperBound
select contact, TrackingMode.AutoChangeTracking);
this.contactsGrid.DataContext =
new ObservableCollection<Contact>(contactInfo);
}
catch (DataServiceQueryException dsqEx)
{
MessageBox.Show(dsqEx.InnerException.Message,
"Error Fetching Contacts", MessageBoxButton.OK,
MessageBoxImage.Error);
}
catch (Exception ex)
{
Lab Answer Key: Updating Data by Using WCF Data Services 11

MessageBox.Show(ex.Message,
"Error Fetching Contacts", MessageBoxButton.OK,
MessageBoxImage.Error);
}
}

4. Save the MainWindow file:


a. If you are using Visual Basic, on the File menu, click Save MainWindow.xaml.vb.
b. If you are using Visual C#, on the File menu, click Save MainWindow.xaml.cs.

Task 4: Call the data service to retrieve the next page of contact results
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Retrieve the next
page of Contact information and display it task in the task list. This task is located in the
moreContacts_Click method:
In the task list, double-click the TODO: Retrieve the next page of Contact information and
display it task.
3. In the moreContacts_Click method, immediately after the comment, write code that performs the
following tasks:
a. If the contactInfo.Continuation property is not null (Nothing in Visual Basic), create an
enumerable collection of Contact objects by calling the Execute method of the context object,
passing the contactInfo.Continuation.NextLinkUri property as a parameter. Pass this collection
to the Load method of the contactInfo class.
b. Define a new property based on the ObservableCollection generic type, specifying Contact as
the type parameter. Pass the contactInfo property as a parameter, and assign this object to the
current contactsGrid.DataContext property.
c. Otherwise display a message box with the following text: No More Contacts.
d. Handle any DataServiceQueryException exceptions or Exception exceptions by displaying a
message box with the following text: Error Fetching Next Page of Contacts.

Your code should resemble the following code example.

[Visual Basic]
Private Sub moreContacts_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Try
If contactInfo IsNot Nothing Then
If contactInfo.Continuation IsNot Nothing Then

contactInfo.Load(context.Execute(Of
Contact)(contactInfo.Continuation.NextLinkUri))
Me.contactsGrid.DataContext = New ObservableCollection(Of
Contact)(contactInfo)

Else
MessageBox.Show("No More Contacts", "No More Contacts",
MessageBoxButton.OK, MessageBoxImage.Information)

End If
End If
Catch dsqEx As DataServiceQueryException
12 Lab Answer Key: Updating Data by Using WCF Data Services

MessageBox.Show(dsqEx.InnerException.Message, "Error Fetching Next Page of


Contacts", MessageBoxButton.OK, MessageBoxImage.Error)
Catch ex As Exception
MessageBox.Show(ex.Message, "Error Fetching Next Page of Contacts",
MessageBoxButton.OK, MessageBoxImage.Error)

End Try

End Sub

[Visual C#]
private void moreContacts_Click(object sender, RoutedEventArgs e)
{
try
{
if (contactInfo != null)
{

if (contactInfo.Continuation != null)
{
contactInfo.Load(context.Execute<Contact>(
contactInfo.Continuation.NextLinkUri));
this.contactsGrid.DataContext = new
ObservableCollection<Contact>(contactInfo);
}
else
{
MessageBox.Show("No More Contacts",
"No More Contacts", MessageBoxButton.OK,
MessageBoxImage.Information);
}
}
}

catch (DataServiceQueryException dsqEx)


{
MessageBox.Show(dsqEx.InnerException.Message,
"Error Fetching Next Page of Contacts",
MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,
"Error Fetching Next Page of Contacts",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}

4. Save the MainWindow file:


a. If you are using Visual Basic, on the File menu, click Save MainWindow.xaml.vb.
b. If you are using Visual C#, on the File menu, click Save MainWindow.xaml.cs.

Task 5: Implement the method that adds, deletes, and updates contacts
1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
Lab Answer Key: Updating Data by Using WCF Data Services 13

2. Locate the next comment in the MainWindow file by double-clicking the TODO: Add the new
Contact to the context task in the task list. This task is located in the
contactsGrid_PreviewKeyDown method:
In the task list, double-click the TODO: Add the new Contact to the context task.
3. In the contactsGrid_PreviewKeyDown method, immediately after the comment, write code that
performs the following tasks:
a. If the newContact object is not null (Nothing in Visual Basic), add it to the context object by
using the AddToContacts method.
b. Insert the newContact object into the displayedContacts collection at the position that the
contactsGrid.SelectedIndex property specifies.
Your code should resemble the following code example.

[Visual Basic]
If newContact IsNot Nothing Then

context.AddToContacts(newContact)
displayedContacts.Insert(contactsGrid.SelectedIndex, newContact)

End If

[Visual C#]
if (newContact != null)
{
context.AddToContacts(newContact);
displayedContacts.Insert(contactsGrid.SelectedIndex,newContact);
}

4. Locate the next comment in the MainWindow file by double-clicking the TODO: Remove the
Contact from the context task in the task list:
In the task list, double-click the TODO: Remove the Contact from the context task.
5. Immediately after the comment, write code to perform the following tasks:
a. Remove the currentContact object from the context.
b. Remove the currentContact object from the displayedContacts collection.
Your code should resemble the following code example.

[Visual Basic]
context.DeleteObject(currentContact)
displayedContacts.Remove(currentContact)

[Visual C#]
context.DeleteObject(currentContact);
displayedContacts.Remove(currentContact);

6. Locate the next comment in the MainWindow file by double-clicking the TODO: Update the
Contact object in the context task in the task list:
In the task list, double-click the TODO: Update the Contact object in the context task.
7. Immediately after the comment, write code to update the currentContact object by calling the
UpdateObject method of the context object.
14 Lab Answer Key: Updating Data by Using WCF Data Services

Your code should resemble the following code example.

[Visual Basic]
...
context.UpdateObject(currentContact)
...

[Visual C#]
...
context.UpdateObject(currentContact);
...

8. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 6: Save the changes to the data context


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Send all changes as
a single batch task in the task list. This task is located in the saveChanges_Click method:
In the task list, double-click the TODO: Send all changes as a single batch task.
3. Immediately after the comment, write code to perform the following tasks:
a. Define a DataServiceResponse object called result and assign this the value that is returned by
calling the SaveChanges method of the context object, specifying the
SaveChangesOptions.Batch enumeration as a parameter.
b. Display a message box specifying Changes Saved as the text.
c. Handle any DataServiceRequestException exceptions by displaying a message box with the
following text: Error Saving Changes.
Your code should resemble the following code example.

[Visual Basic]
Private Sub saveChanges_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)

Try

If context IsNot Nothing Then

Dim result As DataServiceResponse =


context.SaveChanges(SaveChangesOptions.Batch)
MessageBox.Show("Changes Saved", "Changes Saved", MessageBoxButton.OK,
MessageBoxImage.Information)

End If

Catch dsrEx As DataServiceRequestException

MessageBox.Show(dsrEx.InnerException.Message, "Error Saving Changes",


MessageBoxButton.OK, MessageBoxImage.Error)
Catch ex As Exception
Lab Answer Key: Updating Data by Using WCF Data Services 15

MessageBox.Show(ex.Message, "Error Saving Changes", MessageBoxButton.OK,


MessageBoxImage.Error)

End Try

End Sub

[Visual C#]
private void saveChanges_Click(object sender, RoutedEventArgs e)
{
try
{
if (context != null)
{
DataServiceResponse result =
context.SaveChanges(SaveChangesOptions.Batch);
MessageBox.Show("Changes Saved", "Changes Saved",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}

catch (DataServiceRequestException dsrEx)


{
MessageBox.Show(dsrEx.InnerException.Message,
"Error Saving Changes",
MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Saving Changes",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}

4. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 7: Discard the changes to the data context


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the next comment in the MainWindow file by double-clicking the TODO: Clear the context
and fetch the Contact data from the Data Service again task in the task list. This task is located in
the discardChanges_Click method:
In the task list, double-click the TODO: Clear the context and fetch the Contact data from the
Data Service again task.
3. Immediately after the comment, write code to perform the following tasks:
a. Assign the value that is returned by calling the ConnectToContext method to the context
parameter.
b. Reload the contacts by raising the ClickEvent event object of the getContacts button.
Your code should resemble the following code example.

[Visual Basic]
16 Lab Answer Key: Updating Data by Using WCF Data Services

Private Sub discardChanges_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)

context = Me.ConnectToContext()
Me.getContacts.RaiseEvent(New RoutedEventArgs(Button.ClickEvent, Me))

End Sub

[Visual C#]
private void discardChanges_Click(object sender, RoutedEventArgs e)
{
context = this.ConnectToContext();
this.getContacts.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
}

4. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 8: Enable write access on the Contacts entity set


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the ShippingDataService file by double-clicking the TODO: Enable Write access on the
Contacts entity set task in the task list. This task is located in the InitializeService method:
In the task list, double-click the TODO: Enable Write access on the Contacts entity set task.
3. Immediately after the comment, modify the code that sets the access rule for the Contacts entity to
allow AllWrite access and AllRead access.
Your code should resemble the following code example.

[Visual Basic]
...
config.SetEntitySetAccessRule("Contacts", (EntitySetRights.AllWrite Or
EntitySetRights.AllRead))
...

[Visual C#]
...
config.SetEntitySetAccessRule("Contacts",(EntitySetRights.AllWrite |
EntitySetRights.AllRead));
...

4. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 9: Test the ContactManagement application


1. Check that the ContactManagement application is set as the startup project:
In Solution Explorer, if the ContactManagement project is not highlighted in bold text, right-click
the ContactManagement application, and then click Set as StartUp Project.
2. Start the application in Debug mode:
Lab Answer Key: Updating Data by Using WCF Data Services 17

On the Debug menu, click Start Debugging.


3. In the Contact Management window, next to the Get Contacts button, in the box, type 1
4. Press the TAB key, type 100 and then click Get Contacts.
5. Click More to load more contacts. Repeat this until the No More Contacts message is displayed.
6. In the No More Contacts dialog box, click OK.
7. Edit an existing contact:
a. Click a contact, and then press ENTER.
b. In the Edit Contact dialog box, modify the details of the contact, and then click OK.
c. In the Contact Management window, notice the updated contact details.
8. Insert a new contact:
a. Click a contact, and then press INSERT.
b. In the New Contact dialog box, type some details for the contact, and then click OK.
9. Commit the changes to the database:
a. In the Contact Management window, click Save.
b. In the Changes Saved dialog box, click OK.
10. Delete a contact:
a. Click the contact that you added previously, and then press DELETE.
b. In the Delete Contact dialog box, click Yes.
11. Commit the changes to the database:
a. In the Contact Management window, click Save.
b. In the Changes Saved dialog box, click OK.
12. Discard the changes and reload the contact data from the database:
In the Contact Management window, click Discard.
13. Close the ContactManagement application.
14. Close the solution:
On the File menu, click Close Solution.

Exercise 3: Restricting Create, Update, and Delete Requests


Task 1: Open the starter project
1. In the E:\Labfiles\Lab13\VB\Ex3\Starter or E:\Labfiles\Lab13\CS\Ex3\Starter folder, run ExSetup.bat as
an administrator:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, double-click Lab13, double-click VB or CS,
double-click Ex3, and then double-click Starter.
c. Right-click ExSetup.bat, and then click Run as administrator.
d. In the User Account Control dialog box, in the Password box, type Pa$$w0rd and then click
Yes.
e. Wait for the batch file to finish running, and then close Windows Explorer.
18 Lab Answer Key: Updating Data by Using WCF Data Services

2. Open the existing solution, ContactManagement.sln, in the


E:\Labfiles\Lab13\VB\Ex3\Starter\ContactManagement or
E:\Labfiles\Lab13\CS\Ex3\Starter\ContactManagement folder:
a. In Visual Studio, on the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab13\VB\Ex3\Starter\ContactManagement folder, click
ContactManagement.sln, and then click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab13\CS\Ex3\Starter\ContactManagement folder, click
ContactManagement.sln, and then click Open.

Task 2: Set the credentials for accessing the ShippingData service


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the MainWindow file by double-clicking the TODO: Set the credentials for accessing the
service using the username and password specified on the form task in the task list. This task is
located in the ConnectToContext method:
In the task list, double-click the TODO: Set the credentials for accessing the service using the
username and password specified on the form task.
3. In the ConnectToContext method, immediately after the comment, create a new
NetworkCredentials object by using the userName.Text and password.Password properties as
parameters. Assign this object to the Credentials property of the context object.
Your code should resemble the following code example.

[Visual Basic]
context.Credentials = new NetworkCredential(Me.userName.Text, Me.password.Password)

[Visual C#]
context.Credentials = new NetworkCredential(this.userName.Text, this.password.Password);

4. Save the MainWindow file:


a. If you are using Visual Basic, on the File menu, click Save MainWindow.xaml.vb.
b. If you are using Visual C#, on the File menu, click Save MainWindow.xaml.cs.
5. Build the solution and correct any errors:
On the Build menu, click Build Solution. Correct any errors.

Task 3: Define the change interceptors


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the ShippingDataService file by double-clicking the TODO: Define the change interceptor
task in the task list:
In the task list, double-click the TODO: Define the change interceptor task.
Lab Answer Key: Updating Data by Using WCF Data Services 19

3. In the ShippingDataService class, immediately after the comment, write code that defines a new
Change Interceptor method for the Contacts entity called OnChangeContacts.
Your code should resemble the following code example.

[Visual Basic]
<ChangeInterceptor("Contacts")>
Public Sub OnChangeContacts(ByVal contact As Contact, ByVal operations As
UpdateOperations)

End Sub

[Visual C#]
[ChangeInterceptor("Contacts")]
public void OnChangeContacts(Contact contact, UpdateOperations operations)
{

4. In the OnChangeContacts method, write code that throws a DataServiceException exception if the
current user is not a member of the AdventureWorksEmployees group. Return a status code of 403
and a status message of "Cannot add, modify or delete contacts".
Your code should resemble the following code example.

[Visual Basic]
If Not HttpContext.Current.User.IsInRole("AdventureWorksEmployees")

Then

Throw New DataServiceException(403, "Cannot add, modify or delete contacts")

End If

[Visual C#]

if(!HttpContext.Current.User.IsInRole("AdventureWorksEmployees"))
{
throw new DataServiceException(403,
"Cannot add, modify or delete contacts");
}

5. In the OnChangeContacts method, write code that performs the following tasks only when a Change
update operation is performed:
a. Define a LINQ query named oldemail that queries a new AdventureWorksEntities object and
retrieves the string value from the EmailAddress property of the contact that the contact object
specifies.
b. If the EmailAddress property of the contact object is not null and the value of the oldEmail
object is null, set the value of the contact object's EmailPromotion property to 1.
c. If the value of the contact object's EmailAddress property is null, set the contact object's
EmailPromotion property to 0.
Your code should resemble the following code example.

[Visual Basic]
20 Lab Answer Key: Updating Data by Using WCF Data Services

If operations = UpdateOperations.Change Then

Dim oldeMail As String = (From old In New AdventureWorksEntities().Contacts


Where old.ContactID = contact.ContactID
Select old).First().EmailAddress.ToString()

If String.IsNullOrWhiteSpace(oldeMail) And Not


String.IsNullOrWhiteSpace(contact.EmailAddress) Then

contact.EmailPromotion = 1
End If

If String.IsNullOrWhiteSpace(contact.EmailAddress) Then

contact.EmailPromotion = 0
End If

End If

[Visual C#]
if (operations == UpdateOperations.Change)
{
string oldeMail = (
from old in new AdventureWorksEntities().Contacts
where old.ContactID == contact.ContactID
select old).First().EmailAddress.ToString();

if (string.IsNullOrWhiteSpace(oldeMail) &&
!String.IsNullOrWhiteSpace(contact.EmailAddress))
{
contact.EmailPromotion = 1;
}
if (String.IsNullOrWhiteSpace(contact.EmailAddress))
{
contact.EmailPromotion = 0;
}
}

6. Build the solution and correct any errors:


On the Build menu, click Build Solution. Correct any errors.

Task 4: Test the ContactManagement application


1. Check that the ContactManagement application is set as the startup project:
In Solution Explorer, if the ContactManagement project is not highlighted in bold text, right-click
the ContactManagement application, and then click Set as StartUp Project.
2. Start the application in Debug mode:
On the Debug menu, click Start Debugging.
3. In the Contact Management window, next to the Get Contacts button, in the box, type 1
4. Press the TAB key, type 100 and then click Get Contacts.
5. In the Error Fetching Contacts dialog box, click Close.
6. In the Contact Management window, in the User Name box, type Mary
7. In the Password box, type Pa$$w0rd
Lab Answer Key: Updating Data by Using WCF Data Services 21

8. Click Get Contacts.


9. Click More to load more contacts. Repeat this until the No More Contacts message is displayed.
10. In the No More Contacts dialog box, click OK.
11. Edit an existing contact:
a. Click a contact, and then press ENTER.
b. In the Edit Contact dialog box, modify the details of the contact, and then click OK.
c. In the Contact Management window, notice the updated contact details.
12. Insert a new contact:
a. Click a contact, and then press INSERT.
b. In the New Contact dialog box, type some details for the contact, and then click OK.
13. Commit the changes to the database:
a. In the Contact Management window, click Save.
b. In the Changes Saved dialog box, click OK.
14. Delete a contact:
a. Select a contact, and then press DELETE.
b. In the Delete Contact dialog box, click Yes.
15. Commit the changes to the database:
a. In the Contact Management window, click Save.
b. In the Changes Saved dialog box, click OK.
16. Edit an existing contact:
a. Click a contact, and then press ENTER.
b. In the Edit Contact dialog box, modify the details of the contact, and then click OK.
c. In the Contact Management window, notice the updated contact details.
17. Discard the changes and reload the contact data from the database:
In the Contact Management window, click Discard.
18. Close the ContactManagement application.
19. Close the solution, and then close Visual Studio:
a. On the File menu, click Close Solution.
b. On the File menu, click Exit.
Lab Answer Key: Using ADO.NET 1

Module 14
Lab Answer Key: Using ADO.NET
Contents:
Exercise 1: Using ADO.NET to Retrieve Read-Only Information Quickly and
Perform Simple Data Modifications 2
Exercise 2: Developing the Product List Web Application 29
Exercise 3: Enabling Data Modifications 36
2 Lab Answer Key: Using ADO.NET

Lab 14: Using ADO.NET


Exercise 1: Using ADO.NET to Retrieve Read-Only Information Quickly and
Perform Simple Data Modifications
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-14 virtual machine as Student with the password Pa$$w0rd.
2. In the E:\Labfiles folder, run AWReset.bat:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the solution, Using ADO.NET.sln, in the E:\Labfiles\Lab14\VB\Ex1\Starter\Using ADO.NET or
E:\Labfiles\Lab14\CS\Ex1\Starter\Using ADO.NET folder:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab14\VB\Ex1\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and then
click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab14\CS\Ex1\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and then
click Open.

Task 3: Create the ProductDataAccessLayer class


1. Review the IProductDataAccessLayer interface.
This is the interface that the ProductDataAccess class will implement. It defines the methods shown
in the following code example.
If you are using Microsoft Visual Basic, in Solution Explorer, double-click
IProductDataAccessLayer.vb.
If you are using Microsoft Visual C#, in Solution Explorer, double-click
IProductDataAccessLayer.cs.

[Visual Basic]
Interface IProductDataAccessLayer
''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <returns>An IEnumerable collection of
''' ProductDataObjects</returns>
Function GetProductList() As List(Of ProductDataObject)

''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <param name="color">Only return products that match this
Lab Answer Key: Using ADO.NET 3

''' color</param>
''' <returns></returns>
Function GetProductList(ByVal color As String) As List(Of
ProductDataObject)

''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <param name="maxListPrice">Only return products where the
''' ListPrice is equal to or less that maxListPrice</param>
''' <returns></returns>
Function GetProductList(ByVal maxListPrice As Decimal) As List(Of
ProductDataObject)

''' <summary>
''' Get a single ProductDataObject by providing the ProductID
''' </summary>
''' <param name="productID">The Int ProductID of the
''' ProductDataObject to return</param>
''' <returns>A ProductDataObject</returns>
Function GetProduct(ByVal productID As Integer) As
ProductDataObject

''' <summary>
''' Updates a ProductDataObject in the Database
''' </summary>
''' <param name="product">The ProductDataObject to update</param>
Function UpdateProduct(ByVal product As ProductDataObject) As
Boolean

''' <summary>
''' Deletes a single Product from the Database
''' </summary>
''' <param name="product">The ProjectDataObject to delete</param>
Function DeleteProduct(ByVal product As ProductDataObject) As
Boolean

End Interface

[Visual C#]
interface IProductDataAccessLayer
{
/// <summary>
/// Get an Enumerable collection of ProductDataObjects.
/// </summary>
/// <returns>An IEnumerable collection of
/// ProductDataObjects.</returns>
List<ProductDataObject> GetProductList();

/// <summary>
/// Get an Enumerable collection of ProductDataObjects.
/// </summary>
/// <param name="color">Only return products that match this
/// color.</param>
/// <returns></returns>
List<ProductDataObject> GetProductList(string color);

/// <summary>
/// Get an Enumerable collection of ProductDataObjects.
/// </summary>
4 Lab Answer Key: Using ADO.NET

/// <param name="maxListPrice">Only return products where the


/// ListPrice is equal to or less that maxListPrice.</param>
/// <returns></returns>
List<ProductDataObject> GetProductList(decimal maxListPrice);

/// <summary>
/// Get a single ProductDataObject by providing the ProductID.
/// </summary>
/// <param name="productID">The Int ProductID of the
/// ProductDataObject to return.</param>
/// <returns>A ProductDataObject</returns>
ProductDataObject GetProduct(int productID);

/// <summary>
/// Updates a ProductDataObject in the database.
/// </summary>
/// <param name="product">The ProductDataObject to update.</param>
bool UpdateProduct(ProductDataObject product);

/// <summary>
/// Deletes a single product from the database.
/// </summary>
/// <param name="product">The ProjectDataObject to delete.</param>
bool DeleteProduct(ProductDataObject product);

2. Review the ProductDataObject class.


This is the type returned by the methods in the IProductDataAccess interface. The following code
example shows this class.
If you are using Visual Basic, in Solution Explorer, double-click ProductDataObject.vb.
If you are using Visual C#, in Solution Explorer, double-click ProductDataObject.cs.

[Visual Basic]

Public Class ProductDataObject

Public Property ProductID As Integer

Public Property Name As String

Public Property ProductNumber As String

Public Property Color As String

Public Property ListPrice As Decimal

Public Property ModifiedDate As DateTime

End Class

[Visual C#]
public class ProductDataObject
{
public int ProductID { get; set; }
public string Name { get; set; }

public string ProductNumber { get; set; }


Lab Answer Key: Using ADO.NET 5

public string Color { get; set; }

public Decimal ListPrice { get; set; }

public DateTime ModifiedDate { get; set; }


}

3. In the DAL project, create a class named ProductDataAccessLayer:


a. In Solution Explorer, right-click the DAL project, point to Add, and then click Class.
b. In the Add New Item DAL dialog box, in the templates list, click Class.
c. In the Name box, type ProductDataAccessLayer and then click Add.
4. Modify the ProductDataAccessLayer class declaration to implement the IProductDataAccessLayer
interface.
Your code should resemble the following code example.

[Visual Basic]
Public Class ProductDataAccessLayer
Implements IProductDataAccessLayer
...

[Visual C#]
public class ProductDataAccessLayer : IProductDataAccessLayer
...

5. If you are using Visual Basic, generate the method stubs for each of the methods in the
IProductDataAccessLayer interface by positioning the cursor after IProductDataAccessLayer and
pressing ENTER.
If you are using Visual C#, use the Implement Interface Wizard to generate method stubs for each of
the methods in the IProductDataAccessLayer interface:
Right-click IProductDataAccessLayer, point to Implement Interface, and then click Implement
Interface.
6. Bring the System.Collections.Generic, System.Data, and System.Data.SqlClient namespaces into
scope:
If you are using Visual Basic, at the start of the file, add the following code.

[Visual Basic]
Imports System.Data
Imports System.Data.SqlClient
Imports System.Collections.Generic

If you are using Visual C#, at the start of the file, after the existing using statements, add the
following code.

[Visual C#]
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;
6 Lab Answer Key: Using ADO.NET

Task 4: Implement the GetProductList method


1. Review the Constants and AWDatabase classes:
If you are using Visual Basic, in Solution Explorer, double-click Constants.vb.
If you are using Visual C#, in Solution Explorer, double-click Constants.cs.
The Constants class contains a series of strings that specify the names of the stored procedures that
the methods in the ProductDataAccess class will invoke. These stored procedures have already been
created in the AdventureWorks database.
The following code example shows this class.

[Visual Basic]

Friend Class Constants


Friend Const GetAllProducts As String = "productGetAllProducts"
Friend Const GetProductByID As String = "productGetProductByID"
Friend Const GetProductByColor As String = "productGetProductByColor"
Friend Const GetProductByMaxListPrice As String = "productGetProductByMaxListPrice"
Friend Const UpdateProduct As String = "productUpdateProduct"
Friend Const DeleteProduct As String = "productDeleteProduct"

End Class

[Visual C#]

internal class Constants


{
internal const string GetAllProducts = "productGetAllProducts";
internal const string GetProductByID = "productGetProductByID";
internal const string GetProductByColor = "productGetProductByColor";
internal const string GetProductByMaxListPrice = "productGetProductByMaxListPrice";
internal const string UpdateProduct = "productUpdateProduct";
internal const string DeleteProduct = "productDeleteProduct";
}

The AWDatabase class is a singleton that retrieves the connection string that you will use to connect
to the AdventureWorks database from the configuration file.
The following code example shows this class.

[Visual Basic]

Friend Class AWDatabase

''' <summary>
''' The connection string for the AdventureWorks Database
''' </summary>
Friend Shared DatabaseConnectionString As String =
ConfigurationManager.ConnectionStrings("AdventureWorks").ConnectionString

''' <summary>
''' Prevent object construction
''' </summary>
Private Sub New()
End Sub

End Class
Lab Answer Key: Using ADO.NET 7

[Visual C#]
internal class AWDatabase
{
/// <summary>
/// The connection string for the AdventureWorks database.
/// </summary>
internal static string DatabaseConnectionString =
ConfigurationManager.ConnectionStrings["AdventureWorks"].ConnectionString;

/// <summary>
/// Prevent object construction.
/// </summary>
private AWDatabase() { }
}

2. Return to the ProductDataAccessLayer class:


If you are using Visual C#, in Solution Explorer, double-click ProductDataAccessLayer.cs.
If you are using Visual Basic, in Solution Explorer, double-click ProductDataAccessLayer.vb.
3. Locate the GetProductList method, which accepts no parameters.
If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
Add code to instantiate a generic list of ProductDataObject objects, named products.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList() As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList
Dim products As New List(Of ProductDataObject)
End Function

[Visual C#]
public List<ProductDataObject> GetProductList()
{
List<ProductDataObject> products = new List<ProductDataObject>();
}

4. In the GetProductList method, add a using code block that instantiates a new SqlConnection object
named connection. The SqlConnection object should take the contents of the static field
AWDatabase.DatabaseConnectionString as a parameter. This is the connection string that contains
the details necessary to connect to the AdventureWorks database.
This using code block ensures that the SqlConnection object is correctly disposed of and the
database connection is closed when it goes out of scope.

Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList() As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList

Dim products As New List(Of ProductDataObject)

Using connection As New SqlConnection(


8 Lab Answer Key: Using ADO.NET

AWDatabase.DatabaseConnectionString)

End Using

End Function

[Visual C#]
public List<ProductDataObject> GetProductList()
{
List<ProductDataObject> products = new List<ProductDataObject>();

using (SqlConnection connection =


new SqlConnection(AWDatabase.DatabaseConnectionString))
{
}
}

5. In the using block, add code to perform the following tasks:


a. Create a SqlCommand object called getAllProducts. This should run the string that is specified
by the GetAllProducts field in the Constants class and use the connection that is defined by the
using statement.
b. Set the CommandType property of the SqlCommand object to specify that the command runs
a stored procedure.
c. Open the connection to the database.
d. Create a SqlDataReader object called reader and use it to invoke the stored procedure. Specify
that the SqlDataReader object should automatically close the connection after it has retrieved
the data returned by the stored procedure.
e. Iterate through the results returned by the SqlDataReader object. For each row returned by the
stored procedure, create a new ProductDataObject object. Populate the fields in the
ProductDataObject object with the data returned by the SqlDataReader object, as shown in the
following table, and add the ProductDataObject object to the products list.

Column number Type of column Can be null? Field to populate

0 Int32 No ProductID

1 String No Name

2 String No ProductNumber

3 String Yes Color

4 Decimal No Price

5 Date Yes ModifiedDate

Your code should resemble the following code example.

[Visual Basic]
Dim getAllProducts As New SqlCommand(Constants.GetAllProducts, connection)
getAllProducts.CommandType = CommandType.StoredProcedure
connection.Open()
Dim reader As SqlDataReader =
getAllProducts.ExecuteReader(CommandBehavior.CloseConnection)
Lab Answer Key: Using ADO.NET 9

While reader.Read()

Dim prod As New ProductDataObject()


prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)

If Not reader.IsDBNull(3) Then


prod.Color = reader.GetString(3)
End If
prod.ListPrice = reader.GetDecimal(4)

If Not reader.IsDBNull(5) Then


prod.ModifiedDate = reader.GetDateTime(5).[Date]
End If

products.Add(prod)

End While

[Visual C#]

SqlCommand getAllProducts = new SqlCommand(Constants.GetAllProducts, connection);

getAllProducts.CommandType = CommandType.StoredProcedure;
connection.Open();

SqlDataReader reader = getAllProducts.ExecuteReader(CommandBehavior.CloseConnection);

while (reader.Read())
{
ProductDataObject prod = new ProductDataObject();
prod.ProductID = reader.GetInt32(0);
prod.Name = reader.GetString(1);
prod.ProductNumber = reader.GetString(2);

if (!reader.IsDBNull(3))
{
prod.Color = reader.GetString(3);
}

prod.ListPrice = reader.GetDecimal(4);

if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
}
products.Add(prod);

6. At the end of the GetProductList method, return the list of products.


Your code should resemble the following code example.

[Visual Basic]

Public Function GetProductList() As System.Collections.Generic.List(Of


ProductDataObject) Implements IProductDataAccessLayer.GetProductList
10 Lab Answer Key: Using ADO.NET

Dim products As New List(Of ProductDataObject)

Using connection As New SqlConnection(AWDatabase.DatabaseConnectionString)

Dim getAllProducts As New SqlCommand(Constants.GetAllProducts, connection)

getAllProducts.CommandType = CommandType.StoredProcedure
connection.Open()

Dim reader As SqlDataReader =


getAllProducts.ExecuteReader(CommandBehavior.CloseConnection)

While reader.Read()

Dim prod As New ProductDataObject()


prod.ProductID = reader.GetInt32(0)

prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)

If Not reader.IsDBNull(3) Then


prod.Color = reader.GetString(3)
End If

prod.ListPrice = reader.GetDecimal(4)

If Not reader.IsDBNull(5) Then


prod.ModifiedDate = reader.GetDateTime(5).[Date]
End If

products.Add(prod)

End While

End Using

Return products

End Function

[Visual C#]
public List<ProductDataObject> GetProductList()
{
List<ProductDataObject> products = new List<ProductDataObject>();

using (SqlConnection connection = new


SqlConnection(AWDatabase.DatabaseConnectionString))
{

SqlCommand getAllProducts = new


SqlCommand(Constants.GetAllProducts, connection);

getAllProducts.CommandType = CommandType.StoredProcedure;
connection.Open();
SqlDataReader reader = getAllProducts.ExecuteReader(
CommandBehavior.CloseConnection);
while (reader.Read())
{
ProductDataObject prod = new ProductDataObject();
prod.ProductID = reader.GetInt32(0);
prod.Name = reader.GetString(1);
Lab Answer Key: Using ADO.NET 11

prod.ProductNumber = reader.GetString(2);

if (!reader.IsDBNull(3))
{
prod.Color = reader.GetString(3);
}

prod.ListPrice = reader.GetDecimal(4);

if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
}
products.Add(prod);
}
}
return products;
}

7. Build the project and correct any errors:


On the Build menu, click Build DAL. Correct any errors.

Task 5: Implement the GetProduct method


1. Locate the GetProduct method in the ProductDataAccessLayer class, which accepts an integer
parameter that specifies a product ID.
If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
2. In the GetProduct method, add a ProductDataObject object named prod and assign it the value
null.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProduct(ByVal productID As Integer) As _
ProductDataObject _
Implements IProductDataAccessLayer.GetProduct

Dim prod As ProductDataObject = Nothing

End Function

[Visual C#]
public ProductDataObject GetProduct(int productID)
{
ProductDataObject prod = null;
}

3. In the GetProduct method, add a using code block that instantiates a new SqlConnection object
named connection. The SqlConnection object should use the static field
AWDatabase.DatabaseConnectionString as a parameter.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProduct(ByVal productID As Integer) As _
ProductDataObject _
12 Lab Answer Key: Using ADO.NET

Implements IProductDataAccessLayer.GetProduct
Dim prod As ProductDataObject = Nothing
Using connection As New SqlConnection(
AWDatabase.DatabaseConnectionString)
End Using

End Function

[Visual C#]
public ProductDataObject GetProduct(int productID)
{
ProductDataObject prod = null;
using (SqlConnection connection = new
SqlConnection(AWDatabase.DatabaseConnectionString))
{ }
}

4. In the using block, add code to perform the following tasks:


a. Create a SqlCommand object called getProductsId. This should run the string that is specified
by the GetProductByID field in the Constants class and use the connection that is defined by
the using statement.
b. Set the CommandType property of the SqlCommand object to specify that the command runs
a stored procedure.
c. Create a SqlParameter object for the productID parameter that is passed in. The
GetProductByID method expects the product ID as a parameter. It returns all products that
match this product ID.
d. Open the connection to the database.
e. Create a SqlDataReader object called reader and use it to invoke the stored procedure. Specify
that the SqlDataReader object should automatically close the connection after it has retrieved
the data returned by the stored procedure.
f. Read the first row of the results returned by the SqlDataReader object. Instantiate the prod
object as a new ProductDataObject object. Populate the fields in the ProductDataObject
object with the data returned by the SqlDataReader object, as shown in the following table.

Column number Type of column Can be null? Field to populate

0 Int32 No ProductID

1 String No Name

2 String No ProductNumber

3 String Yes Color

4 Decimal No Price

5 Date Yes ModifiedDate

Your code should resemble the following code example.

[Visual Basic]
Dim getProductById As New SqlCommand(
Constants.GetProductByID, connection)
getProductById.CommandType = CommandType.StoredProcedure
Lab Answer Key: Using ADO.NET 13

Dim parameter As New SqlParameter("@ProductID", productID)


getProductById.Parameters.Add(parameter)
connection.Open()

Dim reader As SqlDataReader = getProductById.ExecuteReader(


CommandBehavior.CloseConnection)

If reader.Read() Then
prod = New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)

If Not reader.IsDBNull(3) Then


prod.Color = reader.GetString(3)
End If

prod.ListPrice = reader.GetDecimal(4)

If Not reader.IsDBNull(5) Then


prod.ModifiedDate = reader.GetDateTime(5).[Date]
End If
End If

[Visual C#]

SqlCommand getProductById = new SqlCommand(Constants.GetProductByID, connection);


getProductById.CommandType = CommandType.StoredProcedure;
SqlParameter parameter = new SqlParameter("@ProductID", productID);
getProductById.Parameters.Add(parameter);
connection.Open();

SqlDataReader reader = getProductById.ExecuteReader(CommandBehavior.CloseConnection);

if (reader.Read())
{

prod = new ProductDataObject();


prod.ProductID = reader.GetInt32(0);
prod.Name = reader.GetString(1);
prod.ProductNumber = reader.GetString(2);
if (!reader.IsDBNull(3))
{
prod.Color = reader.GetString(3);
}

prod.ListPrice = reader.GetDecimal(4);

if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
}
}

5. At the end of the GetProductById method, return the prod object.


Your code should resemble the following code example.

[Visual Basic]

Public Function GetProduct(ByVal productID As Integer) As _


14 Lab Answer Key: Using ADO.NET

ProductDataObject _
Implements IProductDataAccessLayer.GetProduct

Dim prod As ProductDataObject = Nothing

Using connection As New SqlConnection(


AWDatabase.DatabaseConnectionString)

Dim getProductById As New SqlCommand(


Constants.GetProductByID, connection)
getProductById.CommandType = CommandType.StoredProcedure
Dim parameter As New SqlParameter("@ProductID", productID)
getProductById.Parameters.Add(parameter)
connection.Open()

Dim reader As SqlDataReader = getProductById.ExecuteReader(


CommandBehavior.CloseConnection)

If reader.Read() Then
prod = New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)

If Not reader.IsDBNull(3) Then


prod.Color = reader.GetString(3)
End If
prod.ListPrice = reader.GetDecimal(4)

If Not reader.IsDBNull(5) Then


prod.ModifiedDate = reader.GetDateTime(5).[Date]
End If
End If

End Using

Return prod

End Function

[Visual C#]

public ProductDataObject GetProduct(int productID)


{
ProductDataObject prod = null;
using (SqlConnection connection = new
SqlConnection(AWDatabase.DatabaseConnectionString))
{
SqlCommand getProductById = new
SqlCommand(Constants.GetProductByID, connection);

getProductById.CommandType = CommandType.StoredProcedure;

SqlParameter parameter = new


SqlParameter("@ProductID", productID);

getProductById.Parameters.Add(parameter);

connection.Open();

SqlDataReader reader = getProductById.ExecuteReader(


Lab Answer Key: Using ADO.NET 15

CommandBehavior.CloseConnection);

if (reader.Read())
{
prod = new ProductDataObject();
prod.ProductID = reader.GetInt32(0);
prod.Name = reader.GetString(1);
prod.ProductNumber = reader.GetString(2);

if (!reader.IsDBNull(3))
{
prod.Color = reader.GetString(3);
}
prod.ListPrice = reader.GetDecimal(4);

if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
}
}
}
return prod;
}

6. Build the project and correct any errors:


On the Build menu, click Build DAL. Correct any errors.

Task 6: Implement the overloaded GetProductList methods


1. Locate the GetProductList method, which accepts a string parameter that specifies the color to
search for.
If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
2. Add code to instantiate a generic list of ProductDataObject objects, named products.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList(ByVal color As String) As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList
Dim products As New List(Of ProductDataObject)
End Function

[Visual C#]
public List<ProductDataObject> GetProductList(string color)
{
List<ProductDataObject> products = new List<ProductDataObject>();
}

3. In the GetProductList method, add code that creates a connection to the database by using the
value that is specified by the static field AWDatabase.DatabaseConnectionString and then uses a
SqlDataReader object to execute the stored procedure that is specified by the
Constants.GetProductByColor string. This stored procedure expects a string parameter called color
that indicates the color to match; create a SqlParameter object based on the parameter that is
16 Lab Answer Key: Using ADO.NET

passed to the GetProductList method. Iterate through the results and fetch each matching product.
Add the products to the products list, and return this list from the GetProductList method.
The method should be similar to the original GetProductList method that takes no parameters.

Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList(ByVal color As String) As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList

Dim products As New List(Of ProductDataObject)

Using connection As New SqlConnection(


AWDatabase.DatabaseConnectionString)

connection.Open()
Dim getProductsByColor As New SqlCommand(
Constants.GetProductByColor, connection)
getProductsByColor.CommandType = CommandType.StoredProcedure
Dim param As New SqlParameter("color", color)
getProductsByColor.Parameters.Add(param)
Dim reader As SqlDataReader =
getProductsByColor.ExecuteReader(
CommandBehavior.CloseConnection)

While reader.Read()
Dim prod As New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)

If Not reader.IsDBNull(3) Then


prod.Color = reader.GetString(3)
End If

prod.ListPrice = reader.GetDecimal(4)

If Not reader.IsDBNull(5) Then


prod.ModifiedDate = reader.GetDateTime(5).[Date]
End If
products.Add(prod)
End While
End Using

Return products

End Function

[Visual C#]
public List<ProductDataObject> GetProductList(string color)
{
List<ProductDataObject> products = new List<ProductDataObject>();
using (SqlConnection connection = new
SqlConnection(AWDatabase.DatabaseConnectionString))
{
connection.Open();
SqlCommand getProductsByColor = new
SqlCommand(Constants.GetProductByColor, connection);
getProductsByColor.CommandType = CommandType.StoredProcedure;
Lab Answer Key: Using ADO.NET 17

SqlParameter param = new SqlParameter("color", color);


getProductsByColor.Parameters.Add(param);
SqlDataReader reader = getProductsByColor.ExecuteReader(
CommandBehavior.CloseConnection);

while (reader.Read())
{
ProductDataObject prod = new ProductDataObject();
prod.ProductID = reader.GetInt32(0);
prod.Name = reader.GetString(1);
prod.ProductNumber = reader.GetString(2);

if (!reader.IsDBNull(3))
{
prod.Color = reader.GetString(3);
}
prod.ListPrice = reader.GetDecimal(4);

if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
}
products.Add(prod);
}
}
return products;
}

4. Locate the GetProductList method, which accepts a decimal parameter that specifies the maximum
price to search for.
If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
5. In this version of the GetProductList method, add code to retrieve all products that have a price that
is less than or equal to the price that is specified as the parameter. Use the stored procedure that is
specified by the Constants.GetProductByMaxListPrice field. The stored procedure expects a
parameter named maxListPrice. Create and return a list of all matching ProductDataObject objects.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList(ByVal maxListPrice As Decimal) As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList

Dim products As New List(Of ProductDataObject)()

Using connection As New SqlConnection(


AWDatabase.DatabaseConnectionString)

connection.Open()
Dim getProductsByPrice As New SqlCommand(
Constants.GetProductByMaxListPrice, connection)
getProductsByPrice.CommandType =
CommandType.StoredProcedure
Dim param As New SqlParameter("maxListPrice",
maxListPrice)
getProductsByPrice.Parameters.Add(param)

Dim reader As SqlDataReader =


getProductsByPrice.ExecuteReader(
CommandBehavior.CloseConnection)
18 Lab Answer Key: Using ADO.NET

While reader.Read()
Dim prod As New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)

If Not reader.IsDBNull(3) Then


prod.Color = reader.GetString(3)
End If

prod.ListPrice = reader.GetDecimal(4)
If Not reader.IsDBNull(5) Then
prod.ModifiedDate = reader.GetDateTime(5).[Date]
End If
products.Add(prod)

End While

End Using

Return products

End Function

[Visual C#]
public List<ProductDataObject> GetProductList(decimal maxListPrice)
{
List<ProductDataObject> products = new List<ProductDataObject>();

using (SqlConnection connection = new


SqlConnection(AWDatabase.DatabaseConnectionString))
{
connection.Open();
SqlCommand getProductsByPrice = new SqlCommand(
Constants.GetProductByMaxListPrice, connection);
getProductsByPrice.CommandType = CommandType.StoredProcedure;
SqlParameter param = new SqlParameter(
"maxListPrice", maxListPrice);
getProductsByPrice.Parameters.Add(param);

SqlDataReader reader = getProductsByPrice.ExecuteReader(


CommandBehavior.CloseConnection);

while (reader.Read())
{
ProductDataObject prod = new ProductDataObject();
prod.ProductID = reader.GetInt32(0);
prod.Name = reader.GetString(1);
prod.ProductNumber = reader.GetString(2);

if (!reader.IsDBNull(3))
{
prod.Color = reader.GetString(3);
}

prod.ListPrice = reader.GetDecimal(4);

if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
Lab Answer Key: Using ADO.NET 19

}
products.Add(prod);
}
}
return products;
}

6. Build the project and correct any errors:


On the Build menu, click Build DAL. Correct any errors.

Task 7: Implement the UpdateProduct method


1. Locate the UpdateProduct method. This method takes a ProductDataObject object as a parameter
and returns a Boolean value. You will implement this method to update the specified product in the
database with the information in this object and return true if the operation is successful or false
otherwise.
2. If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
3. In the body of the method, add code to perform the following tasks:
a. Create a Boolean variable called result and initialize it to false. You will use this variable to
indicate whether the delete was successful.
b. Create a database connection object in a using block. Connect to the database by using the
connection string that is specified in the AWDatabase.DatabaseConnectionString field.
c. Create a SqlCommand object that can be used to execute the stored procedure that is specified
by the Constants.UpdateProduct field.
d. Create SqlParameter objects with the names that are specified in the following table. Populate
these SqlParameter objects with the data from the ProductDataObject object that is passed in
to the method.

Stored procedure parameter


name ProductDataObject field to use

@productID ProductID

@name Name

@productNumber ProductNumber

@color Color

@listPrice ListPrice

e. Add the parameters to the SqlCommand object.


f. Open the connection to the database.
g. Execute the stored procedure by using the ExecuteNonQuery method of the SqlCommand
object and set the value of the result variable to true. Note that if the update fails, a
SqlException exception will be thrown.
h. Catch the SqlException exception and set the value of the result variable to false.
i. Return the result variable.

Your code should resemble the following code example.

[Visual Basic]
20 Lab Answer Key: Using ADO.NET

Public Function UpdateProduct(


ByVal product As ProductDataObject) As Boolean _
Implements IProductDataAccessLayer.UpdateProduct

Dim result As Boolean = False

Using connection As New SqlConnection(


AWDatabase.DatabaseConnectionString)
Dim _UpdateProduct As New SqlCommand(
Constants.UpdateProduct, connection)

_UpdateProduct.CommandType = CommandType.StoredProcedure
Dim prodID As New SqlParameter("@productID",
product.ProductID)

Dim name As New SqlParameter("@name", product.Name)

Dim productNumber As New SqlParameter("@productNumber",


product.ProductNumber)

Dim color As New SqlParameter("@color", product.Color)

Dim listPrice As New SqlParameter("@listPrice",


product.ListPrice)

_UpdateProduct.Parameters.Add(prodID)
_UpdateProduct.Parameters.Add(name)
_UpdateProduct.Parameters.Add(productNumber)
_UpdateProduct.Parameters.Add(color)
_UpdateProduct.Parameters.Add(listPrice)

connection.Open()

Try
_UpdateProduct.ExecuteNonQuery()
result = True
Catch generatedExceptionName As SqlException
result = False
End Try
End Using

Return result

End Function

[Visual C#]
public bool UpdateProduct(ProductDataObject product)
{
bool result = false;

using (SqlConnection connection = new


SqlConnection(AWDatabase.DatabaseConnectionString))
{
SqlCommand updateProduct = new
SqlCommand(Constants.UpdateProduct, connection);

updateProduct.CommandType = CommandType.StoredProcedure;
SqlParameter prodID = new
SqlParameter("@productID", product.ProductID);

SqlParameter name = new SqlParameter("@name", product.Name);


Lab Answer Key: Using ADO.NET 21

SqlParameter productNumber = new


SqlParameter("@productNumber", product.ProductNumber);

SqlParameter color = new


SqlParameter("@color", product.Color);

SqlParameter listPrice = new


SqlParameter("@listPrice", product.ListPrice);

updateProduct.Parameters.Add(prodID);
updateProduct.Parameters.Add(name);
updateProduct.Parameters.Add(productNumber);
updateProduct.Parameters.Add(color);
updateProduct.Parameters.Add(listPrice);

connection.Open();

try
{
updateProduct.ExecuteNonQuery();
result=true;
}
catch(SqlException)
{
result=false;
}
}
return result;
}

4. Build the project and correct any errors:


On the Build menu, click Build DAL. Correct any errors.

Task 8: Implement the DeleteProduct method


1. Locate the DeleteProduct method. This method takes a ProductDataObject object as a parameter
and returns a Boolean value. You will implement this method to delete the specified product from the
database and return true if the operation is successful or false otherwise.
2. If you are using Visual C#, remove the default method body inserted by Visual Studio that throws a
NotImplementedException exception.
3. In the body of the method, add code to perform the following tasks:
a. Create a Boolean variable called result and initialize it to false. You will use this variable to
indicate whether the delete was successful.
b. Create a database connection object in a using block. Connect to the database by using the
connection string that is specified in the AWDatabase.DatabaseConnectionString field.
c. Create a SqlCommand object that can be used to execute the stored procedure that is specified
by the Constants.DeleteProduct field.
d. Create a SqlParameter object with the details that are specified in the following table.

Stored procedure parameter


name ProductDataObject field to use

@productID ProductID

e. Add the parameter to the SqlCommand object.


22 Lab Answer Key: Using ADO.NET

f. Open the connection to the database.


g. Execute the stored procedure by using the ExecuteNonQuery method of the SqlCommand
object and set the value of result to true. Note that if the update fails, a SqlException exception
will be thrown.
h. Catch the SqlException exception and set the value of result to false.
i. Return the result variable.

Your code should resemble the following code example.

[Visual Basic]

Public Function DeleteProduct(ByVal product As ProductDataObject) As _


Boolean _
Implements IProductDataAccessLayer.DeleteProduct

Dim result As Boolean = False


Using connection As New SqlConnection(
AWDatabase.DatabaseConnectionString)

Dim _DeleteProduct As New SqlCommand(


Constants.DeleteProduct, connection)
_DeleteProduct.CommandType = CommandType.StoredProcedure

Dim prodID As New SqlParameter("@productID",


product.ProductID)
_DeleteProduct.Parameters.Add(prodID)
connection.Open()

Try
_DeleteProduct.ExecuteNonQuery()
result = True
Catch generatedExceptionName As SqlException
result = False
End Try
End Using
Return result
End Function

[Visual C#]
public bool DeleteProduct(ProductDataObject product)
{
bool result = false;
using (SqlConnection connection = new
SqlConnection(AWDatabase.DatabaseConnectionString))
{

SqlCommand deleteProduct = new


SqlCommand(Constants.DeleteProduct, connection);
deleteProduct.CommandType = CommandType.StoredProcedure;

SqlParameter prodID = new


SqlParameter("@productID", product.ProductID);

deleteProduct.Parameters.Add(prodID);
connection.Open();

try
{
deleteProduct.ExecuteNonQuery();
result=true;
Lab Answer Key: Using ADO.NET 23

}
catch(SqlException)
{
result=false;
}
}
return result;
}

4. Build the project and correct any errors:


On the Build menu, click Build DAL. Correct any errors.

Task 9: Create unit tests for the data access layer


1. In the DAL Unit Tests project, open the IProductDataAccessLayerTest class:
In Solution Explorer, expand the DAL Unit Tests project, and then double-click
IProductDataAccessLayerTest.cs.
2. Locate the CreateIProductDataAccessLayer method and remove the comment TODO: Instantiate
an appropriate concrete class.
3. Add code to perform the following tasks:
a. Create a new instance of the ProductDataAccessLayer class named target.
b. Return the value of the target variable.

Your code should resemble the following code example.

[Visual Basic]
Friend Overridable Function CreateIProductDataAccessLayer() As _
IProductDataAccessLayer

Dim target As New ProductDataAccessLayer()


Return target

End Function

[Visual C#]
internal virtual IProductDataAccessLayer CreateIProductDataAccessLayer()
{
IProductDataAccessLayer target = new ProductDataAccessLayer();
return target;
}

4. Locate the GetProductListTest method.


5. Remove the comment TODO and add code to perform the following tasks:
a. Create a variable of type IProductDataAccessLayer named target. Initialize the value of the
target variable by invoking the CreateIProductDataAccessLayer method.
b. Create a new instance of a generic list of ProductDataType objects, named expected. Initialize
this list by invoking the static GetLocalProductList method.

The GetLocalProductList method is part of the IProductDataAccessLayerTest class. It creates a


list that contains a set of known values for testing purposes.

c. Create another instance of a generic list of ProductDataType objects, named actual. Initialize
this list by invoking the target.GetProductList method.
24 Lab Answer Key: Using ADO.NET

d. Iterate through the actual and expected lists, and verify that the items in each list are identical.
Use the AreEqual method of the Assert class.
Your completed code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
Public Sub GetProductListTest()

Dim target As IProductDataAccessLayer =


CreateIProductDataAccessLayer()
Dim expected As List(Of ProductDataObject) = GetLocalProductList()
Dim actual As List(Of ProductDataObject) = target.GetProductList()

For i As Integer = 0 To actual.Count - 1


Assert.AreEqual(actual(i).Color, expected(i).Color)
Assert.AreEqual(actual(i).ListPrice, expected(i).ListPrice)
Assert.AreEqual(actual(i).ModifiedDate,
expected(i).ModifiedDate)
Assert.AreEqual(actual(i).Name, expected(i).Name)
Assert.AreEqual(actual(i).ProductID, expected(i).ProductID)
Assert.AreEqual(actual(i).ProductNumber,
expected(i).ProductNumber)
Next

End Sub

[Visual C#]
[TestMethod()]
public void GetProductListTest()
{
IProductDataAccessLayer target = CreateIProductDataAccessLayer();
List<ProductDataObject> expected = GetLocalProductList();
List<ProductDataObject> actual = target.GetProductList();

for (int i = 0; i <= actual.Count-1; i++)


{
Assert.AreEqual(actual[i].Color, expected[i].Color);
Assert.AreEqual(actual[i].ListPrice, expected[i].ListPrice);
Assert.AreEqual(actual[i].ModifiedDate,
expected[i].ModifiedDate);
Assert.AreEqual(actual[i].Name, expected[i].Name);
Assert.AreEqual(actual[i].ProductID, expected[i].ProductID);
Assert.AreEqual(actual[i].ProductNumber,
expected[i].ProductNumber);
}
}

6. Locate the GetProductByProductIDTest method.


7. Remove the comment TODO and add code to perform the following tasks:
a. Create a variable of type IProductDataAccessLayer named target. Initialize the value of the
target variable by invoking the CreateIProductDataAccessLayer method.
b. Define an integer variable called productID and initialize it with the value 319.
c. Create a variable of type ProductDataObject called expected. Initialize this object with the data
for product 319 (use the productID variable) in the list returned by the GetLocalProductList
method.
Lab Answer Key: Using ADO.NET 25

d. Create another variable of type ProductDataObject called actual. Initialize this object with the
data returned from the GetProduct method of the target object. Pass the productID variable as
the parameter to the GetProduct method.
e. Verify that the data in the actual and expected variables is the same. Use the AreEqual method
of the Assert class.
Your completed code should resemble the following code example.

[Visual Basic]

<TestMethod()> _
Public Sub GetProductByProductIDTest()

Dim target As IProductDataAccessLayer =


CreateIProductDataAccessLayer()
Dim productID As Integer = 319
Dim expected As ProductDataObject =
GetLocalProductList().Find(
Function(p) p.ProductID = productID)

Dim actual As ProductDataObject


actual = target.GetProduct(productID)

Assert.AreEqual(actual.Color, expected.Color)
Assert.AreEqual(actual.ListPrice, expected.ListPrice)
Assert.AreEqual(actual.ModifiedDate, expected.ModifiedDate)
Assert.AreEqual(actual.Name, expected.Name)
Assert.AreEqual(actual.ProductID, expected.ProductID)
Assert.AreEqual(actual.ProductNumber, expected.ProductNumber)

End Sub

[Visual C#]
[TestMethod()]
public void GetProductByProductIDTest()
{
IProductDataAccessLayer target = CreateIProductDataAccessLayer();
int productID = 319;
ProductDataObject expected =
GetLocalProductList().Find(p => p.ProductID == productID);

ProductDataObject actual;
actual = target.GetProduct(productID);

Assert.AreEqual(actual.Color, expected.Color);
Assert.AreEqual(actual.ListPrice, expected.ListPrice);
Assert.AreEqual(actual.ModifiedDate, expected.ModifiedDate);
Assert.AreEqual(actual.Name, expected.Name);
Assert.AreEqual(actual.ProductID, expected.ProductID);
Assert.AreEqual(actual.ProductNumber, expected.ProductNumber);
}

8. Locate the GetProductListByMaxPriceTest method.


9. Remove the comment TODO and add code to test the version of the GetProductList method that
expects a decimal parameter, specifying the maximum price to search for. Search for products that
have a maximum price of 100. Follow the same general pattern as the GetProductListTest method.
Your completed code should resemble the following code example.
26 Lab Answer Key: Using ADO.NET

[Visual Basic]
<TestMethod()> _
Public Sub GetProductListByMaxPriceTest()

Dim target As IProductDataAccessLayer =


CreateIProductDataAccessLayer()
Dim maxListPrice As New [Decimal](100.0R)
Dim expected As List(Of ProductDataObject) =
GetLocalProductList().FindAll(
Function(p) p.ListPrice <= maxListPrice)

Dim actual As List(Of ProductDataObject)


actual = target.GetProductList(maxListPrice)
For i As Integer = 0 To actual.Count - 1
Assert.AreEqual(actual(i).Color, expected(i).Color)
Assert.AreEqual(actual(i).ListPrice, expected(i).ListPrice)
Assert.AreEqual(actual(i).ModifiedDate,
expected(i).ModifiedDate)
Assert.AreEqual(actual(i).Name, expected(i).Name)
Assert.AreEqual(actual(i).ProductID, expected(i).ProductID)
Assert.AreEqual(actual(i).ProductNumber,
expected(i).ProductNumber)
Next

End Sub

[Visual C#]
[TestMethod()]
public void GetProductListByMaxPriceTest()
{
IProductDataAccessLayer target = CreateIProductDataAccessLayer();
Decimal maxListPrice = new Decimal(100.00);
List<ProductDataObject> expected =
GetLocalProductList().FindAll(p => p.ListPrice <= maxListPrice);

List<ProductDataObject> actual;
actual = target.GetProductList(maxListPrice);
for (int i = 0; i <= actual.Count - 1; i++)
{
Assert.AreEqual(actual[i].Color, expected[i].Color);
Assert.AreEqual(actual[i].ListPrice, expected[i].ListPrice);
Assert.AreEqual(actual[i].ModifiedDate,
expected[i].ModifiedDate);
Assert.AreEqual(actual[i].Name, expected[i].Name);
Assert.AreEqual(actual[i].ProductID, expected[i].ProductID);
Assert.AreEqual(actual[i].ProductNumber,
expected[i].ProductNumber);
}
}

10. Locate the GetProductListByColorTest method.


11. Remove the comment TODO and add code to test the version of the GetProductList method that
expects a string parameter, specifying the color to search for. Search for products that have a color of
"Silver".
Your completed code should resemble the following code example.

[Visual Basic]
<TestMethod()> _
Public Sub GetProductListByColorTest()
Lab Answer Key: Using ADO.NET 27

Dim target As IProductDataAccessLayer =


CreateIProductDataAccessLayer()
Dim color As String = "Silver"
Dim expected As List(Of ProductDataObject) =
GetLocalProductList().FindAll(Function(p) p.Color = color)

Dim actual As List(Of ProductDataObject)


actual = target.GetProductList(color)
For i As Integer = 0 To actual.Count - 1
Assert.AreEqual(actual(i).Color, expected(i).Color)
Assert.AreEqual(actual(i).ListPrice, expected(i).ListPrice)
Assert.AreEqual(actual(i).ModifiedDate,
expected(i).ModifiedDate)
Assert.AreEqual(actual(i).Name, expected(i).Name)
Assert.AreEqual(actual(i).ProductID, expected(i).ProductID)
Assert.AreEqual(actual(i).ProductNumber,
expected(i).ProductNumber)
Next
End Sub

[Visual C#]
[TestMethod()]
public void GetProductListByColorTest()
{
IProductDataAccessLayer target = CreateIProductDataAccessLayer();
string color = "Silver";
List<ProductDataObject> expected =
GetLocalProductList().FindAll(p=>p.Color==color);

List<ProductDataObject> actual;
actual = target.GetProductList(color);
for (int i = 0; i <= actual.Count-1; i++)
{
Assert.AreEqual(actual[i].Color, expected[i].Color);
Assert.AreEqual(actual[i].ListPrice, expected[i].ListPrice);
Assert.AreEqual(actual[i].ModifiedDate,
expected[i].ModifiedDate);
Assert.AreEqual(actual[i].Name, expected[i].Name);
Assert.AreEqual(actual[i].ProductID, expected[i].ProductID);
Assert.AreEqual(actual[i].ProductNumber,
expected[i].ProductNumber);
}
}

12. Locate the UpdateProductTest method.


13. Remove the comment TODO and add code to test the UpdateProduct method by updating product
1; set the color to "Red" and the list price to 12.99. Verify that the UpdateProduct method returns
true when it has successfully updated the product.
Your completed code should resemble the following code example.

[Visual Basic]
<TestMethod()> _

Public Sub UpdateProductTest()

Dim result As Boolean = False


Dim target As IProductDataAccessLayer =
CreateIProductDataAccessLayer()
28 Lab Answer Key: Using ADO.NET

Dim product As ProductDataObject =


GetLocalProductList().Find(Function(p) p.ProductID = 1)
product.Color = "Red"
product.ListPrice = 12.99D
result = target.UpdateProduct(product)
Assert.IsTrue(result)

End Sub

[Visual C#]
[TestMethod()]

public void UpdateProductTest


{

bool result = false;


IProductDataAccessLayer target = CreateIProductDataAccessLayer();
ProductDataObject product =
GetLocalProductList().Find(p => p.ProductID == 1);
product.Color = "Red";
product.ListPrice = 12.99M;
result=target.UpdateProduct(product);
Assert.IsTrue(result);

14. Locate the DeleteProductTest method.


15. Remove the comment TODO and add code to test the DeleteProduct method by deleting product
1. Verify that the DeleteProduct method returns true when it has successfully deleted the product.
Your completed code should resemble the following code example.

[Visual Basic]
<TestMethod()> _

Public Sub DeleteProductTest()

Dim result As Boolean = False


Dim target As IProductDataAccessLayer =
CreateIProductDataAccessLayer()
Dim product As ProductDataObject =
GetLocalProductList().Find(Function(p) p.ProductID = 1)
result = target.DeleteProduct(product)

Assert.IsTrue(result)

End Sub

[Visual C#]
[TestMethod()]

public void DeleteProductTest


{

bool result = false;


IProductDataAccessLayer target = CreateIProductDataAccessLayer();
ProductDataObject product =
Lab Answer Key: Using ADO.NET 29

GetLocalProductList().Find(p => p.ProductID == 1);


result = target.DeleteProduct(product);

Assert.IsTrue(result);

16. Build the project and correct any errors:


On the Build menu, click Build DAL Unit Tests. Correct any errors.

Task 10: Test the data access layer


1. In the DAL Unit Tests project, run all unit tests and verify that they all pass:
a. On the Test menu, point to Run, and then click All Tests in Solution.
b. Verify that all of the tests succeed.
2. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Developing the Product List Web Application


Task 1: Open the starter project for this exercise
Open the solution, Using ADO.NET.sln, in the E:\Labfiles\Lab14\VB\Ex2\Starter\Using ADO.NET or
E:\Labfiles\Lab14\CS\Ex2\Starter\Using ADO.NET folder. This solution contains an ASP.NET Model-
View-Controller (MVC) Web application that you will use to test the ADO.NET data access layer that
you built in the previous exercise:
a. On the File menu, point to Open, and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab14\VB\Ex2\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and then
click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab14\CS\Ex2\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and then
click Open.

Task 2: Implement the BrowseColor controller action


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the HomeController file by double-clicking the comment TODO: Create a new instance of
the ProductDataAccessLayer. This task is located in the BrowseColor method. The purpose of this
method is to retrieve a list of all products that match the color that is specified by the user:
In the task list, double-click the TODO: Create a new instance of the ProductDataAccessLayer.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
Your code should resemble the following code example.

[Visual Basic]
Dim dal As New ProductDataAccessLayer()
30 Lab Answer Key: Using ADO.NET

[Visual C#]
ProductDataAccessLayer dal = new ProductDataAccessLayer();

4. Locate the comment TODO: Create a list of Products.


5. Remove the comment and replace it with a statement that instantiates a generic list of
ProductDataObject objects named products. Initialize this list with the value returned by the
dal.GetProductList method, passing the value productResponse.Color as a parameter to this
method.
Your code should resemble the following code example.

[Visual Basic]
Dim products As List(Of ProductDataObject) =
dal.GetProductList(productResponse.Color)

[Visual C#]
List<ProductDataObject> products =
dal.GetProductList(productResponse.Color);

6. Locate the comment TODO: Return the Products view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the products collection to the Products view for display.
Your code should resemble the following code example.

[Visual Basic]
Return View("Products", products)

[Visual C#]
return View("Products", products);

Your completed code should resemble the following code example.

[Visual Basic]
<AcceptVerbs(HttpVerbs.Post)> _
Public Function BrowseColor(ByVal productResponse As ProductResponse) As ActionResult
If String.IsNullOrEmpty(productResponse.Color) Then

ModelState.AddModelError("error", "Please enter a color")


End If
If ModelState.IsValid Then
Dim dal As New ProductDataAccessLayer()

Dim products As List(Of ProductDataObject) =


dal.GetProductList(productResponse.Color)

Return View("Products", products)

Else
Return View("Index")
End If
End Function
Lab Answer Key: Using ADO.NET 31

[Visual C#]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult BrowseColor(ProductResponse productResponse)
{
if (string.IsNullOrEmpty(productResponse.Color))
{
ModelState.AddModelError("error", "Please enter a color");
}
if (ModelState.IsValid)
{
ProductDataAccessLayer dal = new ProductDataAccessLayer();
List<ProductDataObject> products =
dal.GetProductList(productResponse.Color);
return View("Products", products);
}
else
{
return View("Index");
}
}

Task 3: Implement the BrowsePrice controller action


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the BrowsePrice method by double-clicking the first comment TODO: Create a new
instance of the ProductDataAccessLayer in the task list. The purpose of this method is to retrieve a
list of all products that have a list price not greater than the maximum price that is specified by the
user.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
Your code should resemble the following code example.

[Visual Basic]

Dim dal As New ProductDataAccessLayer()

[Visual C#]

ProductDataAccessLayer dal = new ProductDataAccessLayer();

4. Locate the comment TODO: Create a list of Products.


5. Remove the comment and replace it with a statement that instantiates a generic list of
ProductDataObject objects, named products. Initialize this list with the value returned by the
dal.GetProductList method, passing the value maxprice as a parameter to this method.
Your code should resemble the following code example.

[Visual Basic]

Dim products As List(Of ProductDataObject) =


32 Lab Answer Key: Using ADO.NET

dal.GetProductList(maxprice)

[Visual C#]

List<ProductDataObject> products = dal.GetProductList(maxprice);

6. Locate the comment TODO: Return the Products view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the products collection to the Products view for display.
Your code should resemble the following code example.

[Visual Basic]
Return View("Products", products)

[Visual C#]
return View("Products", products);

Your completed code should resemble the following code example.

[Visual Basic]
<AcceptVerbs(HttpVerbs.Post)> _
Public Function BrowsePrice(ByVal productResponse As ProductResponse) As ActionResult
If String.IsNullOrEmpty(productResponse.Price) Then
ModelState.AddModelError("error", "Please enter a price")
End If
If ModelState.IsValid Then
Dim maxprice As Decimal
Dim valid As Boolean = Decimal.TryParse(productResponse.Price, maxprice)

Dim dal As New ProductDataAccessLayer()

Dim products As List(Of ProductDataObject) =


dal.GetProductList(maxprice)

Return View("Products", products)

Else
Return View("Index")
End If

End Function

[Visual C#]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult BrowsePrice(ProductResponse productResponse)
{
if (string.IsNullOrEmpty(productResponse.Price))
{
ModelState.AddModelError("error", "Please enter a price");
}
if (ModelState.IsValid)
{
Lab Answer Key: Using ADO.NET 33

decimal maxprice;
bool valid =
decimal.TryParse(productResponse.Price, out maxprice);

ProductDataAccessLayer dal = new ProductDataAccessLayer();


List<ProductDataObject> products =
dal.GetProductList(maxprice);
return View("Products", products);
}
else
{
return View("Index");
}
}

Task 4: Implement the Products controller action


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the Products method by double-clicking the first comment TODO: Create a new instance of
the ProductDataAccessLayer in the task list. The purpose of this method is to retrieve a list of all
products.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
Your code should resemble the following code example.

[Visual Basic]
Dim dal As New ProductDataAccessLayer()

[Visual C#]
ProductDataAccessLayer dal = new ProductDataAccessLayer();

4. Locate the comment TODO: Create a list of all products.


5. Remove the comment and replace it with a statement that instantiates a generic list of
ProductDataObject objects, named products. Initialize this object with the data returned by the
dal.GetProductList method. Pass the product ID that is passed as the parameter to this method to
the GetProductList method.
Your code should resemble the following code example.

[Visual Basic]
Dim _products As List(Of ProductDataObject) = dal.GetProductList()

[Visual C#]
List<ProductDataObject> products = dal.GetProductList();

6. Locate the comment TODO: Return the Products view, passing the products list to the model
context.
34 Lab Answer Key: Using ADO.NET

7. Remove the comment and replace it with the following statement that returns from the method and
passes the products collection to the current view for display.

[Visual Basic]
Return View(_products)

[Visual C#]
return View(products);

Your completed code should resemble the following code example.

[Visual Basic]
Public Function Products() As ActionResult

Dim dal As New ProductDataAccessLayer()


Dim _products As List(Of ProductDataObject) = dal.GetProductList()
Return View(_products)

End Function

[Visual C#]
public ActionResult Products()
{

ProductDataAccessLayer dal = new ProductDataAccessLayer();


List<ProductDataObject> products = dal.GetProductList();
return View(products);

Task 5: Implement the Details controller action


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Locate the Details method by double-clicking the first comment TODO: Create a new instance of
the ProductDataAccessLayer in the task list. The purpose of this method is to retrieve the details of
the product that has the product ID that is specified by the user.
3. Remove the comment TODO: Create a new instance of the ProductDataAccessLayer and replace
it with a statement that creates a new instance of the ProductDataAccessLayer class named dal.
Your code should resemble the following code example.

[Visual Basic]
Dim dal As New ProductDataAccessLayer()

[Visual C#]
ProductDataAccessLayer dal = new ProductDataAccessLayer();

4. Locate the comment TODO: Get the product specified by id.


Lab Answer Key: Using ADO.NET 35

5. Remove the comment and replace it with a statement that creates a ProductDataObject object
named product. Initialize this object with the value returned by the dal.GetProduct method, passing
the value of id as a parameter to this method.
Your code should resemble the following code example.

[Visual Basic]
Dim product As ProductDataObject = dal.GetProduct(id)

[Visual C#]
ProductDataObject product = dal.GetProduct(id);

6. Locate the comment TODO: Return the Details view, passing the products list to the model
context.
7. Remove the comment and replace it with the following statement that returns from the method and
passes the product object to the current view for display.

[Visual Basic]
Return View(product)

[Visual C#]
return View(product);

Your completed code should resemble the following code example.

[Visual Basic]
Public Function Details(ByVal id As Integer) As ActionResult

Dim dal As New ProductDataAccessLayer()


Dim product As ProductDataObject =
dal.GetProduct(id)
Return View(product)

End Function

[Visual C#]
public ActionResult Details(int id)
{
ProductDataAccessLayer dal = new ProductDataAccessLayer();
ProductDataObject product = dal.GetProduct(id);
return View(product);
}

Task 6: Test the data access layer by using the Web application
1. Start the Test Application application.
On the Debug menu, click Start Without Debugging.
Windows Internet Explorer starts and displays the Test Application page:
2. On the Test Application page, click Browse all Products.
Internet Explorer displays a list of the products in the AdventureWorks database.
36 Lab Answer Key: Using ADO.NET

3. Click Home to return to the first page.


4. In the Browse Products by Color box, type Silver and then click Browse.
Internet Explorer displays a list of silver products.
5. Click Details adjacent to the first product.
Internet Explorer displays the details of the selected product.
6. Click Home.
7. In the Browse Products by Maximum Price box, type 100 and then click Browse.
Internet Explorer displays a list of products where the list price is not greater than 100.
8. Close Internet Explorer, and then return to Visual Studio.
9. Close the solution:
On the File menu, click Close Solution.

Exercise 3: Enabling Data Modifications


Task 1: Open the starter project for this exercise
Open the solution, Using ADO.NET.sln, in the E:\Labfiles\Lab14\VB\Ex3\Starter\Using ADO.NET or
E:\Labfiles\Lab14\CS\Ex3\Starter\Using ADO.NET folder. This solution contains a new version of the
data access layer code that you will implement by using DataSets and a Windows Forms application
that you will use to test the data access layer code:
a. On the File menu, point to Open and then click Project/Solution.
b. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab14\VB\Ex3\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and then
click Open.
c. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab14\CS\Ex3\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and then
click Open.

Task 2: Create the ProductDataSet DataSet


1. Add a new DataSet class named ProductDataSet to the DAL project:
a. In Solution Explorer, right-click the DAL project, point to Add, and then click New Item.
b. In the Add New Item DAL dialog box, in the templates list, click DataSet.
c. In the Name box, type ProductDataSet.xsd and then click Add.
2. Add the Product (Production) table in the AdventureWorks database to the ProductDataSet.xsd file:
a. If Server Explorer is not already visible, on the View menu, click Server Explorer.
b. In Server Explorer, right-click Data Connections, and then click Add Connection.
c. In the Choose Data Source dialog box, in the Data source list, click Microsoft SQL Server, and
then click Continue.
d. In the Add Connection dialog box, in the Server name box, type 10265A-GEN-
DEV\sqlexpress.
e. In the Select or enter a database name box, enter AdventureWorks and then click OK.
f. In Server Explorer, expand Data Connections, and then double-click 10265A-GEN-
DEV\sqlexpress.AdventureWorks.dbo.
g. Expand Tables, and then drag Product (Production) onto the ProductDataSet.xsd window.
3. Add the ProductListPriceHistory (Production) table to the ProductDataSet.xsd file:
Lab Answer Key: Using ADO.NET 37

In Server Explorer, drag ProductListPriceHistory (Production) onto the ProductDataSet.xsd


window.
4. Save the ProductDataSet.xsd file, and then close it:
a. On the File menu, click Save ProductDataSet.xsd.
b. On the File menu, click Close.

5. Review the ProductDataProvider class:


If you are using Visual Basic, in Solution Explorer, double-click ProductDataProvider.vb.
If you are using Visual C#, in Solution Explorer, double-click ProductDataProvider.cs.
This class is a singleton that you can use to construct a ProductDataSet DataSet in a controlled
manner.

Task 3: Implement the GetProductList method


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the ProductDataAccessLayer file by double-clicking the comment TODO: Add code to return
a list of all products. This task is located in the GetProductList method.
3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable class.

The ProductDataAccessLayer class provides a table adapter called productDataAdapter that


you can use to connect to the AdventureWorks database and retrieve product information. Use
the Fill method of the productDataAdapter object.

b. Create a new instance of a generic list of ProductDataObject objects, named products.


c. Iterate through the DataRow collection in the ProductDataProvider.DataSet.Product
DataTable and add the product found in each row to the products collection.

The ProductDataAccessLayer class contains a helper method called


BusinessObjectFromDataRow. You can use this method to construct a ProductDataObject
object from a DataRow object that contains the data for a product. You pass the DataRow
object as the parameter, and the method returns a ProductDataObject object.

d. Return the products collection.

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' This method returns all of the Products from the Adventure Works
''' Database
''' </summary>
''' <returns>A list of ProductDataObject's </returns>
Public Function GetProductList() As List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList

productAdapter.Fill(ProductDataProvider.DataSet.Product)
Dim products As New List(Of ProductDataObject)

For Each row As DataRow In


ProductDataProvider.DataSet.Product.Rows
products.Add(BusinessObjectFromDataRow(row))
38 Lab Answer Key: Using ADO.NET

Next
Return products
End Function

[Visual C#]
/// <summary>
/// This method returns all of the products from the AdventureWorks
/// database.
/// </summary>
/// <returns>A list of ProductDataObject objects.</returns>
public List<ProductDataObject> GetProductList()
{
productAdapter.Fill(ProductDataProvider.DataSet.Product);
List<ProductDataObject> products = new List<ProductDataObject>();

foreach (DataRow row in ProductDataProvider.DataSet.Product.Rows)


{
products.Add(BusinessObjectFromDataRow(row));
}
return products;
}

Task 4: Implement the GetProduct method


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. In the Task View window, locate the comment TODO: Add code to return a single product.
Double-click this comment to go to the GetProduct method.
3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable.
b. Retrieve the DataRow object for the product from the ProductDataProvider.DataSet.Product
DataTable object by using the FindByProductID method of this DataTable object, passing the
productID variable as a parameter.
The FindByProductID method was generated by the Dataset Designer.

c. If the DataRow object is not null, construct a ProductDataObject object from the DataRow
object by using the BusinessObjectFromDataRow helper method, and then return this
ProductDataObject object. Otherwise, return a null value.

Your code should resemble the following code example.

[Visual Basic]

''' <summary>
''' Return a specified product. Products are identified by the Product ID
''' </summary>

''' <param name="productID">The Product ID to return</param>


''' <returns>A ProductDataObject</returns>

Public Function GetProduct(ByVal productID As Integer) As ProductDataObject _


Implements IProductDataAccessLayer.GetProduct

productAdapter.Fill(ProductDataProvider.DataSet.Product)
Lab Answer Key: Using ADO.NET 39

Dim row As DataRow = ProductDataProvider.DataSet.Product.FindByProductID(productID)

If row IsNot Nothing Then


Return BusinessObjectFromDataRow(row)

Else
Return Nothing
End If

End Function

[Visual C#]
/// <summary>
/// Return a specified product. Products are identified by the
/// productID.
/// </summary>
/// <param name="productID">The Product ID to return.</param>
/// <returns>A ProductDataObject</returns>
public ProductDataObject GetProduct(int productID)
{
productAdapter.Fill(ProductDataProvider.DataSet.Product);
DataRow row = ProductDataProvider.DataSet.Product.FindByProductID(
productID);

if (row != null)
{
return BusinessObjectFromDataRow(row);
}
else
{
return null;
}
}

Task 5: Implement the overloaded GetProductList methods


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. In the Task View window, locate the comment TODO: Add code to return a list of products
matching a given color. Double-click this comment to go to the GetProductList method that
retrieves products that match a specified color.
3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable object.
b. Create a new instance of a generic list of ProductDataObject objects, named products.
c. Define a Language-Integrated Query (LINQ) to DataSet query that retrieves all rows from the
ProductDataProvider.DataSet.Product DataTable object where the Color column is not null
and matches the color that is specified as the parameter to this method.
d. Iterate through the results returned by the LINQ to DataSet query and add each product found
to the products collection. Use the BusinessObjectFromDataRow helper method to convert
each DataRow object to a ProductDataObject object.
e. Return the products collection.
40 Lab Answer Key: Using ADO.NET

Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' An overload of the GetProductList method that returns a list of products that match
the specified color.
''' </summary>
''' <param name="color">The Color of the products to return</param>
''' <returns>A list of ProductDataObject's that are match the specified color.</returns>
Public Function GetProductList(ByVal color As String) As List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList
If String.IsNullOrEmpty(color) Then
Return Nothing
End If
productAdapter.Fill(ProductDataProvider.DataSet.Product)
Dim products As New List(Of ProductDataObject)()
Dim rows = From p In ProductDataProvider.DataSet.Product _
Where Not p.IsColorNull() AndAlso p.Color = color _
Select p
For Each row As ProductDataSet.ProductRow In rows
products.Add(BusinessObjectFromDataRow(row))
Next
Return products
End Function

[Visual C#]
/// <summary>
/// An overload of the GetProductList method that returns a list of
/// products that match the specified color.
/// </summary>
/// <param name="color">The color of the products to return.</param>
/// <returns>A list of ProductDataObject objects that match the specified
color.</returns>
public List<ProductDataObject> GetProductList(string color)
{
if (string.IsNullOrEmpty(color))
{
return null;
}

productAdapter.Fill(ProductDataProvider.DataSet.Product);
List<ProductDataObject> products = new List<ProductDataObject>();

var rows = from p in ProductDataProvider.DataSet.Product


where !p.IsColorNull() && p.Color == color
select p;

foreach (ProductDataSet.ProductRow row in rows)


{

products.Add(BusinessObjectFromDataRow(row));
}

return products;
}

4. In the Task View window, locate the comment TODO: Add code to return a list of products
filtered on list price. Double-click this comment to go to the GetProductList method that retrieves
products that have a list price not exceeding the price that is specified as the parameter to this
method.
Lab Answer Key: Using ADO.NET 41

5. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable.
b. Create a new instance of a generic list of ProductDataObject objects, named products.
c. Define a LINQ to DataSet query that retrieves all rows from the
ProductDataProvider.DataSet.Product DataTable where the list price is not greater than the
value that is specified as the parameter to this method.
d. Iterate through the results returned by the LINQ to DataSet query and add each product found
to the products collection. Use the BusinessObjectFromDataRow helper method to convert
each DataRow object to a ProductDataObject object.
e. Return the products collection.
Your code should resemble the following code example.

[Visual Basic]
''' <summary>
''' An overload of the GetProductList method that returns a list of
''' products who's list price is less than or equal to the specified
''' price.
''' </summary>
''' <param name="maxListPrice">The maximum list price</param>
''' <returns>A list of ProductDataObject's who's list price is less
''' than or equal to the specified maxListPrice</returns>
Public Function GetProductList(ByVal maxListPrice As Decimal) As List(Of
ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList

productAdapter.Fill(ProductDataProvider.DataSet.Product)
Dim products As New List(Of ProductDataObject)()

Dim rows = From p In ProductDataProvider.DataSet.Product _


Where p.ListPrice <= maxListPrice _
Select p

For Each row As ProductDataSet.ProductRow In rows


products.Add(BusinessObjectFromDataRow(row))
Next
Return products

End Function

[Visual C#]
/// <summary>
/// An overload of the GetProductList method that returns a list of
/// products with a list price that is less than or equal to the
/// specified price.
/// </summary>
/// <param name="maxListPrice">The maximum list price.</param>
/// <returns>A list of ProductDataObject objects with a list price
/// that is less than or equal to the specified
/// maxListPrice.</returns>

public List<ProductDataObject> GetProductList(decimal maxListPrice)

{
productAdapter.Fill(ProductDataProvider.DataSet.Product);
List<ProductDataObject> products = new List<ProductDataObject>();

var rows = from p in ProductDataProvider.DataSet.Product


42 Lab Answer Key: Using ADO.NET

where p.ListPrice <= maxListPrice


select p;

foreach (ProductDataSet.ProductRow row in rows)

{
products.Add(BusinessObjectFromDataRow(row));
}

return products;
}

Task 6: Implement the UpdateProduct method


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. In the Task View window, locate the comment TODO: Add code to update the product in the
database. Double-click this comment to go to the UpdateProduct method.
3. Review the code in the UpdateProduct method.
Most of the code for this method has already been provided for you.
This method updates a product in the Product table. If the product list price has changed, a new row
is added to the ProductListPriceHistory table. This table maintains a list of all changes to a product's
list price. If the update to the Product or ProductListPriceHistory tables fails, the transaction is
rolled back. If a concurrency exception occurs, it is rethrown to the client application.
4. Remove the comment and replace it with code that performs the following tasks:
a. Invoke the productAdapter.Update method. Pass row as the parameter.
b. If the updatePrice variable is true, call the listPriceAdapter.Update method to update the list
price history. You should use the listPriceAdapter.Update method and pass the
ProductDataProvider.DataSet.ProductListPriceHistory DataTable object as the parameter.

Your code should resemble the following code example.

[Visual Basic]
...
If Not row.Table.HasErrors Then

productAdapter.Update(row)

If updatePrice Then
listPriceAdapter.Update(
ProductDataProvider.DataSet.ProductListPriceHistory)
End If

ts.Complete()
success = True
...

[Visual C#]

...

if (!row.Table.HasErrors)
Lab Answer Key: Using ADO.NET 43

{
productAdapter.Update(row);

if (updatePrice)
{
listPriceAdapter.Update(
ProductDataProvider.DataSet.ProductListPriceHistory);
}

ts.Complete();
success = true;
}
...

Task 7: Implement the DeleteProduct method


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. In the Task View window, locate the comment TODO: Add code to delete a product from the
database. Double-click this comment to go to the DeleteProduct method.
3. Review the code in the DeleteProduct method.
Most of the code for this method has already been provided for you.
This deletes a product from the Product table along with any related rows in the
ProductListPriceHistory table. If the updates to the Product or ProductListPriceHistory tables fail,
the transaction is automatically rolled back. If a concurrency exception occurs, it is rethrown to the
client application.
4. Remove the comment and replace it with code that performs the following tasks:
a. Create a variable of type DataRelation named relation that refers to the relationship between
the Product DataTable object and the ProductListPriceHistory DataTable object. You can find
this relationship in the ChildRelations collection of the Product DataTable object. It has the
name "FK_ProductListPriceHistory_Product_ProductID".
When the code deletes a product, it will automatically delete all related price history rows to
prevent any referential integrity problems. This strategy is known as a cascading delete.
b. Create a DataRow variable called row that refers to the product being deleted in the Product
DataTable. You can locate this product by using the FindByProductID method of the Product
DataTable.
c. Find all child rows in the ProductListPriceHistory DataTable object for the product identified
by the row table, and store a reference to these child rows in the toRemove DataRow array. You
can find these child rows by using the GetChildRows method of row object; specify relation as
the parameter to this method.
d. Iterate through the rows in the toRemove array and mark them for deletion.
e. If there is at least one row in the toRemove array, delete all marked ProductListPriceHistory
rows from the database. Use the Update method of the listPriceAdapter object and provide the
toRemove array as the parameter to this method.
f. Locate the DataRow object for the product to be deleted in the Product DataTable object and
mark it for deletion.
g. Delete the product from the database. Use the Update method of the productAdapter
TableAdapter and specify the Product DataTable as the parameter.
44 Lab Answer Key: Using ADO.NET

h. Commit the transaction and set the success variable to true. Note that if either of the Update
methods fail, they will throw an exception, the transaction will be automatically rolled back, and
the success variable will remain at its default value of false.

Your code should resemble the following code example.

[Visual Basic]
...
Try

Dim toRemove As DataRow() = Nothing

Dim relation As DataRelation =


ProductDataProvider.DataSet.Product.ChildRelations(
"FK_ProductListPriceHistory_Product_ProductID")
Dim row As DataRow =
ProductDataProvider.DataSet.Product.FindByProductID(
product.ProductID)

toRemove = row.GetChildRows(relation)

For Each r As DataRow In toRemove


r.Delete()
Next

If toRemove IsNot Nothing Then


Dim x As Integer = listPriceAdapter.Update(toRemove)
End If

ProductDataProvider.DataSet.Product.FindByProductID(
product.ProductID).Delete()

productAdapter.Update(ProductDataProvider.DataSet.Product)

ts.Complete()

success = True

...
[Visual C#]
...
try
{
DataRow[] toRemove = null;
DataRelation relation =
ProductDataProvider.DataSet.Product.ChildRelations[
"FK_ProductListPriceHistory_Product_ProductID"];
DataRow row = ProductDataProvider.DataSet.Product.
FindByProductID(product.ProductID);

toRemove = row.GetChildRows(relation);
foreach (DataRow r in toRemove)
{
r.Delete();
}
if (toRemove != null)
{
int x = listPriceAdapter.Update(toRemove);
}
ProductDataProvider.DataSet.Product.
FindByProductID(product.ProductID).Delete();
productAdapter.Update(
ProductDataProvider.DataSet.Product);
Lab Answer Key: Using ADO.NET 45

ts.Complete();
success = true;
}
...

5. Build the project and correct any errors:


In Solution Explorer, right-click the DAL project, and then Build. Correct any errors.

Task 8: Modify the test application


1. Review the task list:
a. If the task list is not already visible, on the View menu, click Task List.
b. If the task list is showing User Tasks, in the Categories list, click Comments.
2. Open the Form1 file by double-clicking the comment TODO: Add code to get a product with a
given product ID. This task is located in the btnSearchByProduct_Click method.
3. Remove the comment and replace it with code to create a new ProductDataObject object named
prod. Initialize the prod object with the value returned by the dal.GetProduct method, passing the
prodID variable as a parameter.
Your code should resemble the following code example.

[Visual Basic]
...
Dim prod As ProductDataObject = dal.GetProduct(prodID)
...

[Visual C#]
...
ProductDataObject prod = dal.GetProduct(prodID);
...

4. Locate the comment TODO: Add code to get a list of products matching a given color.
5. Remove the comment and replace it with code to set the value of the products variable by calling the
dal.GetProductList method. Use the txtColor.Text property as the parameter.
Your code should resemble the following code example.

[Visual Basic]
...
products = dal.GetProductList(txtColor.Text)
...

[Visual C#]
...
products = dal.GetProductList(txtColor.Text);
...

6. Locate the comment TODO: Add code to get a list of products filtered by list price.
7. Remove the comment and replace it with code to set the value of the products variable by calling the
dal.GetProductList method and passing the maxPrice variable as a parameter.
46 Lab Answer Key: Using ADO.NET

Your code should resemble the following code example.

[Visual Basic]
...
products = dal.GetProductList(maxPrice)
...

[Visual C#]
...
products = dal.GetProductList(maxPrice);
...

8. Locate the comment TODO: Add code to update products. This task is located in the
btnSave_Click method and is run when the user clicks Save Changes on the form.
9. Remove the comment and replace it with code to call the dal.UpdateProduct method. The
UpdateProduct method expects a ProductDataObject object as a parameter; use the object created
for each iteration of the enclosing loop.
Your code should resemble the following code example.

[Visual Basic]
...
For Each p As ProductDataObject In updatedProducts

dal.UpdateProduct(p)

Next
...

[Visual C#]
...
foreach (ProductDataObject p in updatedProducts)
{
dal.UpdateProduct(p);
}
...

10. Locate the comment TODO: Add code to delete products.


11. Remove the comment and replace it with code to invoke the dal.DeleteProduct method. The
DeleteProduct method expects a ProductDataObject object as a parameter; use the object created
for each iteration of the enclosing loop.
Your code should resemble the following code example.

[Visual Basic]

...
For Each p As ProductDataObject In deletedProducts

dal.DeleteProduct(p)
Next
...
Lab Answer Key: Using ADO.NET 47

[Visual C#]
...
foreach (ProductDataObject p in deletedProducts)
{
dal.DeleteProduct(p);
}
...

12. Build the project and correct any errors:


a. On the Build menu, click Build Test Application.
b. If you are using Visual Basic, you may experience build errors in ProductDataSet.Designer.vb.
These errors are caused by the file being generated incorrectly. To rectify these errors, delete the
file and regenerate it:
i.In Solution Explorer, right-click ProductDataSet.Designer.vb, and then click Delete.
ii.In the Microsoft Visual Studio dialog box, click OK.
iii.In Solution Explorer, right-click ProductDataSet.xsd, and then click Run Custom Tool.
iv. On the Build menu, click Build Test Application, and verify that the errors caused by
ProductDataSet.Designer.vb are no longer present.
v. Correct any other errors.

Task 9: Test the data access layer by using the test application
1. Start the Test Application application:
On the Debug menu, click Start Without Debugging
2. In the Data Set Test Application window, click Load All Products. This will display a list of all of the
products in the data grid.
3. In the Data Set Test Application window, in the Product ID box, type 316 and then click Search By
Product.
4. In the Data Set Test Application window, in the data grid, in the Color box, type Gold and then click
Save Changes.
5. In the Data Set Test Application window, in the Color box, type Gold and then click Search By Color.
You should see only one product listed in the data grid.
6. Select the product and delete it:
a. In the data grid, click the left column, to select the entire top row.
b. Click Delete Selected.
c. Click Save Changes.
7. Verify that the product has been deleted:
a. Click Load All Products.
b. Verify that Product ID 316 has been deleted.
8. Close the solution:
a. Close the Data Set Test Application window.
b. On the File menu, click Close Solution.
Lab Answer Key: Using LINQ to SQL 1

Module 15
Lab Answer Key: Using LINQ to SQL
Contents:
Exercise 1: Using LINQ to SQL to Build a Data Access Layer 2
Exercise 2: Updating a Database by Using a Stored Procedure 11
Exercise 3: Building a Custom Entity Class 14
2 Lab Answer Key: Using LINQ to SQL

Lab 15: Using LINQ to SQL


Exercise 1: Using LINQ to SQL to Build a Data Access Layer
Task 1: Prepare the AdventureWorks database for the lab
1. Log on to the 10265A-GEN-DEV-15 virtual machine as Student with the password Pa$$w0rd.
2. Run AWReset.bat in the E:\Labfiles folder:
a. Click Start, and then click Computer.
b. Double-click Allfiles (E:), double-click Labfiles, and then double-click AWReset.bat.
c. Wait for the batch file to finish running, and then close Windows Explorer.

Task 2: Open the starter project for this exercise


1. Open Microsoft Visual Studio 2010:
Click Start, click All Programs, click Microsoft Visual Studio 2010, and then click Microsoft
Visual Studio 2010.
2. Open the existing solution, Using ADO.NET.sln, in the E:\Labfiles\Lab15
\CS\Ex1\Starter\Using ADO.NET or E:\Labfiles\Lab15\VB\Ex1\Starter\Using ADO.NET folder:
On the File menu, point to Open, and then click Project/Solution:
i. If you are using Microsoft Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab15\VB\Ex1\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and
then click Open.
ii. If you are using Microsoft Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab15\CS\Ex1\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and
then click Open.

Task 3: Create the LINQ to SQL object model


1. Add a LINQ to SQL object model called ProductDataModel.dbml to the DAL solution:
a. In Solution Explorer, right-click DAL, point to Add, and then click New Item.
b. In the Add New Item - DAL dialog box, click LINQ to SQL Classes, in the Name box, type
ProductDataModel.dbml and then click Add.
2. Use Server Explorer to create a connection to the AdventureWorks database on the 10265A-GEN-
DEV\SQLEXPRESS computer:
a. On the View menu, click Server Explorer.
b. In Server Explorer, right-click Data Connections, and then click Add Connection.
c. In the Choose Data Source dialog box, click Microsoft SQL Server, and then click Continue.
d. In the Add Connection dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLEXPRESS
e. In the Select or enter a database name box, type AdventureWorks and then click OK.
3. Add the Product (Production) entity to the LINQ to SQL object model:
a. In Server Explorer, expand 10265a-gen-dev\sqlexpress.AdventureWorks.dbo, and then
expand Tables.
b. Drag Product (Production) to the Object Relational Designer (O/R Designer).

4. In the O/R Designer, rename the Product entity as ProductDataObject:


Lab Answer Key: Using LINQ to SQL 3

a. In the O/R Designer, click the Product entity.


b. In the Properties window, change the Name property to ProductDataObject.
5. Remove all of the columns from the ProductDataObject entity except for the following:
ProductID
Name
ProductNumber
Color
ListPrice
ModifiedDate

a. In the ProductDataObject entity, right-click the MakeFlag column, and then click Delete.
b. Repeat the previous step to remove the FinishedGoodsFlag, SafetyStockLevel,
ReorderPoint, StandardCost, Size, SizeUnitMeasureCode, WeightUnitMeasureCode,
Weight, DaysToManufacture, ProductLine, Class, Style, ProductSubcategoryID,
ProductModelID, SellStartDate, SellEndDate, DiscontinuedDate, and rowguid columns.
6. Examine the code that the O/R Designer generates:
a. In Solution Explorer, expand ProductDataModel.dbml, and then double-click
ProductDataModel.designer.vb or ProductDataModel.designer.cs .
b. Scroll down through the code and note the contents of the
ProductDataModelDataContext class and the ProductDataObject entity class.

Note If there is no option to expand ProductDataModel.dbml, ensure that the Show All Files
button is switched on in Solution Explorer.

Task 4: Implement the methods that retrieve data in the data access layer
1. In Solution Explorer, in the DAL project, open the ProductDataAccessLayer code file:
In Solution Explorer, in the DAL project, double-click ProductDataAccessLayer.vb or
ProductDataAccessLayer.cs.
2. In the IProductDataAccessLayer Members region, implement the GetProductList method to use
LINQ to SQL to retrieve all products from the database, populate a list of ProductDataObject
objects, and then return this list.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object. This is a custom


DataContext object that automatically connects to the AdventureWorks database.

Hint Create the ProductDataModelDataContext object in a using statement and add the remaining
code in this method to the body of the using statement. This will ensure that the
ProductDataModelDataContext object is correctly disposed and its resources released when the
method completes.

b. Retrieve the ProductDataObjects collection from the ProductDataModelDataContext object,


convert it into a list of ProductDataObject objects, and then return this list.
4 Lab Answer Key: Using LINQ to SQL

Your code should resemble the following code example.

[Visual Basic]
#Region "IProductDataAccessLayer Members"

Public Function GetProductList() As List(Of ProductDataObject) _


Implements IproductDataAccessLayer.GetProductList

Using db As New ProductDataModelDataContext()

Return db.ProductDataObjects.ToList()

End Using

End Function

[Visual C#]
#region IProductDataAccessLayer Members

public List<ProductDataObject> GetProductList()


{
using (ProductDataModelDataContext db =
new ProductDataModelDataContext())
{
return db.ProductDataObjects.ToList();
}
}

3. Implement the GetProduct method. Use LINQ to SQL to retrieve the product with the product ID
that matches the value that is passed as a parameter to this method, and then return this product.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Using the ProductDataObjects collection in the ProductDataModelDataContext object, find
the product that matches the product ID that is specified as the parameter, and then return it.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProduct(ByVal productID As Integer) _
As ProductDataObject _
Implements IproductDataAccessLayer.GetProduct

Using db As New ProductDataModelDataContext()

Return db.ProductDataObjects.Single(
Function(p) p.ProductID = productID)

End Using

End Function

[Visual C#]
public ProductDataObject GetProduct(int productID)
{
using (ProductDataModelDataContext db =
new ProductDataModelDataContext())
Lab Answer Key: Using LINQ to SQL 5

{
return db.ProductDataObjects.Single(p => p.ProductID == productID);
}
}

4. Implement the GetProductList method that takes a string parameter called color. Use LINQ to SQL to
retrieve a list of products with the specified color.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Define a LINQ query that finds all of the products that have the specified color from the
ProductDataObjects collection in the ProductDataModelDataContext object. The color is
specified as a string, so perform a case-insensitive match.
c. Retrieve all of the matching products into a list, and then return this list.
Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList(ByVal color As String) _
As List(Of ProductDataObject) _
Implements IproductDataAccessLayer.GetProductList

Using db As New ProductDataModelDataContext()

Dim list = From p In db.ProductDataObjects


Where String.Compare(p.Color, color, True) = 0
Select p

Return list.ToList()

End Using

End Function

[Visual C#]
public List<ProductDataObject> GetProductList(string color)
{
using (ProductDataModelDataContext db =
new ProductDataModelDataContext())
{
var list = from p in db.ProductDataObjects
where String.Compare(p.Color, color, true) == 0
select p;

return list.ToList();
}
}

5. Implement the GetProductList method that takes a decimal parameter called maxListPrice. Use LINQ
to SQL to retrieve a list of products that have a list price that is less than or equal to this value.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Define a LINQ query that finds all of the products that have a price that falls within the specified
range from the ProductDataObjects collection in the ProductDataModelDataContext object.
c. Retrieve all of the matching products into a list, and then return this list.
6 Lab Answer Key: Using LINQ to SQL

Your code should resemble the following code example.

[Visual Basic]
Public Function GetProductList(ByVal maxListPrice As Decimal) As List(Of
ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList

Using db As New ProductDataModelDataContext()

Dim list = From p In db.ProductDataObjects


Where p.ListPrice <= maxListPrice
Select p

Return list.ToList()

End Using

End Function

[Visual C#]
public List<ProductDataObject> GetProductList(decimal maxListPrice)
{
using (ProductDataModelDataContext db =
new ProductDataModelDataContext())
{
var list = from p in db.ProductDataObjects
where p.ListPrice <= maxListPrice
select p;

return list.ToList();
}
}

Task 5: Implement the methods that update data in the data access layer
1. At the top of the ProductDataAccessLayer code file, bring the System.Data.Linq namespace into
scope.
Your code should resemble the following code example.

[Visual Basic]
Imports System.Data.Linq

[Visual C#]
using System.Data.Linq;

2. Implement the UpdateProduct method. This method updates the database with the product
information passed in as the parameter. The method returns true if the update is successful;
otherwise, it returns false.
The code should perform the following tasks:

a. Create a new instance of the ProductDataModelDataContext object.


b. Retrieve the existing data for the product from the database from the ProductDataObjects
collection of the ProductDataModelDataContext object.
Lab Answer Key: Using LINQ to SQL 7

c. If this product still exists in the database, overwrite the data in the product entity object that you
have just retrieved with the data in the product object passed in as the method parameter. This
action causes the changes to be recorded and tracked by the ProductDataObjects collection in
the ProductDataModelDataContext object used to retrieve the product.

If the product has been removed from the database, throw an exception with the message "The
product has already been deleted. Reload the product list."

Hint Use the Single extension method of the ProductDataObjects collection to find the product in the
collection. This method throws an InvalidOperationException exception if no matching product is
found.

d. Using the ProductDataModelDataContext object, save the updated product back to the
database. Specify that the transaction should roll back the first time that a concurrency conflict is
detected.
e. If a ChangeConflictException exception occurs, iterate through all of the change conflict errors
that are reported, resolve them by overwriting the changes made by the user with the latest data
from the database, and then rethrow the exception.
f. If the update is successful, return the value true. Otherwise, return the value false.
Your code should resemble the following code example.

[Visual Basic]
Public Function UpdateProduct(ByVal product As ProductDataObject) _
As Boolean _
Implements IProductDataAccessLayer.UpdateProduct

Dim result As Boolean = False


Using db As New ProductDataModelDataContext()

Try
Dim prod As ProductDataObject =
db.ProductDataObjects.Single(
Function(p) p.ProductID = product.ProductID)

prod.Name = product.Name
prod.ProductNumber = product.ProductNumber
prod.Color = product.Color
prod.ListPrice = product.ListPrice
prod.ModifiedDate = DateTime.Today

db.SubmitChanges(ConflictMode.FailOnFirstConflict)
result = True

Catch ex As ChangeConflictException

For Each oce As ObjectChangeConflict In db.ChangeConflicts


oce.Resolve(RefreshMode.OverwriteCurrentValues)
Next

Throw

Catch ex As InvalidOperationException

Throw New Exception("The product has already " & _


"been deleted. Reload the product list")

End Try
8 Lab Answer Key: Using LINQ to SQL

Return result

End Using

End Function

[Visual C#]
public bool UpdateProduct(ProductDataObject product)
{
bool result = false;

using (ProductDataModelDataContext db =
new ProductDataModelDataContext())
{
try
{
ProductDataObject prod = db.ProductDataObjects.Single(
p => p.ProductID == product.ProductID);

prod.Name = product.Name;
prod.ProductNumber = product.ProductNumber;
prod.Color = product.Color;
prod.ListPrice = product.ListPrice;
prod.ModifiedDate = DateTime.Today;

db.SubmitChanges(ConflictMode.FailOnFirstConflict);
result = true;
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict oce in db.ChangeConflicts)
{
oce.Resolve(RefreshMode.OverwriteCurrentValues);
}
throw;
}
catch (InvalidOperationException ex)
{
throw new Exception("The product has already been deleted. Reload the
product list");
}
return result;
}
}

3. Implement the DeleteProduct method. This method removes the product passed in as the parameter
from the database. The method returns true if the deletion is successful; otherwise, it returns false.
The code should perform the following tasks:
a. Create a new instance of the ProductDataModelDataContext object.
b. Retrieve the existing data for the product from the database from the ProductDataObjects
collection of the ProductDataModelDataContext object.
c. If this product still exists in the database, mark the product for deletion in the
ProductDataObjects collection.
If the product has already been removed from the database, throw an exception with the
message "The product has already been deleted. Reload the product list."
Lab Answer Key: Using LINQ to SQL 9

d. Using the ProductDataModelDataContext object, save the updated product back to the
database. Specify that the transaction should roll back the first time that a concurrency conflict is
detected.
e. If a ChangeConflictException exception occurs, iterate through all of the change conflict errors
that are reported, resolve them by forcibly deleting the conflicting rows from the database, and
then rethrow the exception.
f. If the deletion is successful, return the value true; otherwise, return the value false.
Your code should resemble the following code example.

[Visual Basic]
Public Function DeleteProduct(ByVal product As ProductDataObject) _
As Boolean _
Implements IproductDataAccessLayer.DeleteProduct
Dim result As Boolean = False
Using db As New ProductDataModelDataContext()

Try

Dim prod As ProductDataObject =


db.ProductDataObjects.Single(
Function(p) p.ProductID = product.ProductID)

db.ProductDataObjects.DeleteOnSubmit(prod)
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
result = True

Catch ex As ChangeConflictException

For Each oce As ObjectChangeConflict In db.ChangeConflicts

oce.Resolve(RefreshMode.OverwriteCurrentValues)

Next

Throw

Catch ex As InvalidOperationException

Throw New Exception("The product has already " & _


"been deleted. Reload the product list.")

End Try
Return result
End Using

End Function

[Visual C#]
public bool DeleteProduct(ProductDataObject product)
{
bool result = false;

using (ProductDataModelDataContext db =
new ProductDataModelDataContext())
{
try
{
ProductDataObject prod = db.ProductDataObjects.Single(
p => p.ProductID == product.ProductID);
10 Lab Answer Key: Using LINQ to SQL

db.ProductDataObjects.DeleteOnSubmit(prod);
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
result = true;
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict oce in db.ChangeConflicts)
{
oce.Resolve(RefreshMode.OverwriteCurrentValues);
}
throw;
}
catch (InvalidOperationException ex)
{
throw new Exception("The product has already been deleted. Reload the product
list.");
}
return result;
}
}

4. Build the solution and correct any errors.


On the Build menu, click Build Solution.

Task 6: Test the data access layer


1. Start the test application that is provided with the solution in Debug mode. This application is the
Web application that you used in the previous lab:
a. In Solution Explorer, right-click Test Application, point to Debug, and then click Start New
Instance.
b. If the Debugging Not Enabled dialog box appears, click Modify the Web.config file to enable
debugging, and then click OK.
2. In the test application, click Browse all Products. Verify that the Products screen appears displaying a
list of products from the database.
This operation uses the GetProductList method in the data access layer.
3. Click the Details link adjacent to a product, and then verify that the details for that product are
displayed.
This operation uses the GetProduct method in the data access layer.
4. Click the Home link.
5. In the Browse Products by Color box, type Black and then click Browse. Verify that a list of black
products is displayed.
This operation uses the overloaded GetProductList method that takes a string parameter in the data
access layer.
6. Click the Home link.
7. In the Browse Products by Maximum Price box, type 25 and then click Browse. Verify that a list of
products that have a list price less than or equal to 25 is displayed.
This operation uses the overloaded GetProductList method that takes a decimal parameter in the
data access layer.
8. Close the test application and return to Visual Studio.
Lab Answer Key: Using LINQ to SQL 11

9. Start the Windows Forms test application in Debug mode. This application is the Windows Forms
application that you used in the previous lab:
In Solution Explorer, right-click Windows Forms Test Application, point to Debug, and then
click Start New Instance.
10. In the Data Set Test Application window, click Load All Products. Verify that the data grid is
populated with the details of products from the database.
This operation uses the GetProductList method in the data access layer.
11. In the Product ID box, type 316 and then click Search By Product. Verify that the details of product
316 are displayed.
This operation uses the GetProduct method in the data access layer.
12. In the Color box, type Black and then click Search By Color. Verify that a list of black products is
displayed. This should be the same list of products that was displayed by the Web application earlier.
This operation uses the overloaded GetProductList method that takes a string parameter in the data
access layer.
13. In the Maximum Price box, type 25 and then click Search By Price. Verify that a list of products with
a price less than or equal to 25 is displayed.
This operation uses the overloaded GetProductList method that takes a decimal parameter in the
data access layer.
14. Click Delete Selected to remove the first item displayed in the data grid.
15. Change the Color field of any other product displayed in the data grid.
16. Click Save Changes. This button saves the changes to the database and then redisplays the entire list
of products. Verify that the changes are saved and no exceptions are thrown.
This operation uses the UpdateProduct and DeleteProduct methods in the data access layer.
17. Close the Data Set Test Application window, and then return to Visual Studio.
18. The DAL Unit Tests project contains the same unit tests that you used in the previous lab. Run all of
the unit tests and verify that they all pass:
On the Test menu, point to Run, and then click All Tests in Solution.
19. Close the solution:
On the File menu, click Close Solution.

Exercise 2: Updating a Database by Using a Stored Procedure


Task 1: Create the database objects for this exercise
1. Open the CustomUpdateProcedure.sql file in the E:\Labfiles\Lab15\CS\Ex2
\SQL or E:\Labfiles\Lab15\VB\Ex2\SQL folder.

This script adds a new table called ProductChangeHistory to the AdventureWorks database, and
creates a stored procedure called productUpdateProduct. This stored procedure takes parameters
that correspond to the columns in the Product entity class and uses them to update the Product
table in the database. The stored procedure also adds a row to the ProductChangeHistory table,
recording an audit trail of changes made to products:
On the File menu, point to Open, and then click File:
12 Lab Answer Key: Using LINQ to SQL

i. If you are using Visual Basic, in the Open File dialog box, move to the
E:\Labfiles\Lab15\VB\Ex2\SQL folder, click CustomUpdateProcedure.sql, and then click
Open.
ii. If you are using Visual C#, in the Open File dialog box, move to the
E:\Labfiles\Lab15\CS\Ex2\SQL folder, click CustomUpdateProcedure.sql, and then click
Open.

2. Run the script. Connect to the 10265A-GEN-DEV\SQLExpress Microsoft SQL Server instance when
prompted:

a. On the Data menu, point to Transact-SQL Editor, and then click Execute SQL.
b. In the Connect to Database Engine dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress and then click Connect.
c. Verify that the commands in the script complete successfully.

3. Close the CustomUpdateProcedure.sql file:


On the File menu, click Close.

Task 2: Open the starter project for this exercise


Open the existing solution, Using ADO.NET.sln, in the E:\Labfiles\Lab15\VB\Ex2\Starter\Using
ADO.NET or E:\Labfiles\Lab15\CS
\Ex2\Starter\Using ADO.NET folder.
This solution is a copy of the completed solution from Exercise 1:
On the File menu, point to Open, and then click Project/Solution:
i. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab15\VB\Ex2\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and
then click Open.
ii. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab15\CS\Ex2\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and
then click Open.

Task 3: Use a stored procedure to update data


1. Add the productUpdateProduct stored procedure in the AdventureWorks database to the LINQ to
SQL object model.
This action adds a method called productUpdateProduct to the DataContext object for the LINQ to
SQL object model that an application can use to run the stored procedure:

a. In Solution Explorer, double-click ProductDataModel.dbml.


b. On the View menu, click Server Explorer.
c. In Server Explorer, expand 10265a-gen-dev\sqlexpress.AdventureWorks.dbo, expand Stored
Procedures, and then drag the productUpdateProduct stored procedure to the O/R Designer
window.
2. Modify the ProductDataObject entity class to use the productUpdateProduct stored procedure to
update data instead of using a SQL UPDATE statement by performing the following tasks:
Configure the Update property of the ProductDataObject entity class and customize the update
behavior to use the stored procedure.
Map the current values of the fields in the ProductDataObject entity class to the parameters
passed to the stored procedure.
Lab Answer Key: Using LINQ to SQL 13

You can do this by performing the following steps:

a. In the O/R Designer window, click the ProductDataObject entity class.


b. In the Properties window, click the Update property, and then click the ellipsis button that
appears.
c. In the Configure Behavior dialog box, click Customize, in the drop-down list, click
productUpdateProduct.
d. Verify that each of the method arguments are mapped to the corresponding Current properties
in the entity class, and then click OK.
3. Build the solution and correct any errors:
On the Build menu, click Build Solution.

Task 4: Test the data access layer


1. Open the Transact-SQL editor, and then connect to the 10265A-GEN-DEV\SQLExpress SQL Server
instance:
a. On the Data menu, point to Transact-SQL Editor, and then click New Query Connection.
b. In the Connect to Server dialog box, in the Server name box, type 10265A-GEN-
DEV\SQLExpress and then click Connect.
2. Query the Production.ProductChangeHistory table in the AdventureWorks database and verify that
it contains no rows:
a. In the SQL Editor window, type the commands in the following code example.

use AdventureWorks
go
select * from Production.ProductChangeHistory
go

b. Right-click the SQL Editor window, and then click Execute SQL. Verify that the commands run
successfully, but return no data.
3. Start the Windows Forms test application in Debug mode:
In Solution Explorer, right-click Windows Forms Test Application, point to Debug, and then
click Start New Instance.
4. In the Data Set Test Application window, click Load All Products.
5. Change the Color field of any other product displayed in the data grid.
6. Click Save Changes. Verify that the changes are saved and no exceptions are thrown.
7. Close the Data Set Test Application window, and then return to Visual Studio.
8. In the Transact-SQL Editor window, requery the Production.ProductChangeHistory table in the
AdventureWorks database. The data displayed should include the original data for the product that
you changed (the value in the Color column should be the original color):
Right-click the SQL Editor window, and then click Execute SQL.
9. Close the solution:
a. On the File menu, click Close Solution.
b. In the Microsoft Visual Studio dialog box, click No.
14 Lab Answer Key: Using LINQ to SQL

Exercise 3: Building a Custom Entity Class


Task 1: Open the starter project for this exercise
Open the existing solution, Using ADO.NET.sln, in the E:\Labfiles\Lab15\CS\Ex3\Starter\Using
ADO.NET or E:\Labfiles\Lab15\VB\Ex3\Starter\Using ADO.NET folder.
This solution contains a version of the data access layer that was not built by using an entity class or
DataContext object that the O/R Designer generated. The methods in the data access layer use an
ordinary DataContext object to manage collections of ProductDataObject entity objects. The
ProductDataObject entity class has not yet been defined. You will implement this class in this
exercise:
On the File menu, point to Open, and then click Project/Solution:
i. If you are using Visual Basic, in the Open Project dialog box, move to the
E:\Labfiles\Lab15\VB\Ex3\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and
then click Open.
ii. If you are using Visual C#, in the Open Project dialog box, move to the
E:\Labfiles\Lab15\CS\Ex3\Starter\Using ADO.NET folder, click Using ADO.NET.sln, and
then click Open.

Task 2: Create the entity class


1. Add a new class called ProductDataObject to the DAL project:
a. In Solution Explorer, right-click the DAL project, point to Add, and then click Class.
b. In the Add New Item DAL dialog box, in the Name box, type ProductDataObject and then
click Add.
2. At the top of the ProductDataObject code file, bring the System.Data.Linq.Mapping and
System.ComponentModel namespaces into scope.
Your code should resemble the following code example.

[Visual Basic]
Imports System.Data.Linq.Mapping
Imports System.ComponentModel

[Visual C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.ComponentModel;

3. Mark the ProductDataObject class as an entity class that is associated with the
AdventureWorks.Production.Product table.
Your code should resemble the following code example.

[Visual Basic]
Imports System.Data.Linq.Mapping
Imports System.ComponentModel

<Table(Name:="AdventureWorks.Production.Product")> _
Lab Answer Key: Using LINQ to SQL 15

Public Class ProductDataObject

End Class

[Visual C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.ComponentModel;
namespace DAL
{

[Table(Name = "AdventureWorks.Production.Product")]
class ProductDataObject
{

}
}

4. Make the ProductDataObject class public (if it is not already public), and then specify that it
implements the IDataErrorInfo interface.
Your code should resemble the following code example.

[Visual Basic]
Imports System.Data.Linq.Mapping
Imports System.ComponentModel

<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

End Class

[Visual C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.ComponentModel;

namespace DAL
{

[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{

}
}

5. In the ProductDataObject class, add the following private fields:


16 Lab Answer Key: Using LINQ to SQL

a. An integer field called productID.


b. A string field called name.
c. A string field called productNumber.
d. A string field called color.
e. A decimal field called listPrice.
f. A DateTime field called modifiedDate.
Your code should resemble the following code example.

[Visual Basic]

<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

Private _productID As Integer


Private _name As String
Private _productNumber As String
Private _color As String
Private _listPrice As Decimal
Private _modifiedDate As DateTime

End Class

[Visual C#]

[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
private int productID;
private string name;
private string productNumber;
private string color;
private decimal listPrice;
private DateTime modifiedDate;
}

6. Add a property called ProductID that provides read and write access to the productID field. Mark
this property as a primary-key column that does not allow null values. The name of the column in the
database is ProductID.
Your code should resemble the following code example.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

...
<Column(name:="ProductID", IsPrimaryKey:=True,
CanBeNull:=False)> _
Public Property ProductID() As Integer
Get
Return _productID
End Get
Set(ByVal value As Integer)
_productID = value
Lab Answer Key: Using LINQ to SQL 17

End Set
End Property

End Class

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
[Column(Name = "ProductID", IsPrimaryKey = true,
CanBeNull = false)]
public int ProductID
{
get { return productID; }
set { productID = value; }
}
}

7. Add a property called Name that provides read and write access to the name field. In the get
accessor, return the value of name in uppercase. Mark this property as a column that does not allow
null values. The name of the column in the database is Name.
Your code should resemble the following code example.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo
...
<Column(Name:="Name", CanBeNull:=False)> _
Public Property Name() As String
Get
Return _name.ToUpper()
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
[Column(Name = "Name", CanBeNull = false)]
public string Name
{
get { return name.ToUpper(); }
set { name = value; }
}
}

8. Add a property called ProductNumber that provides read and write access to the productNumber
field. Mark this property as a column that does not allow null values. The name of the column in the
database is ProductNumber.
18 Lab Answer Key: Using LINQ to SQL

Your code should resemble the following code example.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo
...
<Column(Name:="ProductNumber", CanBeNull:=False)> _
Public Property ProductNumber() As String
Get
Return _productNumber
End Get
Set(ByVal value As String)
_productNumber = value
End Set
End Property
End Class

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
[Column(Name = "ProductNumber", CanBeNull = false)]
public string ProductNumber
{
get { return productNumber; }
set { productNumber = value; }
}
}

9. Add a property called Color that provides read and write access to the color field. In the get
accessor, return the value of color in uppercase if it is not null, but return a null value otherwise. In the
set accessor, if the value specified is not null, convert it to uppercase before assigning it to the color
field; otherwise, assign an empty string to the color field. Mark the property as a column that allows
null values. The name of the column in the database is Color.
Your code should resemble the following code example.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

...
<Column(Name:="Color", CanBeNull:=True)> _
Public Property Color() As String
Get
If Not [String].IsNullOrEmpty(_color) Then
Return _color.ToUpper()
End If
Return Nothing
End Get
Set(ByVal value As String)
_color = If(value IsNot Nothing, value.ToUpper(),
[String].Empty)
End Set
End Property

End Class
Lab Answer Key: Using LINQ to SQL 19

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
[Column(Name = "Color", CanBeNull = true)]
public string Color
{
get
{
if (!String.IsNullOrEmpty(color))
{
return color.ToUpper();
}
return null;
}
set
{
color = value != null ? value.ToUpper() : String.Empty;
}
}
}

10. Add a property called ListPrice that provides read and write access to the listPrice field. Mark the
property as a column that does not allow null values. The name of the column in the database is
ListPrice.
Your code should resemble the following code example.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

...
<Column(Name:="ListPrice", CanBeNull:=False)> _
Public Property ListPrice() As Decimal
Get
Return _listPrice
End Get
Set(ByVal value As Decimal)
_listPrice = value
End Set
End Property

End Class

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
[Column(Name = "ListPrice", CanBeNull = false)]
public decimal ListPrice
{
get { return listPrice; }
set { listPrice = value; }
}
20 Lab Answer Key: Using LINQ to SQL

11. Add a property called ModifiedDate that provides read and write access to the modifiedDate field.
Mark this property as a column that does not allow null values. The name of the column in the
database is ModifiedDate.
Your code should resemble the following code example.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

...
<Column(Name:="ModifiedDate", CanBeNull:=False)> _
Public Property ModifiedDate() As DateTime
Get
Return _modifiedDate
End Get
Set(ByVal value As DateTime)
_modifiedDate = value
End Set
End Property

End Class

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
[Column(Name = "ModifiedDate", CanBeNull = false)]
public DateTime ModifiedDate
{
get { return modifiedDate; }
set { modifiedDate = value; }
}
}

12. Add the indexer in the following code example to the ProductDataObject class.
This indexer is part of the IDataErrorInfo interface. The get accessor takes the name of a column as a
parameter, and returns a string that contains an error message if the specified column contains
invalid data.
The indexer returns an error message under the following circumstances:
If the Name column is null or empty.
If the ProductNumber column is null or empty.
If the ListPrice column is less than 10.

[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo

...
#Region "IDataErrorInfo Members"
Lab Answer Key: Using LINQ to SQL 21

Public Default ReadOnly Property Item(


ByVal columnName As String) As String _
Implements IDataErrorInfo.Item
Get
If (columnName = "Name") AndAlso
[String].IsNullOrEmpty(Name) Then

Return "Name cannot be null"

End If

If (columnName = "ProductNumber") AndAlso


[String].IsNullOrEmpty(ProductNumber) Then

Return "Product Number cannot be null"

End If

If (columnName = "ListPrice") AndAlso listPrice < 10 Then

Return "List Price must be at least 10.00"

End If
Return Nothing
End Get
End Property

#End Region

End Class

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
#region IDataErrorInfo Members

public string this[string columnName]


{
get
{
if ((columnName == "Name") && String.IsNullOrEmpty(Name))
{
return "Name cannot be null";
}
if ((columnName == "ProductNumber") &&
String.IsNullOrEmpty(ProductNumber))
{
return "Product Number cannot be null";
}
if ((columnName == "ListPrice") && listPrice < 10)
{
return "List Price must be at least 10.00";
}
return null;
}
}

#endregion
22 Lab Answer Key: Using LINQ to SQL

13. Add the Error property in the following code example to the ProductDataObject class.
This property is also part of the IDataErrorInfo interface. This property is used to return an error
message for the object. The ProductDataObject class does not require this data, so the property
simply returns a null value.

[Visual Basic]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
#region IDataErrorInfo Members

...

Public ReadOnly Property [Error]() As String _


Implements IDataErrorInfo.Error
Get
Return Nothing ' Not required
End Get
End Property

#endregion

[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
#region IDataErrorInfo Members

...
public string Error
{
get { return null; } // Not required
}

#endregion
}

14. Build the solution and correct any errors:


On the Build menu, click Build Solution.

Task 3: Test the entity class


1. Start the Windows Forms test application in Debug mode:
In Solution Explorer, right-click Windows Forms Test Application, point to Debug, and then
click Start New Instance.
2. In the Data Set Test Application window, click Load All Products.
Verify that the Name and Color columns are converted to uppercase and that there are errors
because the List Price of several products is less than 10.
Lab Answer Key: Using LINQ to SQL 23

3. Modify a List Price so that it is above 10.00, and then click Save Changes. Verify that the error
disappears.
4. Change the value that you just modified back to 0.00, and then click Save Changes. Notice that the
error returns.
5. Close the Data Set Test Application window.
6. Close Visual Studio:
On the File menu, click Exit.

Das könnte Ihnen auch gefallen