10265AD ENU LabManual
10265AD ENU LabManual
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.
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
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
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
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
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.
PointsUsed Int32
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.
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.
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.
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.
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
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
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.
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.
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
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.
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
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
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.
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.
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.
Insert uspInsertRewardsClaim
Update uspUpdateRewardsClaim
Delete uspDeleteRewardsClaim
4. Map the stored procedure parameters to the entity properties as shown in the following table.
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.
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.
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
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
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.
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 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
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.
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.
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
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
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 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
9. Immediately after the comment, add code to start the BackgroundWorker component running
asynchronously.
10. Save the DataAccessLayer code file.
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.
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
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
5. If you are using Microsoft Visual Basic, you must also manually adjust the namespace to match the
rest of the project.
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 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 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.
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
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
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 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.
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.
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
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
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.
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.
Important: Only build the OrdersClientLibrary project. The OrderManagement project will not build
successfully because it is not yet complete.
Important: Only build the OrdersService project. The OrderManagement project will not build
successfully because it is not yet complete.
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
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.
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.
Important: Only build the OrdersWebService project. The OrderManagement project will not build
successfully because it is not yet complete.
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
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
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.
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.
[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"
End Sub
[Visual C#]
[TestMethod()]
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"
[Visual C#]
[TestMethod()]
public void GetAllOrdersInRangeTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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"
[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";
}
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()>
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"
End Sub
[Visual C#]
[TestMethod()]
public void GetOrdersForContactTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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"
End Sub
[Visual C#]
[TestMethod()]
public void GetOrdersForProductTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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
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
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.
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
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.
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"
order.CalculateOrderTotal()
service.PlaceOrder(order)
actual = service.GetOrdersForProduct(productID)
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";
Assert.AreEqual(expectedComment, modifiedOrder.Comment);
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
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:
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:
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
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".
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]
Try
service.AmendOrder(addedOrder, False,
ConflictResolutionStrategy.None)
Catch cf As FaultException(Of ConcurrencyFault)
expected = True
End Try
Assert.IsTrue(expected)
[Visual C#]
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
Objectives
After completing this lab, you will be able to:
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
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.
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. 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
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:
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
Note: If you are using Visual Basic, do not declare the OnInitialized method as Partial.
12 Lab Instructions: Building Occasionally Connected Solutions
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
Objectives
After completing this lab, you will be able to:
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
The data service will include exception handling to trap and log any exceptions that the service throws.
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.
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.
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 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.
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.
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 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
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.
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
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
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.
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.
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 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.
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.
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
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
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.
[Visual Basic]
Interface IproductDataAccessLayer
''' <summary>
''' Get an Enumerable collection of ProductDataObjects
''' </summary>
''' <returns>An IEnumerable collection of
''' ProductDataObjects</returns>
''' <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>
''' <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>
''' <summary>
''' Updates a ProductDataObject in the Database
''' </summary>
''' <param name="product">The ProductDataObject to update</param>
''' <summary>
6 Lab Instructions: Using ADO.NET
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);
[Visual Basic]
Public Class ProductDataObject
End Class
[Visual C#]
public class ProductDataObject
{
public int ProductID { get; set; }
[Visual Basic]
Friend Class Constants
Friend Const GetAllProducts As String = "productGetAllProducts"
8 Lab Instructions: Using ADO.NET
[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() { }
}
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.
0 Int32 No ProductID
1 String No Name
2 String No ProductNumber
4 Decimal No Price
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.
0 Int32 No ProductID
1 String No Name
2 String No ProductNumber
4 Decimal No Price
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.
@productID ProductID
@name Name
@productNumber ProductNumber
@color Color
@listPrice ListPrice
@productID ProductID
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.
[Visual Basic]
Return View("Products", products)
[Visual C#]
return View("Products", products);
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]
[Visual C#]
[Visual Basic]
Return View(products)
[Visual C#]
16 Lab Instructions: Using ADO.NET
return View(products);
[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
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.
This class is a singleton that you can use to construct a ProductDataSet DataSet in a controlled
manner.
3. Remove the comment and replace it with code that performs the following tasks:
a. Populate the ProductDataProvider.DataSet.Product DataTable class.
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.
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
Objectives
After completing this lab, you will be able to:
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
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:
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.
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
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:
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.
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
2. Run the script. Connect to the 10265A-GEN-DEV\SQLExpress SQL Server instance when prompted.
3. Close the CustomUpdateProcedure.sql file.
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
...
Get
End If
End If
End If
Return Nothing
End Get
End Property
#End Region
End Class
[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
get
{
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")]
[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
#region IDataErrorInfo Members
...
public string Error
{
get { return null; } // Not required
}
#endregion
}
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
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
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.
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.
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.
<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.
b. Change the Name attribute to TerritoryName. Your code should resemble the following code
example.
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.
3. Add the scalar properties described in the following table to the 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 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.
PointsUsed Int32
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.
a. In the Reward entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.
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
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.
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.
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
-- --------------------------------------------------
-- --------------------------------------------------
-- Creating all tables
-- --------------------------------------------------
-- --------------------------------------------------
-- Creating all PRIMARY KEY Constraints
-- --------------------------------------------------
-- --------------------------------------------------
Lab Answer Key: Building Entity Data Models 9
-- --------------------------------------------------
-- 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
-- --------------------------------------------------
-- --------------------------------------------------
-- Creating all tables
-- --------------------------------------------------
);
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 all FOREIGN KEY Constraints
-- --------------------------------------------------
-- --------------------------------------------------
-- Script has ended
-- --------------------------------------------------
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.
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.
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.
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.
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.
a. In the Reward entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.
a. In the Contact entity, in the Navigation Properties section, right-click RewardsClaimeds, and
then click Rename.
b. Type RewardsClaimed and then press ENTER.
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.
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.
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:
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:
b. In the Mapping Details pane, map the NumberOfPointsRequired and the Product properties
to the matching columns in the database.
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.
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.
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.
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
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
[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
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();
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.
[Visual Basic]
'''<summary>
'''A test for GetContactList
'''</summary>
<TestMethod()> _
Public Sub GetContactListTest()
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();
dal.Dispose();
}
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.
[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
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();
[Visual Basic]
'''<summary>
'''A test for GetContactList
'''</summary>
8 Lab Answer Key: Querying Entity Data
<TestMethod()> _
Public Sub GetAContactListTest()
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);
[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>
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>
[Visual Basic]
'''<summary>
'''A test for GetRewardsClaimedList
'''</summary>
<TestMethod()> _
Public Sub GetRewardsClaimedListTest()
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()
{
dal.Dispose();
[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>
reward.RewardID = reader.GetInt32(0)
reward.RewardName = reader.GetString(1)
reward.NumberOfPointsRequired = reader.GetInt32(2)
reward.Product = reader.GetString(3)
End While
End Using
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>
while (reader.Read())
{
reward.RewardID = reader.GetInt32(0);
reward.RewardName = reader.GetString(1);
reward.NumberOfPointsRequired =
reader.GetInt32(2);
reward.Product = reader.GetString(3);
}
}
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()
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();
}
b. If you are using Visual C#, on the File menu, click Save DataAccessLayerTest.cs.
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.
[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
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();
[Visual Basic]
'''<summary>
'''A test for CountOrders
'''</summary>
<TestMethod()> _
Public Sub CountOrdersTest()
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();
}
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
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.
[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
Try
' Encrypt the password in the detached object.
EncryptPassword(contact)
Catch ex As InvalidOperationException
Catch ex As UpdateException
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
try
{
// Encrypt the password in the detached object.
EncryptPassword(contact);
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.
[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
Try
End If
Return True
Catch ex As InvalidOperationException
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>
try
{
// Get the key of the entity you are modifying.
EntityKey key = entities.CreateEntityKey("Contacts", contact);
object contactToModify;
return true;
}
[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
Try
entities.DeleteObject(rewardClaimed)
Next
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 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;
return true;
}
[Visual Basic]
''' <summary>
'''A test for AddContact
'''</summary>
<TestMethod()> _
Public Sub AddContactTest()
Assert.AreEqual(expected.Phone, actual.Phone)
Assert.AreEqual(expected.CurrentPoints, actual.CurrentPoints)
Assert.AreNotEqual(expected.PasswordHash, actual.PasswordHash)
Assert.AreNotEqual(expected.PasswordSalt, actual.PasswordSalt)
target.Dispose()
End Sub
[Visual C#]
/// <summary>
///A test for AddContact.
///</summary>
[TestMethod()]
public void AddContactTest()
{
DataAccessLayer target = new DataAccessLayer();
// 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
[Visual Basic]
''' <summary>
'''A test for ModifyContact
'''</summary>
<TestMethod()> _
Public Sub ModifyContactTest()
target.Dispose()
End Sub
[Visual C#]
/// <summary>
///A test for ModifyContact.
///</summary>
12 Lab Answer Key: Creating, Updating, and Deleting Entity Data
[TestMethod()]
// 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()
End Sub
[Visual C#]
/// <summary>
///A test for DeleteContact.
///</summary>
[TestMethod()]
public void DeleteContactTest()
{
DataAccessLayer target = new DataAccessLayer();
// Tidy up.
target.Dispose();
}
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
Try
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();
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);
}
}
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.
[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
Try
End If
Return True
Catch ex As InvalidOperationException
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>
try
{
// Get the key of the entity you are modifying.
EntityKey key = entities.CreateEntityKey("Rewards", reward);
object rewardToModify;
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);
}
}
[Visual Basic]
''' <summary>
''' Delete a reward.
''' </summary>
Try
End If
Return True
Catch ex As InvalidOperationException
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>
try
{
object rewardToDelete = null;
[Visual Basic]
''' <summary>
'''A test for AddReward
'''</summary>
<TestMethod()> _
Public Sub AddRewardTest()
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();
// Do some checks.
Assert.IsTrue(id > 0);
Reward actual = GetRewardById(id);
Assert.IsInstanceOfType(actual, typeof(AdventureWorksReward));
// Tidy up.
target.DeleteReward(id);
// 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()
target.UpdateReward(expected)
target.Dispose()
End Sub
[Visual C#]
/// <summary>
///A test for ModifyReward.
///</summary>
[TestMethod()]
public void ModifyRewardTest()
{
DataAccessLayer target = new DataAccessLayer();
target.UpdateReward(expected);
// 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()
End Sub
[Visual C#]
/// <summary>
///A test for DeleteReward.
///</summary>
[TestMethod()]
public void DeleteRewardTest()
{
DataAccessLayer target = new DataAccessLayer();
// Tidy up.
target.Dispose();
}
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.
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.
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.
[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
Try
Return claim.ClaimID
Lab Answer Key: Creating, Updating, and Deleting Entity Data 27
Catch ex As InvalidOperationException
Catch ex As UpdateException
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();
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);
}
}
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>
Try
' 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
DirectCast(rewardClaimToModify, _
RewardsClaimed).Contact.CurrentPoints = _
DirectCast(rewardClaimToModify, _
RewardsClaimed).Contact.CurrentPoints + newPoints
End If
End If
Catch ex As InvalidOperationException
Catch ex As UpdateException
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
if (originalPoints != newPoints)
{
// Update contacts points.
((RewardsClaimed)rewardClaimToModify)
.Contact.CurrentPoints += originalPoints;
((RewardsClaimed)rewardClaimToModify)
.Contact.CurrentPoints -= newPoints;
}
}
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.
[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
Catch ex As InvalidOperationException
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>
[Visual Basic]
''' <summary>
''' A test for CreateRewardsClaim
''' </summary>
<TestMethod()> _
Public Sub CreateRewardsClaimTest()
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();
// 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()
expected.PointsUsed = 2000
expected.RewardID = 21
Dim updateResult As Boolean = dal.UpdateRewardsClaim(expected)
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();
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
[Visual Basic]
''' <summary>
''' A test for DeleteRewardsClaim
''' </summary>
<TestMethod()> _
Public Sub DeleteRewardsClaimTest()
End Sub
[Visual C#]
/// <summary>
/// A test for DeleteRewardsClaim
/// </summary>
[TestMethod()]
public void DeleteRewardsClaimTest()
{
DataAccessLayer dal = new DataAccessLayer();
// Tidy up.
dal.Dispose();
}
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
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.
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]
[Visual C#]
[Visual Basic]
Catch ex As OptimisticConcurrencyException
Return claim.ClaimID
[Visual C#]
return claim.ClaimID;
}
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.
[Visual Basic]
Catch ex As OptimisticConcurrencyException
' It's very unlikely the claim has been modified as we've
' only just refreshed it.
entities.Refresh(RefreshMode.ClientWins, rewardClaimToModify)
rewardClaimToModify.Contact.ModifiedDate = DateTime.Now
Return True
[Visual C#]
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);
return true;
[Visual Basic]
' TODO: Ex1 - Handle the OptimisticConcurrencyException in DeleteRewardsClaim
Catch ex As OptimisticConcurrencyException
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);
return true;
}
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()
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();
[Visual Basic]
''' <summary>
''' A test for UpdateRewardsClaim
''' when there is an OptimisticConcurrencyException
''' </summary>
<TestMethod()> _
Public Sub UpdateRewardsClaimConcurrencyTest()
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.
[Visual Basic]
''' <summary>
''' A test for DeleteRewardsClaim
''' when there is an OptimisticConcurrencyException
''' </summary>
<TestMethod()> _
Public Sub DeleteRewardsClaimConcurrencyTest()
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();
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.
[Visual Basic]
archivedEntities.AddToArchivedRewardsClaimed(archivedClaim)
archivedEntities.SaveChanges()
End Using
End Using
[Visual C#]
[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);
}
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
archivedEntities.AddToArchivedRewardsClaimed(archivedClaim)
archivedEntities.SaveChanges()
End Using
End Using
[Visual C#]
// TODO: Ex2 - In UpdateRewardsClaim, wrap SaveChanges in a
// distributed transaction that creates an ArchivedRewardsClaim
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
[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);
}
[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);
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
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()
Console.WriteLine("Context Creation Time: " & vbTab & vbTab & "{0,6} ms",
stagetime.ElapsedMilliseconds)
stagetime.Restart()
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();
stagetime.ElapsedMilliseconds);
stagetime.Restart();
[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);
[Visual Basic]
Public Function GetContactListEntityCompiledLINQ() As List(Of Contact)
Return contacts.ToList()
End Function
[Visual C#]
public List<Contact> GetContactListEntityCompiledLINQ()
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();
return contacts.ToList<Contact>();
}
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)
Return contacts.ToList()
End Function
[Visual C#]
public List<Contact> GetContactListEntityQuery()
{
// Check you have an ObjectContext object.
if (entities == null) entities = new AdventureWorksEntities();
return contacts.ToList<Contact>();
}
Lab Answer Key: Building Optimized Solutions by Using Object Services 7
[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;
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.
c. If you are using Visual C#, on the Build Events panel, add the following command to the Pre-
build event command line.
<add name="AdventureWorksArchivedEntities"
connectionString="metadata=res://*/AdventureWorksArchivedEDM.csdl|res://*/AdventureWorks
ArchivedEDM.ssdl|res://*/AdventureWorksArchivedEDM.msl;provider=System.Data.SqlClient;pr
ovider connection string="Data Source=.\SQLEXPRESS;Initial
Catalog=AdventureWorks;Integrated Security=True;MultipleActiveResultSets=True""
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
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.
[Visual Basic]
AddHandler bw.DoWork,
Sub(o, args)
Try
archivedEntities.AddToArchivedRewardsClaimed(
archivedClaim)
archivedEntities.SaveChanges()
End Using
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
entities.Refresh(RefreshMode.StoreWins, claim)
args.Result = claim
Catch ex As InvalidOperationException
Catch ex As UpdateException
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();
archivedEntities.SaveChanges();
}
Lab Answer Key: Building Optimized Solutions by Using Object Services 13
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);
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)
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();
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.
[Visual Basic]
AddHandler bw.DoWork,
Sub(o, args)
Try
rewardClaimToModify =
DirectCast(objectClaim, RewardsClaimed)
entities.ApplyCurrentValues(
key.EntitySetName, rewardClaim)
rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints -
newPoints
rewardClaimToModify.Contact.ModifiedDate =
DateTime.Now
End If
End If
archivedEntities.AddToArchivedRewardsClaimed(
archivedClaim)
Lab Answer Key: Building Optimized Solutions by Using Object Services 17
archivedEntities.SaveChanges()
End Using
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
rewardClaimToModify.Contact.CurrentPoints =
rewardClaimToModify.Contact.CurrentPoints -
newPoints
rewardClaimToModify.Contact.ModifiedDate =
DateTime.Now
args.Result = rewardClaimToModify
18 Lab Answer Key: Building Optimized Solutions by Using Object Services
Catch ex As InvalidOperationException
Catch ex As UpdateException
End Try
End Using
End Sub
[Visual C#]
bw.DoWork += (o, args) =>
{
int originalPoints = 0;
int newPoints = 0;
RewardsClaimed rewardClaimToModify = null;
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;
}
}
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();
}
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);
entities.Refresh(RefreshMode.ClientWins,
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)
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();
[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.
[Visual Basic]
AddHandler bw.DoWork,
Sub(o, args)
Try
rewardClaimToDelete = DirectCast(
objectClaim, RewardsClaimed)
entities.Refresh(RefreshMode.StoreWins,
rewardClaimToDelete)
Lab Answer Key: Building Optimized Solutions by Using Object Services 23
Catch ex As OptimisticConcurrencyException
' The contact could have been modified so
' get the latest version from the database.
entities.Refresh(
RefreshMode.StoreWins, relatedContact)
Catch ex As InvalidOperationException
Catch ex As UpdateException
End Try
End Using
End Sub
[Visual C#]
bw.DoWork += (o, args) =>
{
RewardsClaimed rewardClaimToDelete = null;
Contact relatedContact = null;
{
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);
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)
[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();
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.
[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
[Visual C#]
// Modify the claim.
claim.PointsUsed = 2000;
claim.RewardID = 21;
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);
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
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();
[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
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.
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.
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.
[Visual Basic]
Private Partial Sub OnValidate()
End Sub
[Visual C#]
4 Lab Answer Key : Customizing Entities and Building Custom Entity Classes
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.
[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.
2. Modify the class definition to define the class as a public partial class for the Contact class.
[Visual Basic]
Lab Answer Key : Customizing Entities and Building Custom Entity Classes 5
Namespace DAL
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.
[Visual Basic]
End Sub
End Class
[Visual C#]
}
}
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
End If
End If
End Sub
End Class
End Namespace
[Visual C#]
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");
}
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]
[Visual C#]
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()
[Visual C#]
// Validate the changes
((IValidate)contact).Validate();
[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();
}
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactCurrentPointsValidationTest()
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactCurrentPointsValidationTest()
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactCurrentPointsValidationTest()
' 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;
// Tidy up
if (contactId > 0)
target.DeleteContact(contactId);
target.Dispose();
}
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactAtSymbolValidationTest()
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactAtSymbolValidationTest()
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactAtSymbolValidationTest()
' Tidy up
If contactId > 0 Then
target.DeleteContact(contactId)
End If
target.Dispose()
End Sub
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
// Create a new contact and set the EmailAddress property to an invalid value
Contact contact = CreateTestContact();
contact.EmailAddress = "ronald1adventure-works.com";
// 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.
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactPeriodValidationTest()
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void AddContactPeriodValidationTest()
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub AddContactPeriodValidationTest()
' 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";
// Tidy up
if (contactId > 0)
target.DeleteContact(contactId);
target.Dispose();
}
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactCurrentPointsValidationTest()
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactCurrentPointsValidationTest()
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactCurrentPointsValidationTest()
' 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);
// Tidy up
target.DeleteContact(testId);
target.Dispose();
}
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactAtSymbolValidationTest()
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactAtSymbolValidationTest()
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactAtSymbolValidationTest()
' Tidy up
target.DeleteContact(testId)
target.Dispose()
End Sub
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
// Create a test contact and then get a detached version of the contact
Contact contact = CreateTestContact();
int testId = target.AddContact(contact);
contact = GetContactById(testId);
// Tidy up
target.DeleteContact(testId);
target.Dispose();
}
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.
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactPeriodValidationTest()
[Visual C#]
[TestMethod()]
[ExpectedException(typeof(DALValidationException))]
public void UpdateContactPeriodValidationTest()
[Visual Basic]
<TestMethod()> _
<ExpectedException(GetType(DALValidationException))> _
Public Sub UpdateContactPeriodValidationTest()
Dim target As New DataAccessLayer()
' 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
// Tidy up
target.DeleteContact(testId);
target.Dispose();
}
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.
[Visual Basic]
18 Lab Answer Key : Customizing Entities and Building Custom Entity Classes
[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.
[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.
[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
[Visual C#]
[EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)]
public Int32 ContactID
{
get
{
return _ContactID;
}
set
{
if (_ContactID != value)
{
_ContactID = value;
}
}
}
return _FirstName;
}
set
{
_FirstName = value;
}
}
}
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;
}
}
return _AdditionalContactInfo;
}
set
{
_AdditionalContactInfo = 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.
[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");
}
}
}
ReportPropertyChanged("PasswordHash");
}
}
_ModifiedDate = StructuralObject.SetValidValue(value);
ReportPropertyChanged("ModifiedDate");
}
}
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
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
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.
<TextBox.Text>
<Binding Path="CurrentPoints">
<Binding.ValidationRules>
<c:NumericValidationRule />
<c:NotNullOrEmptyValidationRule />
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<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>
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
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]
[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]
[Visual C#]
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]
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#]
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
[Visual Basic]
Get
If _Rewards Is Nothing Then
_Rewards = MyBase.CreateObjectSet(Of Reward)("Rewards")
End If
Return _Rewards
End Get
End Property
[Visual C#]
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]
[Visual C#]
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]
[Visual C#]
[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
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; }
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]
Else
Throw New InvalidOperationException("Unrecognized Reward Type")
End If
[Visual C#]
if (preward is AdventureWorksReward)
{
reward = entities.CreateObject<AdventureWorksReward>();
((AdventureWorksReward)reward).Copy(
(AdventureWorksReward)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);
Note: If you try to delete a contact that has reward data, you may see an exception thrown.
[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);
[Visual Basic]
Lab Answer Key: Using POCO Classes with the Entity Framework 13
[Visual C#]
this.Contact.CurrentPoints += this.PointsUsed;
this.Contact.CurrentPoints -= pointsUsed;
this.RewardID = rewardID;
this.PointsUsed = pointsUsed;
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.
[Visual C#]
// TODO: Ex2 - Delete the call to the EncryptPassword method.
// Encrypt the password in the detached object.
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.
[Visual Basic]
[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
[Visual C#]
// Get the entity key you need.
EntityKey key = new EntityKey("AdventureWorksEntities.Contacts",
"ContactID", claim.ContactID);
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);
Note: If you try to delete a contact that has reward data, you will see an exception thrown.
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
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.
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.
[Visual Basic]
End Class
End Class
[Visual C#]
namespace OrdersDAL
{
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
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
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#]
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.
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
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;
}
[Visual Basic]
<ServiceContract (Name:="OrdersWebService",
Namespace:="http://microsoft.com")>
Public Interface IOrdersService
End Interface
[Visual C#]
{
}
[Visual Basic]
Public Interface IOrdersService
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetContactDetails(ByVal contactID As Integer) As Contact
End Interface
[Visual C#]
[FaultContract(typeof(ServiceFault))]
[OperationContract]
Contact GetContactDetails(int contactID);
[Visual Basic]
...
<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#]
{
...
[FaultContract(typeof(ServiceFault))]
[OperationContract]
IEnumerable<Contact> GetAllContactsInRange(
int lowerBound, int upperBound);
}
[Visual Basic]
End Interface
[Visual C#]
[Visual Basic]
[Visual C#]
[Visual Basic]
...
<FaultContract(GetType(ServiceFault))>
<OperationContract()>
Function GetOrdersForProduct(ByVal productID As Integer) _
As IEnumerable(Of SalesOrderHeader)
End Interface
[Visual C#]
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]
...
<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);
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.
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;
...
[Visual Basic]
<ServiceBehavior(Name :="OrdersWebService",
Namespace := "http://microsoft.com",
InstanceContextMode := InstanceContextMode.PerCall,
ConcurrencyMode := ConcurrencyMode.Multiple)>
End Class
14 Lab Answer Key: Building an N-Tier Solution by Using the Entity Framework
[Visual C#]
[Visual Basic]
End Class
[Visual C#]
[Visual Basic]
System.Diagnostics.EventLog.WriteEntry(eventSource, eventMessage,
EventLogEntryType.Error)
End Sub
[Visual C#]
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]
End Sub
[Visual C#]
[Visual Basic]
Private Sub handleException(ByVal ex As Exception,
ByVal operationName As String,
Optional ByVal operationData As Integer = 0)
[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);
}
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
}
[Visual C#]
if (ex is ApplicationException)
{
ServiceFault sf = new ServiceFault
{
ExceptionType = ex.GetType().ToString(),
ExceptionMessage = ex.Message
};
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.
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()
Return contact
End Using
Catch ex As Exception
Me.handleException(ex, "GetContactDetails", contactID)
Return Nothing
End Try
End Function
[Visual C#]
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]
Try
Using context As New AdventureWorksEntities()
End If
Return contacts.ToList()
End Using
Catch ex As Exception
Me.handleException(ex, "GetAllContacts")
Return Nothing
End Try
End Function
[Visual C#]
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
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]
Try
Using context = New AdventureWorksEntities
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#]
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]
Try
Using context = New AdventureWorksEntities()
Return orders.ToList()
End Using
Catch ex As Exception
Me.handleException(ex, "GetOrdersForContact", contactID)
Return Nothing
End Try
End Function
[Visual C#]
try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
orders = from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.ContactID == contactID
select o;
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]
Try
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#]
try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
orders = from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.SalesOrderDetails.Any(
d => d.ProductID == productID)
select o;
return orders.ToList<SalesOrderHeader>();
}
}
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]
Try
Using context = New AdventureWorksEntities
End Using
Catch ex As Exception
Me.handleException(ex, "GetAllOrdersInRange")
Return Nothing
End Try
End Function
[Visual C#]
try
{
using (AdventureWorksEntities context =
new AdventureWorksEntities())
{
orders = from o in context.SalesOrderHeaders.Include(
"SalesOrderDetails")
where o.SalesOrderID >= lowerBound &&
o.SalesOrderID <= upperBound
select o;
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;
}
}
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.
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.
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.
<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>
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]
...
End If
Else
...
[Visual C#]
...
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#]
...
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)
...
[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
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
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.
[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")>
...
End Function
[Visual C#]
[PrincipalPermission(SecurityAction.Demand, Role="ContactAdmin")]
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")>
...
End Function
[Visual C#]
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#]
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#]
...
}
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#]
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.
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
[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;
[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";
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"
End Sub
[Visual C#]
[TestMethod()]
public void GetAllOrdersInRangeTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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"
End Sub
[Visual C#]
[TestMethod()]
public void GetContactDetailsTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Fred";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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"
End Sub
[Visual C#]
[TestMethod()]
public void GetOrderDetailsTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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()>
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert"
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd"
End Sub
[Visual C#]
[TestMethod()]
public void GetOrdersForContactTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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"
End Sub
[Visual C#]
[TestMethod()]
public void GetOrdersForProductTest()
{
service.ClientCredentials.Windows.ClientCredential.UserName =
"Bert";
service.ClientCredentials.Windows.ClientCredential.Password =
"Pa$$w0rd";
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.
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
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
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.
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.
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.
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.
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]
Next detail
End Sub
[Visual C#]
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]
End If
Return True
End Function
[Visual C#]
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;
}
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.
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);
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.
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
[Visual Basic]
Private Function
updateOrderEntityCollectionAndSaveChangesToDatabase(ByVal operationName As String, ByVal
order As SalesOrderHeader) As Boolean
Try
' Add or update the SalesOrderHeader object in the
' SalesOrderHeaders collection of the context.
context.SalesOrderHeaders.ApplyChanges(order)
Catch ex As Exception
End Function
[Visual C#]
private bool
updateOrderEntityCollectionAndSaveChangesToDatabase(string operationName,
SalesOrderHeader order)
{
{
try
{
// Add or update the SalesOrderHeader object in the
// SalesOrderHeaders collection of the context.
context.SalesOrderHeaders.ApplyChanges(order);
{
// Log the details of the exception
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
' Update the sub total in the order to reflect any changes
' made to order details
newOrder.CalculateOrderTotal()
End Function
[Visual C#]
if (!newOrder.Validate())
{
// Throw a ServiceFault if the order is invalid
// and do not save the changes
}
// Update the sub total in the order to reflect
// any changes made to order details
newOrder.CalculateOrderTotal();
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.
[Visual Basic]
<PrincipalPermission(SecurityAction.Demand, Role:="OrderAdmin")>
Public Function AmendOrder(ByVal order As SalesOrderHeader) As Boolean _
Implements IOrdersService.AmendOrder
' Update the sub total in the order to reflect any changes
' made to order details
order.CalculateOrderTotal()
End Function
12 Lab Answer Key: Handling Updates in an N-Tier Solution by Using the Entity Framework
[Visual C#]
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")>
Implements IOrdersService.CancelOrder
order.MarkAsDeleted()
Return Me.updateOrderEntityCollectionAndSaveChangesToDatabase(
"CancelOrder", order)
End Function
[Visual C#]
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.
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.
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.
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".
[Visual Basic]
If service.AmendOrder(order) Then
Me.statusOfLastOperation.Content = "Order saved"
Else
Me.statusOfLastOperation.Content = "Order not saved"
End If
[Visual C#]
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]
If service.CancelOrder(order) Then
Me.statusOfLastOperation.Content = "Order deleted"
Else
Me.statusOfLastOperation.Content = "Order not deleted"
End If
[Visual C#]
if (service.CancelOrder(order))
{
this.statusOfLastOperation.Content = "Order deleted";
}
else
{
this.statusOfLastOperation.Content = "Order not deleted";
}
[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
this.getOrdersForContact.RaiseEvent(
new RoutedEventArgs(Button.ClickEvent, this));
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.
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
[Visual C#]
service.ClientCredentials.Windows.ClientCredential.UserName = "Bert";
service.ClientCredentials.Windows.ClientCredential.Password
= "Pa$$w0rd";
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.
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.
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:
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.
[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))]
[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()>
[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);
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.
[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
Case ConflictResolutionStrategy.StoreWins
context.Refresh(RefreshMode.StoreWins, changedObject)
Return False
Case ConflictResolutionStrategy.ClientWins
context.Refresh(RefreshMode.ClientWins, changedObject)
Return (context.SaveChanges() > 0)
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
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:
[Visual Basic]
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
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#]
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;
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#]
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]
[Visual C#]
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)
[Visual C#]
ConcurrencyFault cf =
determineCauseOfOptimisticConcurrencyException(order, context);
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]
[Visual C#]
};
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);
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]
Then
Me.statusOfLastOperation.Content = "Order saved"
Else
Me.statusOfLastOperation.Content = "Order not saved"
End If
[Visual C#]
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".
[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
[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";
}
}
[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";
}
[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
' 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
[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
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".
[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";
}
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.
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);
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.
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
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.
[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]
[Visual C#]
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");
[Visual Basic]
Try
response = client.DownloadData(
service.Endpoint.Address.Uri.AbsoluteUri)
Catch ex As Exception
Return result
End Try
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
[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.
[Visual Basic]
[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".
[Visual Basic]
Try
If File.Exists(contactsFile) Then
contacts = Me.LoadContactsFromLocalCache(
rangeFrom, rangeTo)
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#]
try
{
if (File.Exists(contactsFile))
{
contacts = this.LoadContactsFromLocalCache(
rangeFrom, rangeTo);
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.
[Visual Basic]
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#]
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:
[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)
Try
doc = XDocument.Load(contactsFile)
Catch ex As Exception
MessageBox.Show(String.Format(
"Exception occurred: {0}", ex.Message))
End Try
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
[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.
[Visual Basic]
[Visual C#]
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.
[Visual Basic]
[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
[Visual C#]
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.
[Visual Basic]
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
[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]
Try
Dim fileName As String = String.Format(
"{0}General.xml", ordersFile)
If File.Exists(fileName) Then
orders = Me.LoadOrdersFromLocalCache(
rangeFrom, rangeTo, fileName)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try
End Sub
[Visual C#]
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.
[Visual Basic]
Try
Dim fileName As String = String.Format(
"{0}contact{1}.xml", ordersFile, contactID)
If File.Exists(fileName) Then
orders = Me.LoadOrdersForContactFromLocalCache(
contactID, fileName)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try
End Sub
[Visual C#]
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]
Try
If File.Exists(fileName) Then
orders = Me.LoadOrdersForProductFromLocalCache(
productID, fileName)
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#]
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
[Visual Basic]
Try
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#]
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.
[Visual Basic]
Try
doc = XDocument.Load(fileName)
Catch ex As Exception
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"))
}
End Function
[Visual C#]
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.
[Visual Basic]
Try
doc = XDocument.Load(fileName)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try
End Function
Lab Answer Key: Building Occasionally Connected Solutions 23
[Visual C#]
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.
[Visual Basic]
Try
doc = XDocument.Load(fileName)
Catch ex As Exception
MessageBox.Show(String.Format("Exception occurred: {0}",
ex.Message))
End Try
Return orders
End Function
[Visual C#]
{
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)
};
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:
[Visual Basic]
.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)
}
Return orderDetails
End Function
[Visual C#]
private TrackableCollection<SalesOrderDetail>
getOrderDetailsFromCache(IEnumerable<XElement> order)
{
TrackableCollection<SalesOrderDetail> orderDetails =
new TrackableCollection<SalesOrderDetail>();
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:
[Visual Basic]
Return orderDetails
End Function
[Visual C#]
private TrackableCollection<SalesOrderDetail>
getOrderDetailsForProductFromCache(IEnumerable<XElement> order, int productID)
{
TrackableCollection<SalesOrderDetail> orderDetails =
new TrackableCollection<SalesOrderDetail>();
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)
};
return orderDetails;
}
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.
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.
[Visual Basic]
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.
[Visual Basic]
[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
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]
[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.
[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.
[Visual Basic]
Try
Dim awSyncAgent As New AWCacheSyncAgent()
Dim awSyncStats As SyncStatistics = awSyncAgent.Synchronize()
Return True
Catch
Return False
End Try
End Function
[Visual C#]
[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);
}
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:
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:
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
[Visual Basic]
Imports ShippingDataServiceLibrary
Imports System.Diagnostics
[Visual C#]
using ShippingDataServiceLibrary;
using System.Diagnostics;
[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)
DataServiceProtocolVersion.V2
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;
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.
[Visual Basic]
''' <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>
''' <summary>
''' The name of the event source for logging exceptions to the event /// log
''' </summary>
''' <summary>
''' The name of the event log to use for recording exceptions.
''' </summary>
[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);
}
/// <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";
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.
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.
[Visual Basic]
' Obtain a new AdventureWorksEntities context object.
context = New AdventureWorksEntities(New Uri(Constants.serviceUrl))
[Visual C#]
// Obtain a new AdventureWorksEntities context object.
context = new AdventureWorksEntities(new Uri(Constants.serviceUrl));
select sh;
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);
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);
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);
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.
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
[Visual Basic]
<QueryInterceptor("SalesOrderHeaders")> _
Public Function OnQuerySalesOrderHeaders() As Expression(Of Func(Of SalesOrderHeader,
Boolean))
' Everything
Return Function(o) o.ShipDate IsNot Nothing
Else
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
[Visual Basic]
[Visual C#]
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>;
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.
[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.
[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()
End If
Me.CurrentDataSource.ArchivedSalesOrderHeaders.AddObject(
ArchivedSalesOrderHeader.CreateArchivedSalesOrderHeader(
h.SalesOrderID, h.SalesOrderNumber, h.CustomerID,
h.ContactID, h.ShipMethodID))
Next
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
{
// 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));
}
this.CurrentDataSource.SaveChanges();
}
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.
[Visual Basic]
<AcceptVerbs(HttpVerbs.Get)> _
Public Function ArchiveOrders() As ActionResult
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
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.
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
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
[Visual C#]
context = new AdventureWorksEntities(new Uri(Constants.serviceUrl));
context.Credentials = CredentialCache.DefaultNetworkCredentials;
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
salesOrderHeaderToChange.Status = StatusList
Try
context.UpdateObject(salesOrderHeaderToChange)
context.SaveChanges()
Catch ex As DataServiceRequestException
End Try
End Function
[Visual C#]
[HandleError]
public ActionResult HandleStatusUpdate(byte StatusList, int id)
{
context = new AdventureWorksEntities(
new Uri(Constants.serviceUrl));
context.Credentials = CredentialCache.DefaultNetworkCredentials;
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);
}
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.
[Visual Basic]
...
config.SetEntitySetAccessRule("SalesOrderHeaders", (EntitySetRights.AllRead Or
EntitySetRights.WriteReplace Or EntitySetRights.WriteMerge))
...
[Visual C#]
...
config.SetEntitySetAccessRule("SalesOrderHeaders",(EntitySetRights.AllRead |
EntitySetRights.WriteReplace | EntitySetRights.WriteMerge));
...
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]
[Visual C#]
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]
[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
[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;
[Visual Basic]
Private Sub getContacts_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Try
Me.contactsGrid.DataContext =
New ObservableCollection(Of Contact)(contactInfo)
MessageBox.Show(dsqEx.InnerException.Message,
"Error Fetching Contacts", MessageBoxButton.OK,
MessageBoxImage.Error)
Catch ex As Exception
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);
}
}
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.
[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
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);
}
}
}
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
[Visual Basic]
...
context.UpdateObject(currentContact)
...
[Visual C#]
...
context.UpdateObject(currentContact);
...
[Visual Basic]
Private Sub saveChanges_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Try
End If
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);
}
}
[Visual Basic]
16 Lab Answer Key: Updating Data by Using WCF Data Services
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));
}
[Visual Basic]
...
config.SetEntitySetAccessRule("Contacts", (EntitySetRights.AllWrite Or
EntitySetRights.AllRead))
...
[Visual C#]
...
config.SetEntitySetAccessRule("Contacts",(EntitySetRights.AllWrite |
EntitySetRights.AllRead));
...
[Visual Basic]
context.Credentials = new NetworkCredential(Me.userName.Text, Me.password.Password)
[Visual C#]
context.Credentials = new NetworkCredential(this.userName.Text, this.password.Password);
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
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
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;
}
}
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
[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
/// <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);
[Visual Basic]
End Class
[Visual C#]
public class ProductDataObject
{
public int ProductID { get; set; }
public string Name { get; set; }
[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
[Visual Basic]
End Class
[Visual C#]
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]
''' <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() { }
}
[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.
[Visual Basic]
Public Function GetProductList() As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList
AWDatabase.DatabaseConnectionString)
End Using
End Function
[Visual C#]
public List<ProductDataObject> GetProductList()
{
List<ProductDataObject> products = new List<ProductDataObject>();
0 Int32 No ProductID
1 String No Name
2 String No ProductNumber
4 Decimal No Price
[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()
products.Add(prod)
End While
[Visual C#]
getAllProducts.CommandType = CommandType.StoredProcedure;
connection.Open();
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);
[Visual Basic]
getAllProducts.CommandType = CommandType.StoredProcedure
connection.Open()
While reader.Read()
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)
prod.ListPrice = reader.GetDecimal(4)
products.Add(prod)
End While
End Using
Return products
End Function
[Visual C#]
public List<ProductDataObject> GetProductList()
{
List<ProductDataObject> products = new List<ProductDataObject>();
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;
}
[Visual Basic]
Public Function GetProduct(ByVal productID As Integer) As _
ProductDataObject _
Implements IProductDataAccessLayer.GetProduct
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))
{ }
}
0 Int32 No ProductID
1 String No Name
2 String No ProductNumber
4 Decimal No Price
[Visual Basic]
Dim getProductById As New SqlCommand(
Constants.GetProductByID, connection)
getProductById.CommandType = CommandType.StoredProcedure
Lab Answer Key: Using ADO.NET 13
If reader.Read() Then
prod = New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)
prod.ListPrice = reader.GetDecimal(4)
[Visual C#]
if (reader.Read())
{
prod.ListPrice = reader.GetDecimal(4);
if (!reader.IsDBNull(5))
{
prod.ModifiedDate = reader.GetDateTime(5).Date;
}
}
[Visual Basic]
ProductDataObject _
Implements IProductDataAccessLayer.GetProduct
If reader.Read() Then
prod = New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)
End Using
Return prod
End Function
[Visual C#]
getProductById.CommandType = CommandType.StoredProcedure;
getProductById.Parameters.Add(parameter);
connection.Open();
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;
}
[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.
[Visual Basic]
Public Function GetProductList(ByVal color As String) As _
System.Collections.Generic.List(Of ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList
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)
prod.ListPrice = reader.GetDecimal(4)
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
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
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)
While reader.Read()
Dim prod As New ProductDataObject()
prod.ProductID = reader.GetInt32(0)
prod.Name = reader.GetString(1)
prod.ProductNumber = reader.GetString(2)
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>();
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;
}
@productID ProductID
@name Name
@productNumber ProductNumber
@color Color
@listPrice ListPrice
[Visual Basic]
20 Lab Answer Key: Using ADO.NET
_UpdateProduct.CommandType = CommandType.StoredProcedure
Dim prodID As New SqlParameter("@productID",
product.ProductID)
_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;
updateProduct.CommandType = CommandType.StoredProcedure;
SqlParameter prodID = new
SqlParameter("@productID", product.ProductID);
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;
}
@productID ProductID
[Visual Basic]
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))
{
deleteProduct.Parameters.Add(prodID);
connection.Open();
try
{
deleteProduct.ExecuteNonQuery();
result=true;
Lab Answer Key: Using ADO.NET 23
}
catch(SqlException)
{
result=false;
}
}
return result;
}
[Visual Basic]
Friend Overridable Function CreateIProductDataAccessLayer() As _
IProductDataAccessLayer
End Function
[Visual C#]
internal virtual IProductDataAccessLayer CreateIProductDataAccessLayer()
{
IProductDataAccessLayer target = new ProductDataAccessLayer();
return target;
}
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()
End Sub
[Visual C#]
[TestMethod()]
public void GetProductListTest()
{
IProductDataAccessLayer target = CreateIProductDataAccessLayer();
List<ProductDataObject> expected = GetLocalProductList();
List<ProductDataObject> actual = target.GetProductList();
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()
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);
}
[Visual Basic]
<TestMethod()> _
Public Sub GetProductListByMaxPriceTest()
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);
}
}
[Visual Basic]
<TestMethod()> _
Public Sub GetProductListByColorTest()
Lab Answer Key: Using ADO.NET 27
[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);
}
}
[Visual Basic]
<TestMethod()> _
End Sub
[Visual C#]
[TestMethod()]
[Visual Basic]
<TestMethod()> _
Assert.IsTrue(result)
End Sub
[Visual C#]
[TestMethod()]
Assert.IsTrue(result);
[Visual Basic]
Dim dal As New ProductDataAccessLayer()
30 Lab Answer Key: Using ADO.NET
[Visual C#]
ProductDataAccessLayer dal = new ProductDataAccessLayer();
[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);
[Visual Basic]
<AcceptVerbs(HttpVerbs.Post)> _
Public Function BrowseColor(ByVal productResponse As ProductResponse) As ActionResult
If String.IsNullOrEmpty(productResponse.Color) Then
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");
}
}
[Visual Basic]
[Visual C#]
[Visual Basic]
dal.GetProductList(maxprice)
[Visual C#]
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);
[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)
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);
[Visual Basic]
Dim dal As New ProductDataAccessLayer()
[Visual C#]
ProductDataAccessLayer dal = new ProductDataAccessLayer();
[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);
[Visual Basic]
Public Function Products() As ActionResult
End Function
[Visual C#]
public ActionResult Products()
{
[Visual Basic]
Dim dal As New ProductDataAccessLayer()
[Visual C#]
ProductDataAccessLayer dal = new ProductDataAccessLayer();
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);
[Visual Basic]
Public Function Details(ByVal id As Integer) As ActionResult
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
[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)
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>();
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.
[Visual Basic]
''' <summary>
''' Return a specified product. Products are identified by the Product ID
''' </summary>
productAdapter.Fill(ProductDataProvider.DataSet.Product)
Lab Answer Key: Using ADO.NET 39
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;
}
}
[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>();
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)()
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>
{
productAdapter.Fill(ProductDataProvider.DataSet.Product);
List<ProductDataObject> products = new List<ProductDataObject>();
{
products.Add(BusinessObjectFromDataRow(row));
}
return products;
}
[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;
}
...
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.
[Visual Basic]
...
Try
toRemove = row.GetChildRows(relation)
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;
}
...
[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
[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);
}
...
[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);
}
...
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
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:
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.
[Visual Basic]
#Region "IProductDataAccessLayer Members"
Return db.ProductDataObjects.ToList()
End Using
End Function
[Visual C#]
#region IProductDataAccessLayer Members
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:
[Visual Basic]
Public Function GetProduct(ByVal productID As Integer) _
As ProductDataObject _
Implements IproductDataAccessLayer.GetProduct
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:
[Visual Basic]
Public Function GetProductList(ByVal color As String) _
As List(Of ProductDataObject) _
Implements IproductDataAccessLayer.GetProductList
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:
[Visual Basic]
Public Function GetProductList(ByVal maxListPrice As Decimal) As List(Of
ProductDataObject) _
Implements IProductDataAccessLayer.GetProductList
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:
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
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
Throw
Catch ex As InvalidOperationException
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
db.ProductDataObjects.DeleteOnSubmit(prod)
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
result = True
Catch ex As ChangeConflictException
oce.Resolve(RefreshMode.OverwriteCurrentValues)
Next
Throw
Catch ex As InvalidOperationException
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;
}
}
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.
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.
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
[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
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
{
}
}
[Visual Basic]
<Table(Name:="AdventureWorks.Production.Product")> _
Public Class ProductDataObject
Implements IDataErrorInfo
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
[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
End If
End If
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
#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
...
#endregion
[Visual C#]
[Table(Name = "AdventureWorks.Production.Product")]
public class ProductDataObject : IDataErrorInfo
{
...
#region IDataErrorInfo Members
...
public string Error
{
get { return null; } // Not required
}
#endregion
}
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.