09.06.2013 Views

Developing Data Access Solutions with Microsoft® Visual Studio ...

Developing Data Access Solutions with Microsoft® Visual Studio ...

Developing Data Access Solutions with Microsoft® Visual Studio ...

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

OFFICIAL MICROSOFT LEARNING PRODUCT<br />

10265A<br />

<strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong><br />

Microsoft ® <strong>Visual</strong> <strong>Studio</strong> ® 2010<br />

Volume 1


ii <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Information in this document, including URL and other Internet Web site references, is subject to change <strong>with</strong>out notice.<br />

Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people,<br />

places, and events depicted herein are fictitious, and no association <strong>with</strong> any real company, organization, product, domain<br />

name, e-mail address, logo, person, place or event is intended or should be inferred. Complying <strong>with</strong> all applicable copyright<br />

laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be<br />

reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic,<br />

mechanical, photocopying, recording, or otherwise), or for any purpose, <strong>with</strong>out the express written permission of Microsoft<br />

Corporation.<br />

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject<br />

matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this<br />

document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.<br />

The names of manufacturers, products, or URLs are provided for informational purposes only and Microsoft makes no<br />

representations and warranties, either expressed, implied, or statutory, regarding these manufacturers or the use of the<br />

products <strong>with</strong> any Microsoft technologies. The inclusion of a manufacturer or product does not imply endorsement of<br />

Microsoft of the manufacturer or product. Links may be provided to third party sites. Such sites are not under the control of<br />

Microsoft and Microsoft is not responsible for the contents of any linked site or any link contained in a linked site, or any<br />

changes or updates to such sites. Microsoft is not responsible for webcasting or any other form of transmission received from<br />

any linked site. Microsoft is providing these links to you only as a convenience, and the inclusion of any link does not imply<br />

endorsement of Microsoft of the site or the products contained therein.<br />

© 2010 Microsoft Corporation. All rights reserved.<br />

Microsoft, and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or<br />

other countries.<br />

All other trademarks are property of their respective owners.<br />

Product Number: 10265A<br />

Part Number: X17-47847<br />

Released: 10/2010


MICROSOFT LICENSE TERMS<br />

OFFICIAL MICROSOFT LEARNING PRODUCTS - TRAINER EDITION –<br />

Pre-Release and Final Release Versions<br />

These license terms are an agreement between Microsoft Corporation and you. Please read them. They apply to the Licensed<br />

Content named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft<br />

updates,<br />

supplements,<br />

Internet-based services, and<br />

support services<br />

for this Licensed Content, unless other terms accompany those items. If so, those terms apply.<br />

By using the Licensed Content, you accept these terms. If you do not accept them, do not use the Licensed<br />

Content.<br />

If you comply <strong>with</strong> these license terms, you have the rights below.<br />

1. DEFINITIONS.<br />

a. “Academic Materials” means the printed or electronic documentation such as manuals, workbooks, white papers,<br />

press releases, datasheets, and FAQs which may be included in the Licensed Content.<br />

b. “Authorized Learning Center(s)” means a Microsoft Certified Partner for Learning <strong>Solutions</strong> location, an IT<br />

Academy location, or such other entity as Microsoft may designate from time to time.<br />

c. “Authorized Training Session(s)” means those training sessions authorized by Microsoft and conducted at or<br />

through Authorized Learning Centers by a Trainer providing training to Students solely on Official Microsoft Learning<br />

Products (formerly known as Microsoft Official Curriculum or “MOC”) and Microsoft Dynamics Learning Products<br />

(formerly know as Microsoft Business <strong>Solutions</strong> Courseware). Each Authorized Training Session will provide training on<br />

the subject matter of one (1) Course.<br />

d. “Course” means one of the courses using Licensed Content offered by an Authorized Learning Center during an<br />

Authorized Training Session, each of which provides training on a particular Microsoft technology subject matter.<br />

e. “Device(s)” means a single computer, device, workstation, terminal, or other digital electronic or analog device.<br />

f. “Licensed Content” means the materials accompanying these license terms. The Licensed Content may include, but<br />

is not limited to, the following elements: (i) Trainer Content, (ii) Student Content, (iii) classroom setup guide, and (iv)<br />

Software. There are different and separate components of the Licensed Content for each Course.<br />

g. “Software” means the Virtual Machines and Virtual Hard Disks, or other software applications that may be included<br />

<strong>with</strong> the Licensed Content.<br />

h. “Student(s)” means a student duly enrolled for an Authorized Training Session at your location.<br />

i. “Student Content” means the learning materials accompanying these license terms that are for use by Students and<br />

Trainers during an Authorized Training Session. Student Content may include labs, simulations, and courseware files<br />

for a Course.<br />

j. “Trainer(s)” means a) a person who is duly certified by Microsoft as a Microsoft Certified Trainer and b) such other<br />

individual as authorized in writing by Microsoft and has been engaged by an Authorized Learning Center to teach or<br />

instruct an Authorized Training Session to Students on its behalf.<br />

k. “Trainer Content” means the materials accompanying these license terms that are for use by Trainers and Students,<br />

as applicable, solely during an Authorized Training Session. Trainer Content may include Virtual Machines, Virtual Hard<br />

Disks, Microsoft PowerPoint files, instructor notes, and demonstration guides and script files for a Course.<br />

l. “Virtual Hard Disks” means Microsoft Software that is comprised of virtualized hard disks (such as a base virtual hard<br />

disk or differencing disks) for a Virtual Machine that can be loaded onto a single computer or other device in order to<br />

allow end-users to run multiple operating systems concurrently. For the purposes of these license terms, Virtual Hard<br />

Disks will be considered “Trainer Content”.<br />

m. “Virtual Machine” means a virtualized computing experience, created and accessed using Microsoft Virtual PC or<br />

Microsoft Virtual Server software that consists of a virtualized hardware environment, one or more Virtual Hard Disks,


and a configuration file setting the parameters of the virtualized hardware environment (e.g., RAM). For the purposes<br />

of these license terms, Virtual Hard Disks will be considered “Trainer Content”.<br />

n. “you” means the Authorized Learning Center or Trainer, as applicable, that has agreed to these license terms.<br />

2. OVERVIEW.<br />

Licensed Content. The Licensed Content includes Software, Academic Materials (online and electronic), Trainer Content,<br />

Student Content, classroom setup guide, and associated media.<br />

License Model. The Licensed Content is licensed on a per copy per Authorized Learning Center location or per Trainer<br />

basis.<br />

3. INSTALLATION AND USE RIGHTS.<br />

a. Authorized Learning Centers and Trainers: For each Authorized Training Session, you may:<br />

i. either install individual copies of the relevant Licensed Content on classroom Devices only for use by Students<br />

enrolled in and the Trainer delivering the Authorized Training Session, provided that the number of copies in use<br />

does not exceed the number of Students enrolled in and the Trainer delivering the Authorized Training Session, OR<br />

ii. install one copy of the relevant Licensed Content on a network server only for access by classroom Devices and<br />

only for use by Students enrolled in and the Trainer delivering the Authorized Training Session, provided that the<br />

number of Devices accessing the Licensed Content on such server does not exceed the number of Students<br />

enrolled in and the Trainer delivering the Authorized Training Session.<br />

iii. and allow the Students enrolled in and the Trainer delivering the Authorized Training Session to use the Licensed<br />

Content that you install in accordance <strong>with</strong> (ii) or (ii) above during such Authorized Training Session in accordance<br />

<strong>with</strong> these license terms.<br />

i. Separation of Components. The components of the Licensed Content are licensed as a single unit. You may not<br />

separate the components and install them on different Devices.<br />

ii. Third Party Programs. The Licensed Content may contain third party programs. These license terms will apply to<br />

the use of those third party programs, unless other terms accompany those programs.<br />

b. Trainers:<br />

i. Trainers may Use the Licensed Content that you install or that is installed by an Authorized Learning Center on a<br />

classroom Device to deliver an Authorized Training Session.<br />

ii. Trainers may also Use a copy of the Licensed Content as follows:<br />

A. Licensed Device. The licensed Device is the Device on which you Use the Licensed Content. You may install<br />

and Use one copy of the Licensed Content on the licensed Device solely for your own personal training Use and<br />

for preparation of an Authorized Training Session.<br />

B. Portable Device. You may install another copy on a portable device solely for your own personal training Use<br />

and for preparation of an Authorized Training Session.<br />

4. PRE-RELEASE VERSIONS. If this is a pre-release (“beta”) version, in addition to the other provisions in this agreement,<br />

these terms also apply:<br />

a. Pre-Release Licensed Content. This Licensed Content is a pre-release version. It may not contain the same<br />

information and/or work the way a final version of the Licensed Content will. We may change it for the final,<br />

commercial version. We also may not release a commercial version. You will clearly and conspicuously inform any<br />

Students who participate in each Authorized Training Session of the foregoing; and, that you or Microsoft are under no<br />

obligation to provide them <strong>with</strong> any further content, including but not limited to the final released version of the<br />

Licensed Content for the Course.<br />

b. Feedback. If you agree to give feedback about the Licensed Content to Microsoft, you give to Microsoft, <strong>with</strong>out<br />

charge, the right to use, share and commercialize your feedback in any way and for any purpose. You also give to<br />

third parties, <strong>with</strong>out charge, any patent rights needed for their products, technologies and services to use or interface<br />

<strong>with</strong> any specific parts of a Microsoft software, Licensed Content, or service that includes the feedback. You will not<br />

give feedback that is subject to a license that requires Microsoft to license its software or documentation to third parties<br />

because we include your feedback in them. These rights survive this agreement.<br />

c. Confidential Information. The Licensed Content, including any viewer, user interface, features and documentation<br />

that may be included <strong>with</strong> the Licensed Content, is confidential and proprietary to Microsoft and its suppliers.


i. Use. For five years after installation of the Licensed Content or its commercial release, whichever is first, you<br />

may not disclose confidential information to third parties. You may disclose confidential information only to<br />

your employees and consultants who need to know the information. You must have written agreements <strong>with</strong><br />

them that protect the confidential information at least as much as this agreement.<br />

ii. Survival. Your duty to protect confidential information survives this agreement.<br />

iii. Exclusions. You may disclose confidential information in response to a judicial or governmental order. You<br />

must first give written notice to Microsoft to allow it to seek a protective order or otherwise protect the<br />

information. Confidential information does not include information that<br />

becomes publicly known through no wrongful act;<br />

you received from a third party who did not breach confidentiality obligations to Microsoft or its suppliers;<br />

or<br />

you developed independently.<br />

d. Term. The term of this agreement for pre-release versions is (i) the date which Microsoft informs you is the end date<br />

for using the beta version, or (ii) the commercial release of the final release version of the Licensed Content, whichever<br />

is first (“beta term”).<br />

e. Use. You will cease using all copies of the beta version upon expiration or termination of the beta term, and will<br />

destroy all copies of same in the possession or under your control and/or in the possession or under the control of any<br />

Trainers who have received copies of the pre-released version.<br />

f. Copies. Microsoft will inform Authorized Learning Centers if they may make copies of the beta version (in either print<br />

and/or CD version) and distribute such copies to Students and/or Trainers. If Microsoft allows such distribution, you<br />

will follow any additional terms that Microsoft provides to you for such copies and distribution.<br />

5. ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS.<br />

a. Authorized Learning Centers and Trainers:<br />

i. Software.<br />

ii. Virtual Hard Disks. The Licensed Content may contain versions of Microsoft XP, Microsoft Windows Vista,<br />

Windows Server 2003, Windows Server 2008, and Windows 2000 Advanced Server and/or other Microsoft products<br />

which are provided in Virtual Hard Disks.<br />

A. If the Virtual Hard Disks and the labs are launched through the Microsoft Learning Lab Launcher,<br />

then these terms apply:<br />

Time-Sensitive Software. If the Software is not reset, it will stop running based upon the time indicated on the<br />

install of the Virtual Machines (between 30 and 500 days after you install it). You will not receive notice before<br />

it stops running. You may not be able to access data used or information saved <strong>with</strong> the Virtual Machines<br />

when it stops running and may be forced to reset these Virtual Machines to their original state. You must<br />

remove the Software from the Devices at the end of each Authorized Training Session and reinstall and launch<br />

it prior to the beginning of the next Authorized Training Session.<br />

B. If the Virtual Hard Disks require a product key to launch, then these terms apply:<br />

Microsoft will deactivate the operating system associated <strong>with</strong> each Virtual Hard Disk. Before installing any<br />

Virtual Hard Disks on classroom Devices for use during an Authorized Training Session, you will obtain from<br />

Microsoft a product key for the operating system software for the Virtual Hard Disks and will activate such<br />

Software <strong>with</strong> Microsoft using such product key.<br />

C. These terms apply to all Virtual Machines and Virtual Hard Disks:<br />

You may only use the Virtual Machines and Virtual Hard Disks if you comply <strong>with</strong> the terms and<br />

conditions of this agreement and the following security requirements:<br />

o You may not install Virtual Machines and Virtual Hard Disks on portable Devices or Devices that are<br />

accessible to other networks.<br />

o You must remove Virtual Machines and Virtual Hard Disks from all classroom Devices at the end of each<br />

Authorized Training Session, except those held at Microsoft Certified Partners for Learning <strong>Solutions</strong><br />

locations.


o You must remove the differencing drive portions of the Virtual Hard Disks from all classroom Devices at<br />

the end of each Authorized Training Session at Microsoft Certified Partners for Learning <strong>Solutions</strong> locations.<br />

o You will ensure that the Virtual Machines and Virtual Hard Disks are not copied or downloaded from<br />

Devices on which you installed them.<br />

o You will strictly comply <strong>with</strong> all Microsoft instructions relating to installation, use, activation and<br />

deactivation, and security of Virtual Machines and Virtual Hard Disks.<br />

o You may not modify the Virtual Machines and Virtual Hard Disks or any contents thereof.<br />

o You may not reproduce or redistribute the Virtual Machines or Virtual Hard Disks.<br />

ii. Classroom Setup Guide. You will assure any Licensed Content installed for use during an Authorized Training<br />

Session will be done in accordance <strong>with</strong> the classroom set-up guide for the Course.<br />

iii. Media Elements and Templates. You may allow Trainers and Students to use images, clip art, animations,<br />

sounds, music, shapes, video clips and templates provided <strong>with</strong> the Licensed Content solely in an Authorized<br />

Training Session. If Trainers have their own copy of the Licensed Content, they may use Media Elements for their<br />

personal training use.<br />

iv. iv Evaluation Software. Any Software that is included in the Student Content designated as “Evaluation<br />

Software” may be used by Students solely for their personal training outside of the Authorized Training Session.<br />

b. Trainers Only:<br />

i. Use of PowerPoint Slide Deck Templates. The Trainer Content may include Microsoft PowerPoint slide decks.<br />

Trainers may use, copy and modify the PowerPoint slide decks only for providing an Authorized Training Session.<br />

If you elect to exercise the foregoing, you will agree or ensure Trainer agrees: (a) that modification of the slide<br />

decks will not constitute creation of obscene or scandalous works, as defined by federal law at the time the work is<br />

created; and (b) to comply <strong>with</strong> all other terms and conditions of this agreement.<br />

ii. Use of Instructional Components in Trainer Content. For each Authorized Training Session, Trainers may<br />

customize and reproduce, in accordance <strong>with</strong> the MCT Agreement, those portions of the Licensed Content that are<br />

logically associated <strong>with</strong> instruction of the Authorized Training Session. If you elect to exercise the foregoing<br />

rights, you agree or ensure the Trainer agrees: (a) that any of these customizations or reproductions will only be<br />

used for providing an Authorized Training Session and (b) to comply <strong>with</strong> all other terms and conditions of this<br />

agreement.<br />

iii. Academic Materials. If the Licensed Content contains Academic Materials, you may copy and use the Academic<br />

Materials. You may not make any modifications to the Academic Materials and you may not print any book (either<br />

electronic or print version) in its entirety. If you reproduce any Academic Materials, you agree that:<br />

The use of the Academic Materials will be only for your personal reference or training use<br />

You will not republish or post the Academic Materials on any network computer or broadcast in any media;<br />

You will include the Academic Material’s original copyright notice, or a copyright notice to Microsoft’s benefit in<br />

the format provided below:<br />

Form of Notice:<br />

© 2010 Reprinted for personal reference use only <strong>with</strong> permission by Microsoft Corporation. All<br />

rights reserved.<br />

Microsoft, Windows, and Windows Server are either registered trademarks or trademarks of<br />

Microsoft Corporation in the US and/or other countries. Other product and company names<br />

mentioned herein may be the trademarks of their respective owners.<br />

6. INTERNET-BASED SERVICES. Microsoft may provide Internet-based services <strong>with</strong> the Licensed Content. It may change<br />

or cancel them at any time. You may not use these services in any way that could harm them or impair anyone else’s use<br />

of them. You may not use the services to try to gain unauthorized access to any service, data, account or network by any<br />

means.<br />

7. SCOPE OF LICENSE. The Licensed Content is licensed, not sold. This agreement only gives you some rights to use the<br />

Licensed Content. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation,<br />

you may use the Licensed Content only as expressly permitted in this agreement. In doing so, you must comply <strong>with</strong> any<br />

technical limitations in the Licensed Content that only allow you to use it in certain ways. You may not


install more copies of the Licensed Content on classroom Devices than the number of Students and the Trainer in the<br />

Authorized Training Session;<br />

allow more classroom Devices to access the server than the number of Students enrolled in and the Trainer delivering<br />

the Authorized Training Session if the Licensed Content is installed on a network server;<br />

copy or reproduce the Licensed Content to any server or location for further reproduction or distribution;<br />

disclose the results of any benchmark tests of the Licensed Content to any third party <strong>with</strong>out Microsoft’s prior written<br />

approval;<br />

work around any technical limitations in the Licensed Content;<br />

reverse engineer, decompile or disassemble the Licensed Content, except and only to the extent that applicable law<br />

expressly permits, despite this limitation;<br />

make more copies of the Licensed Content than specified in this agreement or allowed by applicable law, despite this<br />

limitation;<br />

publish the Licensed Content for others to copy;<br />

transfer the Licensed Content, in whole or in part, to a third party;<br />

access or use any Licensed Content for which you (i) are not providing a Course and/or (ii) have not been authorized<br />

by Microsoft to access and use;<br />

rent, lease or lend the Licensed Content; or<br />

use the Licensed Content for commercial hosting services or general business purposes.<br />

Rights to access the server software that may be included <strong>with</strong> the Licensed Content, including the Virtual Hard Disks<br />

does not give you any right to implement Microsoft patents or other Microsoft intellectual property in software or<br />

devices that may access the server.<br />

8. EXPORT RESTRICTIONS. The Licensed Content is subject to United States export laws and regulations. You must<br />

comply <strong>with</strong> all domestic and international export laws and regulations that apply to the Licensed Content. These laws<br />

include restrictions on destinations, end users and end use. For additional information, see<br />

www.microsoft.com/exporting.<br />

9. NOT FOR RESALE SOFTWARE/LICENSED CONTENT. You may not sell software or Licensed Content marked as “NFR”<br />

or “Not for Resale.”<br />

10. ACADEMIC EDITION. You must be a “Qualified Educational User” to use Licensed Content marked as “Academic Edition”<br />

or “AE.” If you do not know whether you are a Qualified Educational User, visit www.microsoft.com/education or contact<br />

the Microsoft affiliate serving your country.<br />

11. TERMINATION. Without prejudice to any other rights, Microsoft may terminate this agreement if you fail to comply <strong>with</strong><br />

the terms and conditions of these license terms. In the event your status as an Authorized Learning Center or Trainer a)<br />

expires, b) is voluntarily terminated by you, and/or c) is terminated by Microsoft, this agreement shall automatically<br />

terminate. Upon any termination of this agreement, you must destroy all copies of the Licensed Content and all of its<br />

component parts.<br />

12. ENTIRE AGREEMENT. This agreement, and the terms for supplements, updates, Internet-based services and<br />

support services that you use, are the entire agreement for the Licensed Content and support services.<br />

13. APPLICABLE LAW.<br />

a. United States. If you acquired the Licensed Content in the United States, Washington state law governs the<br />

interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws<br />

of the state where you live govern all other claims, including claims under state consumer protection laws, unfair<br />

competition laws, and in tort.<br />

b. Outside the United States. If you acquired the Licensed Content in any other country, the laws of that country<br />

apply.<br />

14. LEGAL EFFECT. This agreement describes certain legal rights. You may have other rights under the laws of your country.<br />

You may also have rights <strong>with</strong> respect to the party from whom you acquired the Licensed Content. This agreement does<br />

not change your rights under the laws of your country if the laws of your country do not permit it to do so.


15. DISCLAIMER OF WARRANTY. The Licensed Content is licensed “as-is.” You bear the risk of using it.<br />

Microsoft gives no express warranties, guarantees or conditions. You may have additional consumer rights<br />

under your local laws which this agreement cannot change. To the extent permitted under your local laws,<br />

Microsoft excludes the implied warranties of merchantability, fitness for a particular purpose and noninfringement.<br />

16. LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND<br />

ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES,<br />

INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.<br />

This limitation applies to<br />

anything related to the Licensed Content, software, services, content (including code) on third party Internet sites, or<br />

third party programs; and<br />

claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the<br />

extent permitted by applicable law.<br />

It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or<br />

exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential<br />

or other damages.<br />

Please note: As this Licensed Content is distributed in Quebec, Canada, some of the clauses in this agreement<br />

are provided below in French.<br />

Remarque : Ce le contenu sous licence étant distribué au Québec, Canada, certaines des clauses dans ce contrat<br />

sont fournies ci-dessous en français.<br />

EXONÉRATION DE GARANTIE. Le contenu sous licence visé par une licence est offert « tel quel ». Toute utilisation de ce<br />

contenu sous licence est à votre seule risque et péril. Microsoft n’accorde aucune autre garantie expresse. Vous pouvez<br />

bénéficier de droits additionnels en vertu du droit local sur la protection dues consommateurs, que ce contrat ne peut modifier.<br />

La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, d’adéquation à un usage particulier<br />

et d’absence de contrefaçon sont exclues.<br />

LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES. Vous<br />

pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement à hauteur de<br />

5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y compris les dommages spéciaux,<br />

indirects ou accessoires et pertes de bénéfices.<br />

Cette limitation concerne:<br />

tout ce qui est relié au le contenu sous licence , aux services ou au contenu (y compris le code) figurant sur des sites<br />

Internet tiers ou dans des programmes tiers ; et<br />

les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, de négligence ou<br />

d’une autre faute dans la limite autorisée par la loi en vigueur.<br />

Elle s’applique également, même si Microsoft connaissait ou devrait connaître l’éventualité d’un tel dommage. Si votre pays<br />

n’autorise pas l’exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires ou de quelque nature que<br />

ce soit, il se peut que la limitation ou l’exclusion ci-dessus ne s’appliquera pas à votre égard.<br />

EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres droits prévus par les lois<br />

de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois de votre pays si celles-ci ne le<br />

permettent pas.


<strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010 ix


x <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Acknowledgements<br />

Microsoft Learning would like to acknowledge and thank the following for their contribution towards<br />

developing this title. Their effort at various stages in the development has ensured that you have a good<br />

classroom experience.<br />

Lin Joyner – Content Developer<br />

Lin is a subject matter expert, technical writer, and course designer <strong>with</strong> significant experience of creating<br />

training content for <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® and Microsoft SQL Server®. She has worked <strong>with</strong> <strong>Visual</strong><br />

<strong>Studio</strong> and SQL Server since version 6.0 of both products. Lin has designed and written training courses,<br />

labs, and e-learning materials about SQL Server, .NET development, and development of the Microsoft<br />

Office system, often taking the lead content developer role. Before she joined Content Master, Lin was a<br />

professional trainer for five years when she held the MCT and MCSD certifications.<br />

Dominic Betts – Content Developer<br />

Dominic currently works for the Distributed Systems Development Team at Content Master as a subject<br />

matter expert, technical writer, and course designer specializing in Microsoft technologies such as<br />

ASP.NET, F#, and Windows® Azure. Dominic’s recent projects have included a <strong>Visual</strong> <strong>Studio</strong> 2008<br />

Extensibility Training Development Kit (TDK), Windows Mobile® Quickstart and Readiness solutions, and<br />

advising the Windows Azure team on training content. Dominic has created and taught courses that cover<br />

a range of topics such as ASP.NET, .NET threading, and Enterprise Java. Dominic has also written papers<br />

and created technical content for the MSDN® Web site and elsewhere. He was the UK's IT Trainer of the<br />

Year in 2003.


Contents<br />

Module 1: Introduction to <strong>Data</strong> <strong>Access</strong> Technologies<br />

<strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010 xi<br />

Lesson 1: <strong>Data</strong> <strong>Access</strong> Technologies 1-3<br />

Lesson 2: <strong>Data</strong> <strong>Access</strong> Scenarios 1-11<br />

Lab: Analyzing <strong>Data</strong> <strong>Access</strong> Scenarios 1-15<br />

Module 2: Building Entity <strong>Data</strong> Models<br />

Lesson 1: Introduction to Entity <strong>Data</strong> Models 2-3<br />

Lesson 2: Modifying an Entity <strong>Data</strong> Model 2-16<br />

Lesson 3: Customizing an Entity <strong>Data</strong> Model 2-22<br />

Lab: Using Entity <strong>Data</strong> Models 2-30<br />

Module 3: Querying Entity <strong>Data</strong><br />

Lesson 1: Retrieving <strong>Data</strong> by Using LINQ to Entities 3-3<br />

Lesson 2: Retrieving <strong>Data</strong> by Using Entity SQL 3-13<br />

Lesson 3: Retrieving <strong>Data</strong> by Using the EntityClient Provider 3-18<br />

Lesson 4: Retrieving <strong>Data</strong> by Using Stored Procedures 3-27<br />

Lesson 5: Unit Testing Your <strong>Data</strong> <strong>Access</strong> Code 3-32<br />

Lab: Querying Entity <strong>Data</strong> 3-38<br />

Module 4: Creating, Updating, and Deleting Entity <strong>Data</strong><br />

Lesson 1: Understanding Change Tracking in the Entity Framework 4-3<br />

Lesson 2: Modifying <strong>Data</strong> in an Entity <strong>Data</strong> Model 4-9<br />

Lab: Creating, Updating, and Deleting Entity <strong>Data</strong> 4-21<br />

Module 5: Handling Multi-User Scenarios by Using Object Services<br />

Lesson 1: Handling Concurrency in the Entity Framework 5-3<br />

Lesson 2: Transactional Support in the Entity Framework 5-14<br />

Lab: Handling Multi-User Scenarios by Using Object Services 5-24<br />

Module 6: Building Optimized <strong>Solutions</strong> by Using Object Services<br />

Lesson 1: The Stages of Query Execution 6-3<br />

Lesson 2: Change Tracking and Object Materialization 6-8<br />

Lesson 3: Using Compiled Queries 6-14<br />

Lesson 4: Using Design-Time Generated Entity Framework Views 6-19<br />

Lesson 5: Monitoring Performance 6-23<br />

Lesson 6: Performing Asynchronous <strong>Data</strong> Modifications 6-28<br />

Lab: Building Optimized <strong>Solutions</strong> by Using Object Services 6-33


xii <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module 7: Customizing Entities and Building Custom Entity Class<br />

Lesson 1: Overriding Generated Classes 7-3<br />

Lesson 2: Using Templates to Customize Entities 7-14<br />

Lesson 3: Creating and Using Custom Entity Classes 7-26<br />

Lab: Customizing Entities and Building Custom Entity Classes 7-37<br />

Module 8: Using POCO Classes <strong>with</strong> the Entity Framework<br />

Lesson 1: Requirements for POCO Classes 8-3<br />

Lesson 2: POCO Classes and Lazy Loading 8-10<br />

Lesson 3: POCO Classes and Change Tracking 8-15<br />

Lesson 4: Extending Entity Types 8-20<br />

Lab: Using POCO Classes <strong>with</strong> the Entity Framework 8-25<br />

Module 9: Building an N-Tier Solution by Using the Entity Framework<br />

Lesson 1: Designing an N-Tier Solution 9-4<br />

Lesson 2: Defining Operations and Implementing <strong>Data</strong> Transport<br />

Structures 9-10<br />

Lesson 3: Protecting <strong>Data</strong> and Operations 9-24<br />

Lab: Building an N-Tier Solution by Using the Entity Framework 9-31<br />

Module 10: Handling Updates in an N-Tier Solution by Using the Entity Framework<br />

Lesson 1: Tracking Entities and Persisting Changes 10-3<br />

Lesson 2: Managing Exceptions in an N-Tier Solution 10-30<br />

Lab: Handling Updates in an N-Tier Solution by Using the Entity<br />

Framework 10-40<br />

Module 11: Building Occasionally Connected <strong>Solutions</strong><br />

Lesson 1: Offline <strong>Data</strong> Caching by Using XML 11-3<br />

Lesson 2: Using the Sync Framework 11-17<br />

Lab: Building Occasionally Connected <strong>Solutions</strong> 11-34<br />

Module 12: Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services<br />

Lesson 1: Introducing WCF <strong>Data</strong> Services 12-3<br />

Lesson 2: Creating a WCF <strong>Data</strong> Service 12-13<br />

Lesson 3: Consuming a WCF <strong>Data</strong> Service 12-41<br />

Lesson 4: Protecting <strong>Data</strong> and Operations in a WCF <strong>Data</strong> Service 12-66<br />

Lab: Creating and Using WCF <strong>Data</strong> Services 12-77


About This Course<br />

About This Course xiii<br />

This section provides you <strong>with</strong> a brief description of the course, audience, suggested prerequisites, and<br />

course objectives.<br />

Course Description<br />

This course teaches you how to design and develop data access in your applications. The course discusses<br />

how to choose which data access technology is appropriate for your business and application needs and<br />

then teaches you how to use the key technologies, including the ADO.NET Entity Framework, Language-<br />

Integrated Query (LINQ), Windows® Communication Foundation (WCF) <strong>Data</strong> Services, <strong>Microsoft®</strong> Sync<br />

Framework, and ADO.NET.<br />

Audience<br />

This course is intended for professional .NET software developers who use Microsoft <strong>Visual</strong> <strong>Studio</strong>® in a<br />

team-based, medium-sized to large development environment. Audience members are expected to have<br />

experience of implementing data access and data binding <strong>with</strong>in their Web or Windows Client<br />

applications and are interested in learning to optimize data access <strong>with</strong>in their applications by using the<br />

Entity Framework, LINQ, and ADO.NET. Audience members should be experienced users of <strong>Visual</strong> <strong>Studio</strong><br />

2008 Service Pack 1 (SP1) or newer releases of the <strong>Visual</strong> <strong>Studio</strong> product. They should also have some<br />

experience of using <strong>Visual</strong> <strong>Studio</strong> 2010 for either Windows Client or Web application development.<br />

Student Prerequisites<br />

This course requires that you meet the following prerequisites:<br />

• An understanding of the problem-solving techniques that apply to software development, including<br />

modern software development models, the software development life cycle, the concepts of eventdriven<br />

and object-oriented programming, creating use-case diagrams, designing and building a user<br />

interface, and developing a structured application.<br />

• A basic understanding of Web, macro, and Windows scripting techniques and some hands-on<br />

experience of writing scripts.<br />

• A general understanding of the purpose, function, and features of the common language runtime<br />

(CLR), the Microsoft .NET Framework class libraries, the common type system, component<br />

interoperation, cross-language interoperation, application domains, and runtime hosts that the .NET<br />

Framework supports.<br />

• Experience of using <strong>Visual</strong> <strong>Studio</strong> 2008 to use variables, operators, and branching and looping<br />

statements; create and use classes, methods, and events; identify syntax and logic errors; and access<br />

data from a data source.<br />

• Experience in object-oriented design and development including creating and accessing classes and<br />

class properties; creating and accessing methods and overloaded methods; implementing inheritance,<br />

base classes, and abstract classes; declaring, raising, and handling events; responding to and throwing<br />

exceptions; implementing interfaces and polymorphism; implementing shared and static members;<br />

implementing generics; and creating components and class libraries.<br />

• Experience in n-tier application design and development, including managing a software<br />

development process; controlling input at the user interface level in Windows Client and Web<br />

applications; debugging, tracing, and profiling .NET applications; monitoring and logging .NET<br />

applications; implementing basic testing best practices; performing basic data access tasks by using<br />

LINQ; implementing basic security best practices in .NET applications; implementing basic service<br />

calls; using .NET configuration files; and deploying .NET Framework applications by using ClickOnce<br />

and the Microsoft Installer.


xiv About This Course<br />

• <strong>Data</strong> access experience in Windows Client application development, including connecting to a data<br />

source, implementing data binding, and implementing data validation at the user interface layer.<br />

• <strong>Data</strong> access experience in Web application development, including connecting to a data source,<br />

implementing dynamic data, and implementing data validation at the user interface layer.<br />

Course Objectives<br />

After completing this course, students will be able to:<br />

• Evaluate business cases and select an appropriate combination of data access technologies and tools<br />

for each case.<br />

• Use the tools that the Entity Framework provides to map the conceptual model that the business<br />

logic of an application uses to the logical data model that the database provides.<br />

• Query an Entity <strong>Data</strong> Model (EDM) by using common methods such as LINQ to Entities, Entity SQL,<br />

and the classes in the EntityClient namespace.<br />

• Perform data modification tasks through an EDM by using LINQ to Entities, Entity SQL, and the<br />

classes in the EntityClient namespace.<br />

• Describe the optimistic concurrency model in the Entity Framework and manage transactions in Entity<br />

Framework applications.<br />

• Describe the best practices for designing and building a scalable, optimized data access layer by using<br />

Object Services.<br />

• Customize and extend entities <strong>with</strong> their own business logic and use advanced mappings to shape the<br />

data model to their business and application requirements.<br />

• Reuse existing plain-old CLR object (POCO) business classes in a data access layer that is built by<br />

using the Entity Framework.<br />

• Address the architectural issues that can arise when building an n-tier enterprise application by using<br />

the Entity Framework.<br />

• Build extensible solutions that can update data in an n-tier enterprise application by using the Entity<br />

Framework.<br />

• <strong>Access</strong> offline data or data that has limited availability in client applications.<br />

• Design, develop, and consume a simple data service.<br />

• Use WCF <strong>Data</strong> Services to update and delete data and handle multi-user concerns.<br />

• Develop high-performance, scalable ADO.NET applications that can query and update data.<br />

• Use LINQ to SQL to develop against a logical model that abstracts the low-level details of querying<br />

ADO.NET tables and result sets.<br />

Course Outline<br />

This section provides an outline of the course:<br />

Module 1, “Introduction to <strong>Data</strong> <strong>Access</strong> Technologies,” introduces the commonly used data access<br />

technologies and the scenarios that they are best suited to.<br />

Module 2, “Building Entity <strong>Data</strong> Models,” introduces the concepts of data modeling, and in particular,<br />

EDMs. It explains how developers can use EDMs to decouple the conceptual data structure in their<br />

applications from the logical data structure in the data store.


About This Course xv<br />

Module 3, “Querying Entity <strong>Data</strong>,” explains how to use LINQ to Entities, Entity SQL, the EntityClient<br />

provider for the Entity Framework, and stored procedures to retrieve data from an entity model, and<br />

describes when each approach should be used.<br />

Module 4, “Creating, Updating, and Deleting Entity <strong>Data</strong>,” introduces the ways that the Entity Framework<br />

enables data modifications. It also describes how the Entity Framework implements change tracking.<br />

Module 5, “Handling Multi-User Scenarios by Using Object Services,” introduces the concurrency model<br />

and describes how the Entity Framework can make use of transactions to ensure data integrity.<br />

Module 6, “Building Optimized <strong>Solutions</strong> by Using Object Services,” describes best practices for designing<br />

and building a scalable, optimized data access layer by using Object Services.<br />

Module 7, “Customizing Entities and Building Custom Entity Classes,” describes how to customize and<br />

extend entities <strong>with</strong> your own business logic.<br />

Module 8, “Using POCO Classes <strong>with</strong> the Entity Framework,” introduces the ways to define custom entity<br />

classes in Entity Framework applications. By default, <strong>Visual</strong> <strong>Studio</strong> generates a set of entity classes from<br />

the EDM. This module describes how to use an existing set of POCO business classes in the application<br />

and how to extend the generated entity classes to add custom business functionality to the entity objects.<br />

Module 9, “Building an N-Tier Solution by Using the Entity Framework,” introduces the architectural<br />

issues that may be encountered when building an n-tier solution and explains how to solve these<br />

problems by using the Entity Framework.<br />

Module 10, “Handling Updates in an N-Tier Solution by Using the Entity Framework,” describes how to<br />

handle data modifications in an n-tier solution and how to manage the exceptions that can occur during<br />

the data modification process.<br />

Module 11, “Building Occasionally Connected <strong>Solutions</strong>,” describes how to access offline or occasionally<br />

connected data in client applications. It describes how to cache data in local XML files by using LINQ to<br />

XML and how to implement an occasionally connected application by using Sync Framework.<br />

Module 12, “Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services,” introduces the purpose and features of a WCF<br />

<strong>Data</strong> Service and describes how to create and consume a WCF <strong>Data</strong> Service. It also discusses how to grant<br />

and restrict access to resources that a WCF <strong>Data</strong> Service exposes.<br />

Module 13, “Updating <strong>Data</strong> by Using WCF <strong>Data</strong> Services,” describes how to use WCF <strong>Data</strong> Services to<br />

modify data. WCF <strong>Data</strong> Services use standard Internet protocols such as HTTP and the Atom Publishing<br />

Protocol to enable update access to data across the Internet or a corporate network.<br />

Module 14, “Using ADO.NET,” introduces ADO.NET and explains how to use it to develop scalable, highperformance,<br />

data-driven applications.<br />

Module 15, “Using LINQ to SQL,” introduces LINQ to SQL and explains how to use it to abstract the lowlevel<br />

details of ADO.NET queries by developing against a logical data model.


xvi About This Course<br />

Course Materials<br />

The following materials are included <strong>with</strong> your kit:<br />

• Course Handbook A succinct classroom learning guide that provides all the critical technical<br />

information in a crisp, tightly-focused format, which is just right for an effective in-class learning<br />

experience.<br />

• Lessons: Guide you through the learning objectives and provide the key points that are critical to<br />

the success of the in-class learning experience.<br />

• Labs: Provide a real-world, hands-on platform for you to apply the knowledge and skills learned<br />

in the module.<br />

• Module Reviews and Takeaways: Provide improved on-the-job reference material to boost<br />

knowledge and skills retention.<br />

• Lab Answer Keys: Provide step-by-step lab solution guidance at your finger tips when it’s<br />

needed.<br />

Course Companion Content on the http://www.microsoft.com/learning/companionmoc/ Site:<br />

Searchable, easy-to-navigate digital content <strong>with</strong> integrated premium on-line resources designed to<br />

supplement the Course Handbook.<br />

• Modules: Include companion content, such as questions and answers, detailed demo steps and<br />

additional reading links, for each lesson. Additionally, they include Lab Review questions and answers<br />

and Module Reviews and Takeaways sections, which contain the review questions and answers, best<br />

practices, common issues and troubleshooting tips <strong>with</strong> answers, and real-world issues and scenarios<br />

<strong>with</strong> answers.<br />

• Resources: Include well-categorized additional resources that give you immediate access to the most<br />

up-to-date premium content on TechNet, MSDN®, Microsoft Press®<br />

Student Course files on the http://www.microsoft.com/learning/companionmoc/ Site: Includes the<br />

Allfiles.exe, a self-extracting executable file that contains all the files required for the labs and<br />

demonstrations.<br />

• Course evaluation At the end of the course, you will have the opportunity to complete an online<br />

evaluation to provide feedback on the course, training facility, and instructor.<br />

• To provide additional comments or feedback on the course, send e-mail to<br />

support@mscourseware.com. To inquire about the Microsoft Certification Program, send e-mail<br />

to mcphelp@microsoft.com.


Virtual Machine Environment<br />

About This Course xvii<br />

This section provides the information for setting up the classroom environment to support the business<br />

scenario of the course.<br />

Virtual Machine Configuration<br />

In this course, you will use Hyper-V to perform the labs.<br />

Each classroom computer serves as a host for one virtual machine that will run in Hyper-V. There is one<br />

Hyper-V virtual machine for each lab in this course, named 10265A-GEN-DEV-XX. Each virtual machine<br />

has an internal computer name of 10265A-GEN-DEV.<br />

Important: At the end of each lab, you must close the virtual machine and must not save any<br />

changes. To close a virtual machine <strong>with</strong>out saving the changes, perform the following steps: 1. On<br />

the virtual machine, on the Action menu, click Close. 2. In the Close dialog box, in the What do you<br />

want the virtual machine to do? list, click Turn off and delete changes, and then click OK.<br />

The following table shows the role of each virtual machine used in this course:<br />

Virtual machine Role<br />

10265A-GEN-DEV-02 The virtual machine to use for Lab 2<br />

10265A-GEN-DEV-03 The virtual machine to use for Lab 3<br />

10265A-GEN-DEV-04 The virtual machine to use for Lab 4<br />

10265A-GEN-DEV-05 The virtual machine to use for Lab 5<br />

10265A-GEN-DEV-06 The virtual machine to use for Lab 6<br />

10265A-GEN-DEV-07 The virtual machine to use for Lab 7<br />

10265A-GEN-DEV-08 The virtual machine to use for Lab 8<br />

10265A-GEN-DEV-09 The virtual machine to use for Lab 9<br />

10265A-GEN-DEV-10 The virtual machine to use for Lab 10<br />

10265A-GEN-DEV-11 The virtual machine to use for Lab 11<br />

10265A-GEN-DEV-12 The virtual machine to use for Lab 12<br />

10265A-GEN-DEV-13 The virtual machine to use for Lab 13<br />

10265A-GEN-DEV-14 The virtual machine to use for Lab 14<br />

10265A-GEN-DEV-15 The virtual machine to use for Lab 15<br />

Software Configuration<br />

The following software is installed on each VM:<br />

• <strong>Visual</strong> <strong>Studio</strong> 2010 Ultimate


xviii About This Course<br />

• Microsoft SQL Server® Management <strong>Studio</strong> Express<br />

Course Files<br />

There are files associated <strong>with</strong> the labs in this course. The lab files are located in the folder<br />

E:\Labfiles\LabXX on the student computers.<br />

Classroom Setup<br />

Each classroom computer will have the same virtual machine configured in the same way.<br />

Course Hardware Level<br />

To ensure a satisfactory student experience, Microsoft Learning requires a minimum equipment<br />

configuration for trainer and student computers in all Microsoft Certified Partner for Learning <strong>Solutions</strong><br />

(CPLS) classrooms in which Official Microsoft Learning Product courseware are taught.<br />

The course requires that you have a computer that meets or exceeds hardware level 6, which prescribes<br />

the following:<br />

• Intel Virtualization Technology (Intel VT) or AMD Virtualization (AMD-V) processor.<br />

• Dual 120-GB hard disks, 7,200 RAM Serial Advanced Technology Attachment (SATA) or better<br />

(configured as a stripe array).<br />

• 4 GB of RAM expandable to 8 GB or higher.<br />

• DVD drive.<br />

• Network adapter.<br />

• Super VGA (SVGA) 17-inch monitor.<br />

• Microsoft mouse or compatible pointing device.<br />

• Sound card <strong>with</strong> amplified speakers.<br />

In addition, the instructor computer must be connected to a projection display device that supports SVGA<br />

1024 × 768, 16-bit colors


Module 1<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies<br />

Contents:<br />

Lesson 1: <strong>Data</strong> <strong>Access</strong> Technologies 1-3<br />

Lesson 2: <strong>Data</strong> <strong>Access</strong> Scenarios 1-11<br />

Lab: Analyzing <strong>Data</strong> <strong>Access</strong> Scenarios 1-15<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-1


1-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

Before you start developing an application, it is important to know which technology is most appropriate<br />

to use. In this module, you will be introduced to commonly used data access technologies and the<br />

scenarios that they are best suited to.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Describe the key data access technologies that are available to <strong>Microsoft®</strong> .NET Framework<br />

developers.<br />

• Assign appropriate data access technologies to common data access scenarios.


Lesson 1<br />

<strong>Data</strong> <strong>Access</strong> Technologies<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-3<br />

Many data access technologies are available to today’s developer. This lesson introduces each of the key<br />

Microsoft technologies for data access, including the ADO.NET Entity Framework, Language-Integrated<br />

Query (LINQ), the Microsoft Sync Framework, Windows® Communication Foundation (WCF) <strong>Data</strong><br />

Services, and ADO.NET.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the ADO.NET Entity Framework.<br />

• Describe LINQ.<br />

• Describe the Sync Framework.<br />

• Describe WCF <strong>Data</strong> Services.<br />

• Describe ADO.NET.


1-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Overview of the ADO.NET Entity Framework<br />

Key Points<br />

<strong>Data</strong> access code has traditionally been tedious to develop. Queries are written as text strings that cannot<br />

be type-checked or syntax-checked at compile time, complex joins are required to link data items that<br />

belong together from a business point of view, and results are returned as untyped data records.<br />

The ADO.NET Entity Framework solves these problems. It enables you to write your queries directly in the<br />

programming language of your choice, to write data access applications by programming against a<br />

conceptual model of the data instead of against a normalized storage schema, and to access returned<br />

data as typed entities.<br />

Entity <strong>Data</strong> Models<br />

Even if you do not explicitly develop one, all applications have a conceptual model that defines the<br />

objects <strong>with</strong>in the application and the relationships between them. <strong>Data</strong>base applications also have a<br />

physical model that defines how the data is stored in the database and developers traditionally have to<br />

write code to navigate between the two.<br />

The Entity Framework aims to eliminate this impedance mismatch and enable developers to code against<br />

their conceptual model while it manages the mapping to the physical model for them. It introduces the<br />

concept of an Entity <strong>Data</strong> Model (EDM), which is a conceptual model that is mapped to the physical<br />

model of a data store. The model and mappings are stored in XML files that describe the structure of both<br />

models and the mappings in between.<br />

After you have added an EDM to your project, you can simply manage and work <strong>with</strong> the conceptual<br />

model that you defined, while leaving the physical storage concerns to the database engineers. An EDM<br />

also makes it easier to change data sources <strong>with</strong>out requiring code modifications to your application. You<br />

can change the connection information of the model and make any changes to the structural part of the<br />

EDM. As long as the mappings are updated, your code will still run as expected.


Entity SQL<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-5<br />

The Entity Framework provides you <strong>with</strong> a new query language called Entity Structured Query Language<br />

(Entity SQL). This is a storage-independent query language that enables you to query and manipulate<br />

EDM constructs. The Entity Framework uses storage-specific data providers to translate the generic Entity<br />

SQL commands that you write into storage-specific queries that your underlying data source can use.<br />

Entity Client<br />

The EntityClient provider is an ADO.NET data provider that supports accessing data stored in an EDM. It<br />

enables you to write queries in Entity SQL while using familiar objects from the ADO.NET object model.<br />

Object Services<br />

Object Services enables you to work <strong>with</strong> the common language runtime (CLR) objects that the<br />

conceptual model generates. The services that it provides include change tracking, relationship<br />

management, state management, Entity SQL query support, identity resolution, and transmission of<br />

changes back to the data source.<br />

Question: What is the key advantage to using the Entity Framework over other data access technologies?<br />

Additional Reading<br />

For more information about the ADO.NET Entity Framework, see the ADO.NET Entity Framework page<br />

http://go.microsoft.com/fwlink/?LinkId=196089.


1-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Overview of LINQ<br />

Key Points<br />

Traditionally, you can divide the code in data-centric applications into two types: your application logic,<br />

which you write in a programming language, and your data manipulation code, which you write in a<br />

query language. This architecture means that you need to learn two languages and that you have to write<br />

queries as string literals that Microsoft IntelliSense® does not type-check or support.<br />

LINQ solves these issues by enabling you to perform set-based data queries in your usual programming<br />

language. It supports type-checking and syntax-checking at compile time in addition to IntelliSense<br />

because the queries are directly in your code. This support is intended to reduce the time that you spend<br />

debugging your code.<br />

There are several flavors of LINQ for different types of data source, including:<br />

• LINQ to Entities. You use LINQ to Entities to work <strong>with</strong> data in an EDM.<br />

• LINQ to Objects. You use LINQ to Objects to query collections that implement the IEnumerable or<br />

IEnumerable(T) interface. It enables you to write declarative code to manage data that is stored in a<br />

collection <strong>with</strong>out using complex loops.<br />

• LINQ to XML. You use LINQ to XML to work <strong>with</strong> XML data that is stored in memory. It provides the<br />

functionality that the Document Object Model (DOM) supplies, alongside support for LINQ query<br />

expressions.<br />

• LINQ to SQL. You use LINQ to SQL to work <strong>with</strong> data that is stored in a Microsoft SQL Server®<br />

database.<br />

Each of these is part of LINQ, so the structure of the queries is common across all, resulting in increased<br />

code reuse and knowledge reuse.<br />

Question: What advantages over writing queries as string literals does LINQ provide?


Additional Reading<br />

For more information about LINQ, see the LINQ Portal page at<br />

http://go.microsoft.com/fwlink/?LinkId=196090.<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-7


1-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Overview of the Sync Framework<br />

Key Points<br />

The Sync Framework provides a synchronization platform that you can use to build offline applications<br />

and Occasionally Connected Applications (OCAs) by copying data from one source and transferring it to<br />

another. The Sync Framework supports any data from any data source using any protocol.<br />

The Sync Framework provides you <strong>with</strong> a flexible, yet powerful, synchronization system that supports:<br />

• Custom providers. The Sync Framework uses providers to connect to data sources. It ships <strong>with</strong><br />

providers for ADO.NET-enabled data sources, files and folders, and FeedSync feeds such as Real<br />

Simple Syndication (RSS) and Atom feeds. If you need to use other data sources, you can create your<br />

own custom provider for any type of data.<br />

• Conflict handling. The Sync Framework detects the most common conflicts that occur and raises an<br />

event that you can handle to resolve the conflict in your chosen way.<br />

• Filtered subsets of data. You can filter the data that you are synchronizing to minimize the network<br />

traffic during synchronization.<br />

You can configure synchronization to copy data from client to server, from server to client, or in both<br />

directions.<br />

Question: What are the three data source types for which providers ship <strong>with</strong> the Sync Framework?<br />

Additional Reading<br />

For more information about the Sync Framework, see the Microsoft Sync Framework page at<br />

http://go.microsoft.com/fwlink/?LinkId=196091.


Overview of WCF <strong>Data</strong> Services<br />

Key Points<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-9<br />

WCF <strong>Data</strong> Services (previously known as ADO.NET <strong>Data</strong> Services) enable you to create and access data<br />

services over the Internet or an intranet. You expose your data as resources that client applications can<br />

access by using a URI. These resources are exposed as sets of entities that are related by associations, the<br />

same concepts as in an EDM. However, you can expose data from many types of storage, including<br />

databases, CLR classes, and late-bound data types.<br />

By using URIs for addressing, any client application that supports the standard HTTP verbs, GET, PUT,<br />

POST, and DELETE, can use WCF <strong>Data</strong> Services. You access resources by building a URI that traverses the<br />

entities and relationships in the model. For example, the URI in the following code example returns all of<br />

the orders that contact 1 has made.<br />

http://localhost:12344/AdventureWorks.svc/Contacts(1)/SalesOrderHeader<br />

You can extend the URI to include filters, sorting, and paging of data. You can also extend your services<br />

by writing service operations as methods that perform business logic at the server. These methods are<br />

then accessible as URIs in a similar fashion to resources. You can also define interceptors, which are called<br />

when you query, insert, update, or delete data and may validate or alter the data, enforce security, or<br />

reject the change.<br />

Question: Which HTTP verb is used to insert data into the data source?<br />

Additional Reading<br />

For more information about WCF <strong>Data</strong> Services, see the WCF <strong>Data</strong> Services page at<br />

http://go.microsoft.com/fwlink/?LinkId=196092.


1-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Overview of ADO.NET<br />

Key Points<br />

ADO.NET is a flexible and lightweight framework that enables you to build data access applications for<br />

relational, XML, and application data. It supports writing of client and middle-tier components for<br />

Windows-based and Web-based applications. When you use ADO.NET, your data access code is tightly<br />

coupled to your data, meaning that changes to the data storage structure require changes to your code. It<br />

also requires that you work <strong>with</strong> data in the logical model, <strong>with</strong>out a conceptual model to simplify your<br />

code.<br />

ADO.NET is the basis of most of the .NET Framework data access technologies and it still has its own place<br />

in data access development today. Examples include situations such as the need to support legacy<br />

applications, applications where you do not require the additional functionality of higher-level<br />

technologies such as the Entity Framework, and occasions when you are building highly optimized<br />

applications.<br />

ADO.NET provides you <strong>with</strong> a consistent set of classes that you can use to access SQL Server, XML, OLE<br />

DB, and Open <strong>Data</strong>base Connectivity (ODBC) data. You connect to the data source by using a data<br />

provider that translates the ADO.NET commands into the connection information that your data source<br />

requires. The data providers provide key objects that enable you to execute commands, stream data, and<br />

load data. In addition to the generic data providers, you can use data-source-specific data providers, such<br />

as the .NET Framework <strong>Data</strong> Provider for SQL Server, which are tuned for their specific data source.<br />

Question: How can you ensure the best performance of your ADO.NET code?<br />

Additional Reading<br />

For more information about ADO.NET, see the ADO.NET page at<br />

http://go.microsoft.com/fwlink/?LinkId=196093.


Lesson 2<br />

<strong>Data</strong> <strong>Access</strong> Scenarios<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-11<br />

Just as there are many data access technologies, there are also many types of application that need to<br />

access data. It is important that you understand the requirements of your application so that you can<br />

correctly match the appropriate technology to it.<br />

This lesson discusses the different types of data access application and the appropriate technology to use<br />

for each.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the appropriate technologies to use when you are developing different types of enterprise<br />

application.<br />

• Describe the appropriate technologies to use when you are supporting legacy applications.


1-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

New Development Projects<br />

Key Points<br />

Enterprise applications include applications that are used internally, accessed externally by employees, and<br />

accessed externally by partner organizations. Each of these types of application has its own requirements<br />

in terms of functionality and security that you must assess before you select the appropriate technology<br />

for the application.<br />

Internal Applications<br />

Many internal and external applications often use the same data from a data source. If your applications<br />

are tightly bound to the structure of the data source, any changes to the structure of the data will require<br />

changes to your applications. Therefore, it is recommended that you loosely couple your applications to<br />

your data so that changes have minimal impact on your application code.<br />

The Entity Framework provides you <strong>with</strong> the concept of EDMs that enable you to loosely couple your data<br />

to your applications. If the structure of the data changes, you can simply update the data store section of<br />

the model and the code in your application will continue to function as expected. EDMs also enable you<br />

to transform the data into a conceptual form that matches the business requirements of your application<br />

<strong>with</strong>out worrying about the logical structure of the underlying data source.<br />

Enterprise Applications <strong>Access</strong>ed Externally by Employees<br />

With the increase of employees working from home and visiting customer sites, you will often need to<br />

make your internal applications externally accessible by employees who are using different types of<br />

hardware. If you just make your data sources accessible from outside the corporate network, you may find<br />

that users have to wait for slow or unreliable network connections to download large amounts of data,<br />

and that remote users often do not have a network connection that they can use to access the data. These<br />

issues often make it favorable for the users to have their own local copy of the data that they can update<br />

as and when necessary.


Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-13<br />

LINQ to XML provides read-only access to data that is stored in a local XML file. You can develop an<br />

application that caches data locally when it is connected to the central data store and then uses the local<br />

information cache when it is disconnected. Alternatively, the Sync Framework supports the development<br />

of OCAs that provide read/write access to local data when the application is offline and synchronize <strong>with</strong><br />

the central data store when the application is online. This ensures that users have constant access to the<br />

data <strong>with</strong>out impacting the server performance for internal users.<br />

Enterprise Applications <strong>Access</strong>ed Externally by Partners<br />

Although you could allow partners to use synchronization applications to access and manipulate your<br />

corporate data, security issues are unlikely to make this advisable. When you work <strong>with</strong> partner<br />

organizations, you need to ensure that they can only access specific data and that any changes that they<br />

make are valid and safe.<br />

WCF <strong>Data</strong> Services enable you to make your corporate data sources accessible over an intranet or the<br />

Internet by using standard protocols and addressing. You can use service operations and interceptors to<br />

restrict access to appropriate data and validate changes before saving them in the database.<br />

Question: Why would you not use the Sync Framework to develop an application that partner<br />

organizations will use to access your corporate data?<br />

Additional Reading<br />

For more information about developing new applications, see the Microsoft Case Studies: Veracity<br />

<strong>Solutions</strong> page at http://go.microsoft.com/fwlink/?LinkId=194013 and the Microsoft Case Studies: Ian<br />

Mercer page at http://go.microsoft.com/fwlink/?LinkId=194014.


1-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Legacy Applications<br />

Key Points<br />

Few organizations will rewrite their applications <strong>with</strong> every release of a new or updated technology.<br />

Therefore, it is likely that you will need to support legacy applications, in addition to maintaining them<br />

and writing new applications that interact <strong>with</strong> them and their data sources.<br />

ADO.NET enables you to write tightly coupled components and applications that support different<br />

versions of the .NET Framework. It is useful when you do not need the functionality that the higher-level<br />

technologies, such as the Entity Framework, provide. By using ADO.NET, you can write client-agnostic<br />

data tiers that support legacy applications in addition to newer applications. It also provides fast access to<br />

the data source.<br />

Question: Name one disadvantage of using ADO.NET for data access applications.


Lab: Analyzing <strong>Data</strong> <strong>Access</strong> Scenarios<br />

Objectives<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-15<br />

After completing this lab, you will be able to determine the appropriate technology to use for common<br />

data access scenarios.<br />

Introduction<br />

In this lab, you will analyze four common data access scenarios and decide which data technology to use<br />

for each.


1-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

You are working as a data expert for Adventure Works Cycles. You have been asked to analyze the<br />

database application requirements to determine which data access technologies should be used to meet<br />

the requirements of a range of applications and scenarios.


Exercise 1: Identifying <strong>Data</strong> <strong>Access</strong> Technologies<br />

Scenario<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-17<br />

In this exercise, you will determine the appropriate technology for each given data access scenario.<br />

The main tasks for this exercise are as follows:<br />

1. Identify the appropriate data access technology for a customer management application.<br />

2. Identify the appropriate data access technology for an order management application.<br />

3. Identify the appropriate data access technology for a delivery management application.<br />

4. Identify the appropriate data access technology for a product management application.<br />

Task 1: Identify the appropriate data access technology for a customer management<br />

application<br />

Scenario<br />

Adventure Works Cycles has a corporate database that contains customer information. Employees can<br />

browse and maintain customer data, but customers only have read access to the data. Employees use a<br />

Windows Presentation Foundation (WPF) application to access their required data and the corporate<br />

network has no bandwidth issues. The corporate database is several years old and changes are made to<br />

the database structure twice a year.<br />

• Given the scenario above, on a piece of paper, write down what you think is the most appropriate<br />

data access technology to solve the business problem.<br />

Task 2: Identify the appropriate data access technology for an order management<br />

application<br />

Scenario<br />

Adventure Works Cycles has a requirement to enable salespeople to view and create orders during offsite<br />

meetings and add them to the database at a later time. Therefore, the data access layer needs to copy<br />

database content on the remote device to the server.<br />

• Given the scenario above, on a piece of paper, write down what you think is the most appropriate<br />

data access technology to solve the business problem.<br />

Task 3: Identify the appropriate data access technology for a delivery management<br />

application<br />

Scenario<br />

Adventure Works Cycles has agreed to provide an ASP.NET Model-View-Controller (MVC) Web<br />

application to delivery companies to query and maintain the delivery status of orders as they ship them.<br />

The application has to provide fast and responsive access to the database in a potentially low-bandwidth<br />

environment. Adventure Works Cycles has to produce a highly robust data access layer for this application<br />

in a very compressed time scale.<br />

• Given the scenario above, on a piece of paper, write down what you think is the most appropriate<br />

data access technology to solve the business problem.


1-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 4: Identify the appropriate data access technology for a product management<br />

application<br />

Scenario<br />

Adventure Works Cycles previously developed what are now legacy applications that enable employees to<br />

browse and maintain a list of products, and enable customers to browse a list of products. The employee<br />

application is a Windows Forms application, and the customer application is a Web application. Both<br />

applications were built by using the .NET Framework 2.0 or earlier. The underlying database structure is<br />

stable and has not been changed since it was first designed. Employees have no bandwidth limitations,<br />

although customers may do.<br />

1. Given the scenario above, on a piece of paper, write down what you think is the most appropriate<br />

data access technology to solve the business problem.<br />

2. Discuss the benefits and drawbacks of each of your solutions <strong>with</strong> one of the other students.


Lab Review<br />

Review Questions<br />

1. What is the appropriate technology for the customer management application?<br />

2. What is the appropriate technology for the order management application?<br />

3. What is the appropriate technology for the delivery management application?<br />

4. What is the appropriate technology for the product management application?<br />

Introduction to <strong>Data</strong> <strong>Access</strong> Technologies 1-19


1-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. What key advantage does the use of an EDM provide?<br />

2. What key advantage does synchronization provide over users accessing the data in the central<br />

database?<br />

3. What type of client application can use a WCF <strong>Data</strong> Service?


Module 2<br />

Building Entity <strong>Data</strong> Models<br />

Contents:<br />

Lesson 1: Introduction to Entity <strong>Data</strong> Models 2-3<br />

Lesson 2: Modifying an Entity <strong>Data</strong> Model 2-16<br />

Lesson 3: Customizing an Entity <strong>Data</strong> Model 2-22<br />

Lab: Using Entity <strong>Data</strong> Models 2-30<br />

Building Entity <strong>Data</strong> Models 2-1


2-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

<strong>Data</strong> modeling is a well established part of database design, enabling database administrators and<br />

developers to model how data is physically and logically stored. The Entity Framework introduces the<br />

concept of an Entity <strong>Data</strong> Model (EDM) which enables the you to model the data in a meaningful way for<br />

use in your application.<br />

This module introduces the concepts of data modeling, and in particular, EDMs. It explains how you can<br />

use EDMs to decouple the conceptual data structure in your applications from the logical data structure in<br />

the data store.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Describe and create an Entity <strong>Data</strong> Model.<br />

• Modify an Entity <strong>Data</strong> Model by using the Entity Designer.<br />

• Customize a model to meet your business requirements.


Lesson 1<br />

Introduction to Entity <strong>Data</strong> Models<br />

Building Entity <strong>Data</strong> Models 2-3<br />

Before you can use an EDM to its full potential, you need to understand how to use the available tools to<br />

create an EDM and the structure of the model that they create. This lesson shows you how to create an<br />

EDM from an existing database and explains how the model is represented in XML.<br />

Objectives<br />

After you have completed this lesson, you will be able to:<br />

• Describe logical and conceptual models.<br />

• Describe the two key model design strategies.<br />

• Create models in the Entity Designer.<br />

• Map a model to a database.<br />

• Describe the XML behind a model.<br />

• Describe the issues you should consider when you deploy a model.


2-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Introduction to <strong>Data</strong> Models<br />

Key Points<br />

There are three types of models that are commonly used in data modeling:<br />

• The physical model describes how the data is stored in the database, including details about<br />

partitions and indexes. Although this model is very important from a database administrator’s point<br />

of view, it is not a convenient way for a developer to work <strong>with</strong> the data.<br />

• The logical model builds on the physical model and describes how the data is stored in tables and<br />

related by keys. Although this is a useful model for a database administrator or database developer, it<br />

is still not the ideal way for an application developer to view the data. When coding against the<br />

logical model, you are likely to require complex queries that involve multiple joins.<br />

• The conceptual model describes the semantics of the business view of the data. It defines entities and<br />

relationships in a business sense and is mapped to the logical model to enable easy access to the<br />

underlying data. Because the conceptual model describes the items in your application in business<br />

terms, you will be able to work <strong>with</strong> data items in a meaningful way, avoiding the complex queries<br />

written against a logical model.<br />

The <strong>Microsoft®</strong> .NET Entity Framework implements the conceptual model as an EDM. The EDM is<br />

mapped to the logical model of the database, and the Entity Framework is responsible for translating your<br />

business functionality into data source–compliant commands.<br />

Entities vs. Entity Objects vs. Business Objects<br />

Entities are things in an application that need to be modeled by data. For example, in an online ordering<br />

application, entities may include customers, products, and orders. An entity is simply a description of the<br />

item and its properties. Entities are linked by relationships. For example, an order is related to a customer<br />

and a product.<br />

An entity object is an object returned from an EDM and is based on an entity. Entity objects have<br />

properties, but the only default methods they contain are to enable change tracking for that object. You


Building Entity <strong>Data</strong> Models 2-5<br />

can extend entity objects to contain behaviors required in your application, or you can create business<br />

objects from the entity objects and store your custom logic in the business objects.<br />

Question: Which model is directly linked to the storage structure in a database?<br />

Additional Reading<br />

For more information about data models, see The ADO.NET Entity Framework: Modeling at the Right<br />

Level of Abstraction, Modeling <strong>Data</strong> at the Conceptual Level of Abstraction: The Entity <strong>Data</strong> Model, and<br />

Bringing <strong>Data</strong> into an EDM Model: Mapping sections of The ADO.NET Entity Framework Overview page at<br />

http://go.microsoft.com/fwlink/?LinkID=194015.


2-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Model Design Strategies<br />

Key Points<br />

There are three ways to approach the design of the data in your application. You can create a database<br />

and then use the Entity Framework tools to generate a model, known as database-first design, you can<br />

design your model and then use the tools to reverse engineer your database, known as model-first<br />

design, or you can write your code <strong>with</strong>out using a model and let one be generated at runtime, known as<br />

code-only design. The following list describes the three different types of design:<br />

• <strong>Data</strong>base-first design. In database-first design, you design and create your database before you<br />

design the entities in your application. This approach is often favored by database administrators, but<br />

it can limit the flexibility of the application in the long term.<br />

• Model-first design. In model-first design, you design the entities for the application and then create<br />

the database structure around these entities. This approach is often favored by developers, because<br />

they can design the application around the business functionality that they require and then build a<br />

database structure to support those needs.<br />

• Code-only design. In code-only design, the model is generated when it is required at run time.<br />

Because the model is automatically generated, it does not support some of the advanced EDM<br />

features that you can manually implement.<br />

However, you will often find that you must develop an application against an existing database, in which<br />

case you have no choice but to use a database-first design.<br />

Design Strategies in the Entity Framework 4<br />

The Entity Framework version 4 supports the database-first and model-first design strategies out of the<br />

box. When you first add a model to a project, you can choose whether to create an empty model and<br />

design the model yourself or generate the model from an existing database. Whichever strategy you<br />

choose, you can create or update the database <strong>with</strong> the structure from your model at any point in the<br />

design process.


Building Entity <strong>Data</strong> Models 2-7<br />

It is recommended that before you start to design a model in any of the available tools, you first plan and<br />

sketch the model.<br />

Question: In what scenario are you likely to use a database-first design?


2-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Introduction to the Entity Designer<br />

Key Points<br />

Microsoft <strong>Visual</strong> <strong>Studio</strong>® 2010 provides the ADO.NET Entity <strong>Data</strong> Model Designer (Entity Designer), which<br />

enables you to graphically create and relate entities in a model. It also provides you <strong>with</strong> the Entity <strong>Data</strong><br />

Model Wizard for generating models, the Update Model Wizard for updating an existing model from a<br />

database, and the Create <strong>Data</strong>base Wizard for generating scripts from a model.<br />

The Entity Designer includes the designer pane, which you can use to create entities, relationships, and<br />

hierarchies. It also includes the Mapping Details pane, which you can use to view and modify the<br />

mappings to the logical model, and the Model Browser pane, which you can use to manage the<br />

properties, mappings, and entities in your model.<br />

Example<br />

On the slide associated <strong>with</strong> this topic, you will see a screen shot of a simple model in the Entity Designer.<br />

In this model, there are two entities—Contact and SalesOrderHeader—that are related by a one-tomany<br />

relationship as shown by the 1:* line linking the two entities. This relationship is also shown in the<br />

Navigation Properties list at the bottom of each entity.<br />

The other properties listed in the entity are the attributes of the entity and will hold the data for an entity<br />

object. Each property has properties of its own that describe it. For example, the ContactID property of<br />

the Contact entity has a Type property of Int32, a Nullable property of False, and a Name property of<br />

Contact ID. The properties shown on the slide are all scalar properties. You will learn about other types of<br />

property later in this module.<br />

EDM Generator<br />

In addition to the Entity Designer, you can use the EdmGen.exe command-line tool to create and modify<br />

EDMs. The tool is installed in the .NET Framework folder, but you can easily access it from the <strong>Visual</strong><br />

<strong>Studio</strong> command prompt. The tool can perform a variety of functions, including validating model XML<br />

files and generating models.


The following code example shows how to use EdmGen.exe to generate a model from the<br />

AdventureWorks database.<br />

Building Entity <strong>Data</strong> Models 2-9<br />

Edmgen.exe /mode:fullgeneration<br />

/c:"<strong>Data</strong> Source=%datasourceserver%; Initial Catalog=AdventureWorks; Integrated<br />

Security=SSPI"<br />

/project:AW /entitycontainer:AWEntities /namespace:AWModel<br />

/language:CSharp<br />

Question: Name two tools that you can use to generate an entity model.<br />

Additional Reading<br />

For more information about the Entity Designer, see the ADO.NET Entity <strong>Data</strong> Model Designer page at<br />

http://go.microsoft.com/fwlink/?LinkID=194016.


2-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Demonstration: Creating a Model from a <strong>Data</strong>base<br />

Key Points<br />

• Create a new class library project.<br />

• Create an EDM in the project based on two tables in the AdventureWorks database.<br />

• Review the information in the Mapping Details window, the Model Browser window, and the<br />

Properties window.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-02 virtual machine as Student <strong>with</strong> the password Pa$$w0rd<br />

2. Open <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010.<br />

3. In <strong>Visual</strong> <strong>Studio</strong> 2010, create a new <strong>Visual</strong> C# class library project named CreatingAModelDemo in the<br />

E:\Demofiles\Mod02\Demo1\Starter\CreatingAModelDemo folder.<br />

4. Add an ADO.NET Entity <strong>Data</strong> Model named AWModel to the solution.<br />

5. Create a model based on the local Microsoft SQL Server® Express AdventureWorks database by using<br />

the Contact and SalesOrderHeader tables.<br />

6. Review the information in the Mapping Details window, the Model Browser window, and the<br />

Properties window.<br />

Question: What information is stored in a NavigationProperty property?


Mapping a Model to a <strong>Data</strong>base<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-11<br />

By default, when you create a model from a database, the Entity Designer automatically generates the<br />

mappings from the conceptual model to the logical model. These mappings are displayed in the<br />

Mapping Details pane, and you can use this pane to view, modify, and delete mappings to the<br />

underlying tables. If you update a model from the database, the mappings are again automatically<br />

created. If you add a custom entity to the model, you must manually map the entity properties to the<br />

tables.<br />

The Mapping Details pane also enables you to add conditions to a mapping to limit the data that is<br />

returned. Continuing <strong>with</strong> the Sales example, if you want to view only the orders from one sales territory,<br />

you can add a condition to that mapping to select only the appropriate orders from the database.<br />

Question: When might you need to manually create the mappings between the conceptual and logical<br />

models?


2-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The XML Behind the Model<br />

Key Points<br />

When you view a model in the Entity Designer, you are viewing an .edmx file. However, that file is defined<br />

in an XML file that defines the conceptual model, the logical (or storage) model, and the mapping<br />

between them. In the Entity Designer, you simply see a graphical representation of this XML.<br />

View the XML in <strong>Visual</strong> <strong>Studio</strong><br />

1. Close the Entity Designer window displaying the model.<br />

2. In Solution Explorer, right-click the model, and then click Open With.<br />

3. In the Open With dialog box, click XML Editor, and then click OK.<br />

The XML file is divided into two sections: the element, which defines the model, and the<br />

element, which defines the Entity Designer content. The second of these two elements should<br />

not be edited manually; however, you can edit the contents of the element to<br />

implement functionality that is not available in the Entity Designer.<br />

Structure of the Element<br />

The runtime element is subdivided into three further sections:<br />

• SSDL content. The store schema definition language (SSDL) section of the XML defines the storage<br />

model of the entity model. It describes the logical structure of each of the entities in the model and<br />

the associations between entities.<br />

• CSDL content. The conceptual schema definition language (CSDL) section of the XML defines the<br />

conceptual model of the entity model. It describes the conceptual structure of the entities and<br />

associations in the model.<br />

• C-S mapping content. The C-S mapping section of the XML defines the mappings between the SSDL<br />

and CSDL content. For each entity in the model, the properties are mapped to columns in tables in<br />

the logical structure.


Building Entity <strong>Data</strong> Models 2-13<br />

Generally, you will not need to work <strong>with</strong> the XML view of the model; however, there are a few scenarios<br />

where you will, including:<br />

• Adding a defining query to emulate a database view in the model itself.<br />

• Editing an entity key that one of the model wizards has incorrectly inferred.<br />

• Working <strong>with</strong> store-generated column values.<br />

• Mapping a globally unique identifier (GUID) property to a binary column.<br />

• Defining customer functions in the storage or conceptual model to emulate stored procedures in the<br />

model itself.<br />

Warning: Changes you make to the SSDL section of a model may be overwritten if you use the<br />

Update Model Wizard to update your model from the database.<br />

Question: What are the two main sections of the XML file behind a model?<br />

Additional Reading<br />

For more information about the XML behind an EDM, see the .edmx File Overview (Entity Framework)<br />

page at http://go.microsoft.com/fwlink/?LinkID=194017.


2-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Demonstration: Reviewing the Model's XML<br />

Key Points<br />

• Open an EDM in the XML Editor.<br />

• Review the XML definition of the model.<br />

Demonstration Steps<br />

1. Continue working <strong>with</strong> the solution files from the previous demonstration.<br />

2. If AWModel.edmx is open in the Entity Designer, save the file and then close the Entity Designer<br />

window.<br />

3. Open AWModel.edmx in the XML Editor.<br />

4. Review the XML definition of the model.<br />

Question: Which section of the XML should you not manually edit?


Model Deployment Considerations<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-15<br />

By default, the model and mapping files are embedded as a resource in the application assembly.<br />

Although this may be appropriate during the development and testing stages of the application, when<br />

you release the application, you should consider whether to change this to store the model and mapping<br />

loosely coupled outside the assembly.<br />

Advantages of Storing the Model and Mapping Files Externally<br />

During the lifetime of an application, it is possible that the database schema may be modified. If your<br />

model and mapping files are part of the application assembly, such changes will require a recompile and<br />

redeployment of the application. This may also result in versioning issues for the application. If the model<br />

and mapping files are stored externally, you only need to update the model and mapping files and<br />

redeploy those.<br />

Configure the model to store the files externally<br />

1. In <strong>Visual</strong> <strong>Studio</strong>, open the model in the Entity Designer.<br />

2. In the Properties pane, in the Metadata Artifact Processing row, select Copy to Output Directory.<br />

After you have configured the Metadata Artifact Processing property, you will find that when you build<br />

the application, three extra files are generated in the output folder: .csdl,<br />

.ssdl, and .msl. These files contain the definitions of the model. If your model<br />

now changes, you can update and redeploy just these files.<br />

If you want to move these files to an alternative location, you can update the paths in the Connection<br />

String property to point to the new location.<br />

Question: Why should you consider storing your XML externally to the application assembly?


2-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

Modifying an Entity <strong>Data</strong> Model<br />

Even when you create a model from a database, you may need to modify the model to meet your specific<br />

requirements. Also, if you want to implement a model-first design, you must add entities and associations<br />

to a model.<br />

This lesson explains how to modify an EDM by adding entities and associations to the model. It also<br />

explains how to generate a script from the model to update the database <strong>with</strong> changes to the model.<br />

Objectives<br />

After you have completed this lesson, you will be able to:<br />

• Add entities to a model.<br />

• Add associations between entities.<br />

• Generate a Transact-SQL script from a model to update a database.


Adding Entities to a Model<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-17<br />

In addition to defining entities by creating a model from an existing database, you can also manually<br />

define them in the Entity Designer. This is useful whether you want to use a model-first design or<br />

customize an existing model.<br />

When you add entities to a model, you must specify their name, key property name and type, and an<br />

entity set name. Optionally, you can derive your entity from an existing entity, in which case you only<br />

specify the name of the new entity.<br />

Add an entity to a model<br />

1. In <strong>Visual</strong> <strong>Studio</strong>, open the model in the Entity Designer.<br />

2. In the Entity Designer pane, right-click in the white space, point to Add, and then click Entity.<br />

3. In the Add Entity dialog box, enter the details for your entity, and then click OK.<br />

After adding an entity, you can define the properties for it.<br />

Add a scalar property to an entity<br />

1. In <strong>Visual</strong> <strong>Studio</strong>, open the model in the Entity Designer.<br />

2. Right-click the entity, point to Add, and then click Scalar Property.<br />

3. Type the name of the property, and then press ENTER.<br />

4. In the Properties pane, configure the properties of the scalar property.<br />

In addition to the scalar and navigation properties that you saw in the earlier demonstration, you can also<br />

define complex properties, associations, inheritance, and function imports. All of these will be discussed<br />

later in this module.<br />

Question: How do you create a derived entity?


2-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Adding Associations Between Entities<br />

Key Points<br />

You add associations to a model to define the relationships between entities. You can define one-to-one,<br />

one-to-many, many-to-one, and many-to-many relationships by using the Entity Designer. Each entity in<br />

the relationship is known as an end. One association can have only two entities in it; however, each entity<br />

can have multiple associations.<br />

Add an association between entities<br />

1. In <strong>Visual</strong> <strong>Studio</strong>, open the model in the Entity Designer.<br />

2. Right-click one of the ends of the required association, point to Add, and then click Association.<br />

3. In the Add Association dialog box, configure the appropriate entities, multiplicities, and navigation<br />

properties, and then click OK.<br />

Navigation Properties<br />

Navigation properties provide quick access to the ends of an association. They are created when you add<br />

an association and deleted when you delete an association. You can view the association information in<br />

the Properties pane for a navigation property. You can also use the Properties pane to rename or<br />

change the multiplicity of that end of the association.<br />

Question: What is the purpose of navigation properties?


Demonstration: Adding Entities and Associations<br />

Key Points<br />

• Add an entity to an existing EDM.<br />

• Add properties to an entity.<br />

• Add an association between two entities.<br />

Demonstration Steps<br />

Building Entity <strong>Data</strong> Models 2-19<br />

1. Continue working <strong>with</strong> the solution files from the previous demonstration.<br />

2. Open AWModel.edmx in the Entity Designer.<br />

3. Add an entity named ProductInfo to the model, <strong>with</strong> an entity set name of ProductInfos and a key<br />

property name of ProductID.<br />

4. Add a scalar property named ProductName to the entity.<br />

5. Add a scalar property named UnitPrice to the entity.<br />

6. Add a scalar property named SupplierID to the entity.<br />

7. Add an entity named Suppliers to the model, <strong>with</strong> an entity set name of Suppliers and a key<br />

property name of SupplierID.<br />

8. Add a many-to-many association between the ProductInfo and Suppliers entities.<br />

9. Review the navigation properties that have been created.<br />

Question: How can you change the data type of a scalar property?


2-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Generating Transact-SQL Scripts from a Model<br />

Key Points<br />

When you add entities to an existing model, you must later map them to database objects. It is likely that<br />

you will create entities that do not have matching database tables; therefore, the Entity Framework<br />

provides the Generate <strong>Data</strong>base Wizard, which you can use to generate a database from a model.<br />

The wizard inspects your model, the entities in it, the properties of those entities, and the associations<br />

between them, and then generates a Transact-SQL script to create database objects that will map to your<br />

model.<br />

The model has a <strong>Data</strong>base Schema Name property that defines the schema name under which all of the<br />

objects will be created. The default setting for this property is dbo, so it is important to configure this to a<br />

more meaningful schema name.<br />

When you use a model-first design, you can use the Generate <strong>Data</strong>base Wizard to generate a script for<br />

your entire database. If you are just modifying an existing model that already has mappings to a database,<br />

you can customize the script to only create the new items in your model.<br />

The wizard only generates the script and does not execute it. This is to ensure that if you only want to add<br />

items to an existing database, you do not overwrite the existing tables and thus the data that they<br />

contain. You can use Microsoft SQL Server® Management <strong>Studio</strong> or the <strong>Visual</strong> <strong>Studio</strong> Transact-SQL Editor<br />

to execute the script.<br />

Note: The Generate <strong>Data</strong>base Wizard only supports SQL Server 2005 and SQL Server 2008 databases.<br />

Question: You have used the Generate <strong>Data</strong>base Wizard, but you cannot see any corresponding changes<br />

in your database. What have you forgotten to do?


Demonstration: Generating Transact-SQL Scripts<br />

Key Points<br />

• Modify the <strong>Data</strong>base Schema Name property of a model.<br />

• Run the Generate <strong>Data</strong>base Wizard.<br />

• Modify a generated script.<br />

• Execute a generated script.<br />

Demonstration Steps<br />

Building Entity <strong>Data</strong> Models 2-21<br />

1. Continue working <strong>with</strong> the solution files from the previous demonstration.<br />

2. Oepn AWModel.edmx in the Entity Designer.<br />

3. Change the <strong>Data</strong>base Schema Name property of the model to Products.<br />

4. Run the Generate <strong>Data</strong>base Wizard.<br />

5. In the Transact-SQL Editor window, review the Transact-SQL script that has been created. Make<br />

particular note of the Products.SuppliersProductInfo table.<br />

6. In the Transact-SQL Editor window, delete the sections of the script that relate to existing objects in<br />

the database.<br />

7. Execute the Transact-SQL script against the local SQL Server Express database.<br />

8. Open SQL Server Management <strong>Studio</strong>, and then review the new tables in the AdventureWorks<br />

database.<br />

9. Close SQL Server Management <strong>Studio</strong>.<br />

10. Save and close the solution and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: How does the Generate <strong>Data</strong>base Wizard handle many-to-many associations in the entity<br />

model?


2-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 3<br />

Customizing an Entity <strong>Data</strong> Model<br />

Entity models created from a database do not always match your application design. They often use data<br />

from more than one table, inherit from other entities, or require access to stored procedures. They also<br />

combine properties into complex types.<br />

This lesson explains how to customize an EDM to support these requirements. It also explains how to use<br />

entity splitting to map an entity to multiple tables, how to use inheritance to customize your model, how<br />

to access stored procedures from your model, and how to create complex types in a model.<br />

Objectives<br />

After you have completed this lesson, you will be able to:<br />

• Use entity splitting.<br />

• Implement an inheritance hierarchy.<br />

• Add a stored procedure to a model.<br />

• Use complex types.


Using Entity Splitting<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-23<br />

Developers often find themselves using the data from multiple tables as one item in an application. For<br />

example, a database might store employee data in two tables: departmental and location details in one<br />

table and personal information in another. For security purposes, it is important to keep these as separate<br />

tables in the database; however, the joins involved in merging them in applications is tedious to code.<br />

Entity splitting enables you to map one entity to more than one table, which removes the need to include<br />

joins of this type in your code.<br />

For entity splitting to work, both practically and logically, the two (or more) tables must have the same<br />

primary key. This ensures that no data can be wrongly read, updated, or deleted when you work <strong>with</strong> the<br />

single entity.<br />

Implement entity splitting<br />

1. Create the two entities (Entity1 and Entity2) in your model, mapped to the two database tables.<br />

2. Cut the unique properties from Entity1 and paste them into Entity2.<br />

3. In the Mapping Details pane, map the new properties in Entity2 to the tables in the database.<br />

4. Delete Entity1 from the model.<br />

Question: Describe an example where you could use entity splitting in one of your applications.<br />

Additional Reading<br />

For more information about entity splitting, see the How to: Define a Model <strong>with</strong> a Single Entity Mapped<br />

to Two Tables page at http://go.microsoft.com/fwlink/?LinkID=194018.


2-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Demonstration: Using Entity Splitting<br />

Key Points<br />

• Add an ADO.NET EDM to an existing solution.<br />

• Create a model based on existing tables in a database.<br />

• Copy and paste properties from one entity to another entity.<br />

• Map properties of an entity to a database table.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-02 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. Open <strong>Visual</strong> <strong>Studio</strong>.<br />

3. In <strong>Visual</strong> <strong>Studio</strong>, open the starter project in the E:\Demofiles\Mod02\Demo5\Starter folder.<br />

4. Add an ADO.NET EDM named EntitySplitting to the solution.<br />

5. Create a model based on the Customer and Individual tables in the local SQL Server Express<br />

AdventureWorks database.<br />

6. Review the model, noting that both of the tables have the same primary key (CustomerID), but that<br />

the other properties are all different.<br />

7. Copy and paste the ContactID and Demographics properties from the Individual entity to the<br />

Customer entity.<br />

8. Map the new properties of the Customer entity to the Individual table .<br />

9. Delete the Individual entity.<br />

10. Save and close the solution and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: What is the defining factor for whether two tables can be used for entity splitting?


Implementing an Inheritance Hierarchy<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-25<br />

The Entity Framework and the Entity Designer support entity inheritance, which enables you to derive an<br />

entity type from another entity type in the model. They support both table-per-type and table-perhierarchy<br />

inheritance.<br />

Table-per-Type<br />

Table-per-type inheritance uses separate tables in the database for each type. For example, a Contact<br />

table may contain personal information such as name and title, and a Customer table may inherit from<br />

the Contact table and add information such as Billing Address, Shipping Address, and Credit Card<br />

Details.<br />

Table-per-Hierarchy<br />

Table-per-hierarchy inheritance uses conditional mapping to define rows into the different entity types.<br />

In the example shown on the slide associated <strong>with</strong> this topic, the CurrentProduct entity is derived from<br />

the Product entity. In the Mapping Details pane, a condition restricts the data in the CurrentProduct<br />

entity to only products that do not have an entry in the DiscontinuedDate property; that is, they have<br />

not been discontinued. The next step in the application design may be to derive another entity from the<br />

Product entity for the discontinued products.<br />

Implement an inheritance hierarchy<br />

1. Add the base type to your entity model.<br />

2. Create new entities, specifying that their Base type property should be the base entity.<br />

3. Move properties specific to each subtype from the base type to the subtype.<br />

4. Add conditions to the subtypes' specific properties.<br />

5. Set the Abstract property of the base class to True.<br />

Question: Describe an example where you could use entity inheritance in one of your applications.


2-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Additional Reading<br />

For more information about table-per-type inheritance, see the How to: Define a Model <strong>with</strong> Table-per-<br />

Type Inheritance (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194019.


Adding a Stored Procedure to a Model<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-27<br />

For security, encapsulation, and performance reasons, stored procedures are a recommended way of<br />

interacting <strong>with</strong> data in a database. Therefore, it is logical that the Entity Framework should expose them<br />

to an entity model to enable you to use the functionality and advantages of stored procedures. Function<br />

imports enable you to invoke stored procedures directly from your EDM.<br />

When you have the stored procedure included in the model, you can add a function import to the model<br />

to enable access to the stored procedure. To create the function import, you must specify the stored<br />

procedure to call and the return type of the stored procedure. Stored procedures can return collections of<br />

scalar types, complex types, entity types, or have no return value. When returning entity types, the column<br />

structure of the returned data must exactly match the column structure of the entity type.<br />

The function import is not shown in the Entity Designer, but <strong>with</strong> the Entity Designer open, you can view<br />

the function and the stored procedure details in the Model Browser pane.<br />

Add a stored procedure to a model<br />

1. Use the Update Wizard to add the stored procedure to the model.<br />

2. Add a function import to the model, specifying a name for the import, the stored procedure name it<br />

should call, and the return type of the procedure.<br />

Question: Why would you want to access a stored procedure from an EDM?


2-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Demonstration: Adding a Stored Procedure<br />

Key Points<br />

• Execute a Transact-SQL script to create a stored procedure in the database.<br />

• Add a stored procedure to a model.<br />

• Add a function import to a model.<br />

• Review the runtime content of a function import in the XML Editor.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-02 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. Open <strong>Visual</strong> <strong>Studio</strong>.<br />

3. In <strong>Visual</strong> <strong>Studio</strong>, open the starter project in the E:\Demofiles\Mod02\Demo6\Starter\UsingSPDemo<br />

folder.<br />

4. Execute the CreateSP.sql Transact-SQL script.<br />

5. Open AWModel in the Entity Designer.<br />

6. Add the uspCountOrders stored procedure to the model.<br />

7. Add a function import to the model. Name the function import CountOrders and map it to the<br />

uspCountOrders stored procedure.<br />

8. View the function import in the Model Browser window.<br />

9. Save and then close the model.<br />

10. Open the model in the XML Editor.<br />

11. Review the runtime content, and then close the XML Editor.<br />

12. Save and close the solution and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: How can you query what a stored procedure returns when importing it as a function?


Using Complex Types<br />

Key Points<br />

Building Entity <strong>Data</strong> Models 2-29<br />

You may find that in one entity model, you have a set of related properties that are used in more than<br />

one entity. For example, in a model <strong>with</strong> Customers, Employees, and Directors entities, each may have a<br />

HouseNumber, Street, Town, City, and ZipCode property. This scenario is easier to manage by making a<br />

complex type that contains these properties and then referencing this type in each of the entities.<br />

Create a complex type<br />

1. Select the related properties in one of the entities.<br />

2. Right-click the selection, and then click Refactor into New Complex Type.<br />

3. Type a name for the type, and then press ENTER.<br />

4. In the entity, right-click ComplexProperty, and then click Rename.<br />

5. Type a meaningful name, and then press ENTER.<br />

The complex type is added to the model, and the related properties in the entity you were working on<br />

have been replaced <strong>with</strong> a ComplexProperty property of the complex type.<br />

Use a complex type<br />

1. In another entity <strong>with</strong> the same related properties, delete the related properties.<br />

2. Right-click the entity header, point to Add, and then click Complex Property.<br />

3. Type a name for the property, and then press ENTER.<br />

4. In the Properties pane, set the Type property of the complex type to the complex type you want to<br />

use.<br />

Question: Describe an example where you could use complex types in one of your applications.


2-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab: Using Entity <strong>Data</strong> Models<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Generate an EDM.<br />

• Add entities and associations to an EDM.<br />

• Use the Generate <strong>Data</strong>base Wizard.<br />

• Map entities to multiple tables.<br />

• Implement an inheritance hierarchy.<br />

• Use stored procedures in an EDM.<br />

• Create a complex type.<br />

Introduction<br />

In this lab, you will generate an EDM, add entities and associations to it, generate new tables in the<br />

database for the new entities, and implement an inheritance hierarchy. You will also implement entity<br />

splitting, access stored procedures, and create a complex type.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-02 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


Lab Scenario<br />

Building Entity <strong>Data</strong> Models 2-31<br />

You have been asked to modify the corporate data model to implement a new customer rewards<br />

program. Customers will be awarded points when they purchase items from Adventure Works, and they<br />

can choose to exchange them for air miles, supermarket vouchers, or discounts on future Adventure<br />

Works purchases. You must keep track of the rewards offered and the reward claims made by customers.<br />

Two or more customers may combine their points to claim a better reward. The current points held by a<br />

customer have already been added to the Contact table.<br />

You have been asked to update the database to store current and inactive customers in separate tables,<br />

but you must ensure that this does not impact on the model or the application. You must also add a<br />

stored procedure to the model to track the number of orders a customer has placed and refactor some of<br />

the properties in entities in the model.


2-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 1: Generating an EDM from AdventureWorks<br />

Scenario<br />

In this exercise, you will create a new EDM from the AdventureWorks database, creating one entity per<br />

table. The tables you will use are Contact, SalesTerritory, SalesOrderHeaders, and StoreContact. You<br />

will browse the logical and conceptual models and the mappings in the EDM Designer. Finally, you will<br />

review the XML for the EDM.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the AdventureWorks database for the lab.<br />

2. Open the starter project.<br />

3. Create the AdventureWorks Entity <strong>Data</strong> Model.<br />

4. Review the AdventureWorks model.<br />

5. Modify the SalesTerritory entity by using XML.<br />

Task 1: Prepare the AdventureWorks database for the lab<br />

1. Log on to the 10265A-GEN-DEV-02 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run AWReset.bat.<br />

Task 2: Open the starter project<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, DAL.sln in the E:\Labfiles\Lab02\VB\Ex1\Starter\DAL or<br />

E:\Labfiles\Lab02\CS\Ex1\Starter\DAL folder.<br />

Task 3: Create the AdventureWorks Entity <strong>Data</strong> Model<br />

• Add a new ADO.NET EDM to the DAL project. Generate the EDM from the AdventureWorks SQL<br />

Server database and create entities for the Contact, SalesOrderHeader, SalesTerritory, and<br />

StoreContact tables.<br />

Task 4: Review the AdventureWorks model<br />

1. Review the four entities shown in the Entity Designer pane and the associations between them.<br />

2. Open the Mapping Details pane and review the mappings for each entity in the model.<br />

3. Close the Entity Designer pane.<br />

Task 5: Modify the SalesTerritory entity by using XML<br />

1. Open the AdventureWorksEDM EDM in the XML Editor.<br />

2. Locate the CSDL section of the model.<br />

3. Locate the SalesTerritory entity in the CSDL section of the model.<br />

4. Find the Name property, and then change its Name attribute to TerritoryName.<br />

5. Save the model and close the XML Editor window.<br />

6. Open AdventureWorksEDM.edmx in the Entity Designer pane, verify that the change has been<br />

made, and then change the property back to Name.<br />

7. Save and close the solution.


Exercise 2: Adding Entities and Associations<br />

Scenario<br />

Building Entity <strong>Data</strong> Models 2-33<br />

In this exercise, you will modify the EDM by adding new entities to it to support the new customer<br />

rewards program.<br />

You will create a new entity named Reward. This entity will provide access to information about the<br />

current rewards that Adventure Works offers. The Reward entity will contain the following properties:<br />

RewardID, RewardType (for example, AM for air miles, SM for supermarket vouchers, and AW for<br />

Adventure Works), RewardName (for example, 100 air miles or $10 off at Adventure Works),<br />

NumberOfAirMilesRequired, PointsPerAirMile, Destination, MoneyBackPerPoint,<br />

NumberOfPointsRequired, and Product.<br />

You will then create a second new entity named RewardsClaimed. This entity will provide access to the<br />

customer claims for rewards in the program. It will have the following properties: ClaimID and<br />

PointsUsed. The PointsUsed property must be tracked because Adventure Works permits families to<br />

combine points; therefore, the points used by a customer are not necessarily the price of the reward.<br />

Next, you will add a 1:n association between the Reward and RewardsClaimed entities, changing the<br />

name of the RewardRewardID property to RewardID. You will also add a 1:n association between the<br />

Contact and RewardsClaimed entities, changing the name of the ContactContactID property to<br />

ContactID.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Add the Reward entity.<br />

3. Add the RewardsClaimed entity.<br />

4. Add the Reward entity to the RewardsClaimed association.<br />

5. Add the Contact entity to the RewardsClaimed association.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex2\Starter\DAL or<br />

E:\Labfiles\Lab02\CS\Ex2\Starter\DAL folder.<br />

Task 2: Add the Reward entity<br />

1. Open the AdventureWorksEDM model in the Entity Designer pane.<br />

2. Add a new entity named Reward <strong>with</strong> a Key Property name of RewardID and an EntitySet name of<br />

Reward to the model.<br />

3. Add the scalar properties described in the following table to the entity.<br />

Property name Type Scale Nullable Default Value<br />

RewardType String not available True AW<br />

RewardName String not available False (None)<br />

NumberOfAirMilesRequired Int32 not available True (None)<br />

PointsPerAirMile Int32 not available True (None)<br />

Destination String not available True (None)<br />

MoneyBackPerPoint Decimal 2 True (None)


2-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Property name Type Scale Nullable Default Value<br />

NumberOfPointsRequired Int32 not available True (None)<br />

Product String not available True (None)<br />

Task 3: Add the RewardsClaimed entity<br />

1. Add a new entity named RewardsClaimed <strong>with</strong> a Key Property name of ClaimID and an Entity Set<br />

name of RewardsClaimed to the model.<br />

2. Add the scalar property described in the following table to the entity.<br />

Property name Type<br />

PointsUsed Int32<br />

Task 4: Add the Reward entity to the RewardsClaimed association<br />

1. Add a one-to-many association between the Reward and RewardsClaimed entities.<br />

2. In the RewardsClaimed entity, rename the new RewardRewardID property to RewardID.<br />

3. In the Reward entity, rename the RewardsClaimeds navigation property to RewardsClaimed.<br />

Task 5: Add the Contact entity to the RewardsClaimed association<br />

1. Add a one-to-many association between the Contact and RewardsClaimed entities.<br />

2. Rename the new ContactContactID property in the RewardsClaimed entity to ContactID.<br />

3. Rename the Navigation Property in the Contact entity to RewardsClaimed.<br />

4. Save and close the solution.


Exercise 3: Using the Generate <strong>Data</strong>base Wizard<br />

Scenario<br />

Building Entity <strong>Data</strong> Models 2-35<br />

In this exercise, you will use the Generate <strong>Data</strong>base Wizard to generate a Transact-SQL script for the<br />

model. You will then remove the Transact-SQL code that creates the Customers, SalesTerritory,<br />

SalesOrderHeaders, and StoreContact tables and their keys from the script to ensure that it does not<br />

overwrite the existing data in the database. You will then execute the Transact-SQL script to create the<br />

new tables in the database and review the mappings generated in your model.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Modify the <strong>Data</strong>base Schema Name property.<br />

3. Generate Transact-SQL script of the model.<br />

4. Modify the generated script.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex3\Starter\DAL or<br />

E:\Labfiles\Lab02\CS\Ex3\Starter\DAL folder.<br />

Task 2: Modify the <strong>Data</strong>base Schema Name property<br />

1. Open the AdventureWorksEDM model in the Entity Designer.<br />

Note: The Error List shows that the Reward and RewardsClaimed entities are not mapped. This is<br />

because you created the entities in the last exercise and have not yet mapped them to any database<br />

objects. You will resolve this error later in this exercise.<br />

2. Change the <strong>Data</strong>base Schema Name property to Sales.<br />

Task 3: Generate Transact-SQL script of the model<br />

• Run the Generate <strong>Data</strong>base Wizard to script the AdventureWorksEDM model.<br />

Task 4: Modify the generated script<br />

1. Remove the Dropping existing FOREIGN KEY constraints section from the script.<br />

2. Remove the Dropping existing tables section from the script.<br />

3. Remove the code that creates the following existing tables in the database: Contacts,<br />

SalesOrderHeaders, SalesTerritories, and StoreContacts.<br />

4. Remove the code that creates the existing primary keys on the following tables in the database:<br />

Contacts, SalesOrderHeaders, SalesTerritories, and StoreContacts.<br />

5. Remove the code that creates the existing foreign keys on the following tables in the database:<br />

SalesOrderHeaders and StoreContacts.<br />

6. Modify the code that creates the foreign key on [ContactID] in the RewardsClaimed table to<br />

change the schema for the Contact table to Person and to singularize the table name to Contact.<br />

7. Validate and then execute the script against the 10265A-GEN-DEV<br />

\SQLExpress database engine.<br />

8. Build the solution.<br />

Note: The errors in the Error List have been cleared and the solution builds successfully because the<br />

Reward and RewardsClaimed entities are now mapped to the tables that you have just created.<br />

9. Save and close the solution.


2-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 4: Mapping Entities to Multiple Tables<br />

Scenario<br />

In this exercise, you will use the Update Wizard to add the InactiveStoreContact table to the EDM. You<br />

will then modify the StoreContact entity to contain information from both the StoreContact and<br />

InactiveStoreContact tables.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Create the InactiveStoreContact table.<br />

3. Update the model from the database.<br />

4. Map the StoreContact entity to the InactiveStoreContact table.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex4\Starter\DAL or<br />

E:\Labfiles\Lab02\CS\Ex4\Starter\DAL folder.<br />

Task 2: Create the InactiveStoreContact table<br />

1. Open the InactiveStoreContact.sql script from the E:\Labfiles\Lab02\VB\Ex4\Starter or<br />

E:\Labfiles\Lab02\CS\Ex4\Starter folder.<br />

2. Validate and then execute the script against the 10265A-GEN-DEV<br />

\SQLExpress database engine.<br />

Task 3: Update the model from the database<br />

1. Open the AdventureWorksEDM model in the Entity Designer.<br />

2. Update the model from the database to add the InactiveStoreContacts table to the model.<br />

Note: Due to an issue <strong>with</strong> the prerelease version of the Entity Designer, the mappings for the original<br />

table have been lost. To resolve this issue, it is necessary to delete the model from the project, re-create<br />

the model, and re-create any default values.<br />

3. Delete the model from the project.<br />

4. Add a new ADO.NET EDM named AdventureWorksEDM.edmx to the DAL project. Generate the<br />

data model from the AdventureWorks database, and create entities for the Contact,<br />

InactiveStoreContact, Reward, RewardsClaimed, SalesOrderHeader, SalesTerritory, and<br />

StoreContact tables.<br />

5. Change the <strong>Data</strong>base Schema Name property to Sales.<br />

6. Rename the Navigation Property in the Reward entity to RewardsClaimed.<br />

7. Rename the Navigation Property in the Contact entity to RewardsClaimed.<br />

Task 4: Map the StoreContact entity to the InactiveStoreContact table<br />

1. Open the Mapping Details pane, and then map the StoreContact entity to the<br />

InactiveStoreContact table.<br />

2. In the Entity Designer pane, delete the InactiveStoreContact entity.<br />

3. Build the solution.<br />

4. Save and close the solution.


Exercise 5: Implementing an Inheritance Hierarchy<br />

Scenario<br />

Building Entity <strong>Data</strong> Models 2-37<br />

In this exercise, you will create three new entities: AirMilesReward, SupermarketReward, and<br />

AdventureWorksReward. You will move the NumberOfAirMilesRequired, PointsPerAirMile, and<br />

Destination properties to the AirMilesReward entity, the MoneyBackPerPoint property to the<br />

SupermarketReward entity, and the NumberOfPointsRequired and Product properties to the<br />

AdventureWorksReward entity. You will add a condition to each of the new entities so that they only<br />

include data when the RewardType property is the appropriate type. For example, for the<br />

AirMilesReward, the condition will be When RewardType = AM. Finally, you will review the entity types<br />

generated by the EDM to implement this hierarchy.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Add the AirMilesReward entity.<br />

3. Add the SupermarketReward entity.<br />

4. Add the AdventureWorksReward entity.<br />

5. Map the new entities to the Reward table.<br />

6. Add conditions to the mappings.<br />

7. Assign properties to the new entities.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex5\Starter\ DAL or<br />

E:\Labfiles\Lab02\CS\Ex5\Starter\DAL folder.<br />

Task 2: Add the AirMilesReward entity<br />

1. Open AdventureWorksEDM in the Entity Designer pane.<br />

2. Add a new entity named AirMilesReward based on the Reward entity.<br />

Task 3: Add the SupermarketReward entity<br />

• Add a new entity named SupermarketReward based on the Reward entity.<br />

Task 4: Add the AdventureWorksReward entity<br />

• Add a new entity named AdventureWorksReward based on the Reward entity.<br />

Task 5: Map the new entities to the Reward table<br />

1. Open the Mapping Details pane, and then map the AirMilesReward entity to the Reward table.<br />

2. In the Mapping Details pane, map the SupermarketReward entity to the Reward table.<br />

3. In the Mapping Details pane, map the AdventureWorksReward entity to the Reward table.<br />

Task 6: Add conditions to the mappings<br />

1. Add a condition to the AirMilesReward mapping to specify that this entity should contain rewards<br />

only if the RewardType field is AM.<br />

2. Add a condition to the SupermarketReward mapping to specify that this entity should contain<br />

rewards only if the RewardType field is SM.<br />

3. Add a condition to the AdventureWorksReward mapping to specify that this entity should contain<br />

rewards only if the RewardType field is AW.<br />

4. Remove the RewardType property from the Reward entity.<br />

5. Make the Reward entity abstract.


2-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 7: Assign properties to the new entities<br />

1. Move the NumberOfAirMilesRequired property to the AirMilesReward entity.<br />

2. Move the PointsPerAirMile property to the AirMilesReward entity.<br />

3. Move the Destination property to the AirMilesReward entity.<br />

4. Move the MoneyBackPerPoint property to the SupermarketReward entity.<br />

5. Move the NumberOfPointsRequired property to the AdventureWorksReward entity.<br />

6. Move the Product property to the AdventureWorksReward entity.<br />

7. Map the AirMilesReward properties to the appropriate columns in the Reward table.<br />

8. Map the SupermarketReward properties to the appropriate columns in the Reward table.<br />

9. Map the AdventureWorksReward properties to the appropriate columns in the Reward table.<br />

10. Build the solution.<br />

11. Save and close the solution.


Exercise 6: Using Stored Procedures<br />

Scenario<br />

Building Entity <strong>Data</strong> Models 2-39<br />

In this exercise, you will add the uspCountOrders stored procedure to the EDM and add a new function<br />

import to map to the stored procedure. This stored procedure will return a count of the number of orders<br />

that a given contact has made.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Add the uspCountOrders stored procedure to the model.<br />

3. Add a function import to the model.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex6\Starter or<br />

E:\Labfiles\Lab02\CS\Ex6\Starter folder.<br />

Task 2: Add the uspCountOrders stored procedure to the model<br />

1. Open AdventureWorksEDM in the Entity Designer pane.<br />

2. Run the Update Wizard to add the uspCountOrders stored procedure to the model.<br />

Note: Four errors will appear in the Error List because you have updated the model from the database<br />

and the mappings for the inheritance hierarchy that you created in Exercise 5 have been lost. These errors<br />

will not impact this exercise, so you can ignore the errors and continue <strong>with</strong> the next task.<br />

Task 3: Add a function import to the model<br />

1. Add a function import named CountOrders to the model. Map it to the uspCountOrders stored<br />

procedure and configure it to return a collection of scalar Int32 data types.<br />

2. Build the solution.<br />

3. Save and close the solution.


2-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 7: Creating a Complex Type<br />

Scenario<br />

In this exercise, you will refactor the Name, CountryRegionCode, and Group properties in the<br />

SalesTerritory entity into a new complex type named LocationType. You will then create a complex<br />

property of this type in the SalesTerritory entity, named Location.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Create the complex type.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex7\Starter or<br />

E:\Labfiles\Lab02\CS\Ex7\Starter folder.<br />

Task 2: Create the complex type<br />

1. Open AdventureWorksEDM in the Entity Designer pane.<br />

2. In the SalesTerritory entity, create a complex type named LocationType from the Name,<br />

CountryRegionCode, and Group properties.<br />

3. Change the name of the new complex property in the SalesTerritory entity to Location.<br />

4. Build the solution.<br />

5. Save and close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


Lab Review<br />

Review Questions<br />

1. What information does the CSDL section of the model store?<br />

2. Name three types of association that the Entity Designer supports.<br />

3. What does the <strong>Data</strong>base Schema Name property of a model control?<br />

4. Why would you map one entity to more than one table?<br />

5. How do you configure an entity to inherit from another entity?<br />

6. Name three types of collection that a stored procedure can return.<br />

7. Why might you want to create a complex type?<br />

Building Entity <strong>Data</strong> Models 2-41


2-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. What key advantage does the use of a conceptual model provide?<br />

2. Why might you want to modify an EDM that you have created from a database?<br />

3. Which Entity Framework feature enables you to derive entity types from other entity types?<br />

Best Practices Related to Designing an EDM<br />

Supplement or modify the following best practices for your own work situations:<br />

• Use a conceptual model to disconnect your application from the database engine and database<br />

structure.<br />

• Before you start to design a model in any of the available tools, you must first plan and sketch the<br />

model.<br />

• Do not edit the element of the XML file.<br />

Best Practices Related to Deploying an EDM<br />

Supplement or modify the following best practices for your own work situations:<br />

• When you deploy an application that contains an EDM, store the model and mapping files externally<br />

to the assembly.<br />

Best Practices Related to Generating Transact-SQL Scripts<br />

Supplement or modify the following best practices for your own work situations:<br />

• Ensure that you fully review a script before you execute it against the database so that you do not<br />

accidently recreate existing objects.


Module 3<br />

Querying Entity <strong>Data</strong><br />

Contents:<br />

Lesson 1: Retrieving <strong>Data</strong> by Using LINQ to Entities 3-3<br />

Lesson 2: Retrieving <strong>Data</strong> by Using Entity SQL 3-13<br />

Lesson 3: Retrieving <strong>Data</strong> by Using the EntityClient Provider 3-18<br />

Lesson 4: Retrieving <strong>Data</strong> by Using Stored Procedures 3-27<br />

Lesson 5: Unit Testing Your <strong>Data</strong> <strong>Access</strong> Code 3-32<br />

Lab: Querying Entity <strong>Data</strong> 3-38<br />

Querying Entity <strong>Data</strong> 3-1


3-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

The Entity Framework enables you to retrieve data from a database. You can run queries against the<br />

conceptual model, and these queries can return data as objects. This means that you do not need a<br />

detailed knowledge of the structure of the underlying database, and you can work <strong>with</strong> ordinary<br />

<strong>Microsoft®</strong> .NET Framework objects that represent the entities in your model. The Entity Framework<br />

enables you to use several different technologies to define and run these queries. You can choose the<br />

style of query that is most suitable for the specific task that you need to perform.<br />

This module explains how to use Language-Integrated Query (LINQ) to Entities, Entity Structured Query<br />

Language (Entity SQL), the EntityClient provider for the Entity Framework, and stored procedures to<br />

retrieve data from your entity model, and describes when you should use each approach.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Retrieve data by using LINQ to Entities.<br />

• Retrieve data by using Entity SQL.<br />

• Retrieve data by using the EntityClient provider.<br />

• Retrieve data by using stored procedures in the entity model.<br />

• Create unit tests for your data access code.


Lesson 1<br />

Retrieving <strong>Data</strong> by Using LINQ to Entities<br />

Querying Entity <strong>Data</strong> 3-3<br />

LINQ to Entities enables you to create powerful, strongly typed queries to run against the conceptual data<br />

model that is defined in an Entity <strong>Data</strong> Model (EDM). LINQ expressions and the LINQ standard query<br />

operators enable you to write strongly typed, composable queries directly from the development<br />

environment in a syntax that is similar to Transact-SQL. The queries are expressed in the programming<br />

language itself and not as string literals embedded in the application code, so the compiler can catch<br />

most common errors. This minimizes the potential for run-time errors and results in a more robust<br />

application.<br />

You write your queries to run against the entities and relationships that are defined in the conceptual<br />

model. When an application uses the EDM, the Entity Framework automatically handles the mapping<br />

between the conceptual data model and the underlying data source. This enables you to change the<br />

back-end data source <strong>with</strong>out requiring changes to the client application, because the Entity Framework<br />

handles most database-specific features.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the role of the ObjectContext class.<br />

• Create a LINQ to Entities query by using the ObjectQuery class.<br />

• Perform a query against the EDM and understand how LINQ to Entities materializes objects.


3-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Connecting to the Conceptual Model<br />

Key Points<br />

The conceptual model defines the entities <strong>with</strong> which your application can interact, in the form of .NET<br />

Framework objects. The primary class for interacting <strong>with</strong> these objects is the ObjectContext class. The<br />

key features of the ObjectContext class are:<br />

• It handles the underlying connection to the database.<br />

• It holds the model’s descriptive metadata.<br />

• It tracks changes that are made to objects by using the ObjectStateManager class.<br />

Note: You can find the connection string that the ObjectContext object uses to connect to the<br />

database in the app.config file.<br />

<br />

<br />

<br />

Note: For security reasons, you should consider encrypting the connection information, especially if<br />

passwords are stored in the configuration file.<br />

Note: The Entity Framework will automatically take advantage of connection pooling if it is available.


Querying Entity <strong>Data</strong> 3-5<br />

When you use the tools in Microsoft <strong>Visual</strong> <strong>Studio</strong>® to create an EDM, <strong>Visual</strong> <strong>Studio</strong> automatically<br />

generates several classes from the model that enable your application to interact <strong>with</strong> the database. One<br />

of these classes derives from the ObjectContext class, and the EntityContainer name property in the<br />

EDM determines the name of this class.<br />

To perform queries that run against the entities that are defined in the conceptual model, you first need<br />

to obtain a reference to the ObjectContext object. In the following code example, the name of the<br />

derived class that <strong>Visual</strong> <strong>Studio</strong> generates is AdventureWorksEntities. This derived class enables you to<br />

write code to perform queries against entities such as Contacts and Rewards that are defined in the<br />

Adventure Works EDM.<br />

[<strong>Visual</strong> Basic]<br />

Using entities As New AdventureWorksEntities()<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

…<br />

}<br />

Note: You should always make sure that the ObjectContext object is disposed of when you have finished<br />

<strong>with</strong> it.<br />

Question: What will be the impact of adding a new entity type to the conceptual model on the custom<br />

ObjectContext class?<br />

Additional Reading<br />

For more information about connection strings in the Entity Framework, see the Connection Strings (Entity<br />

Framework) page at http://go.microsoft.com/fwlink/?LinkID=194020.<br />

For more information about connection strings in ADO.NET, see the Connection Strings (ADO.NET) page<br />

at http://go.microsoft.com/fwlink/?LinkID=194021.


3-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Defining the Query<br />

Key Points<br />

To define a LINQ to Entities query, you first need to construct an ObjectQuery instance from the<br />

ObjectContext object. The ObjectQuery generic class in the System.<strong>Data</strong>.Objects namespace<br />

represents a query that returns a collection of zero or more typed entities of the specified type. The<br />

following code example shows how to create an ObjectQuery object that you can use to query the<br />

StoreContacts entity set. After you execute the query, this entity set will contain StoreContacts entities<br />

that are retrieved from the database. You can use these StoreContacts entities to display data in a client<br />

application, or to manipulate the data as part of a business process in your application.<br />

[<strong>Visual</strong> Basic]<br />

Using entities As New AdventureWorksEntities()<br />

Dim contacts As ObjectQuery(Of StoreContact) =<br />

entities.StoreContacts<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

ObjectQuery contacts = entities.StoreContacts;<br />

…<br />

}


Querying Entity <strong>Data</strong> 3-7<br />

You can then construct a LINQ query that defines the data that you want to return based on this<br />

ObjectQuery object. You can use either query expression syntax or method-based syntax for your LINQ<br />

queries. Your queries can use the full range of LINQ syntax to construct queries that can:<br />

• Filter data.<br />

• Order data.<br />

• Perform grouping and aggregate data.<br />

• Use join operators.<br />

• Navigate relationships.<br />

• Page through data by using Skip and Join.<br />

The following code example shows how to define a query that returns all of the contacts stored in the<br />

Adventure Works database by using LINQ expression syntax.<br />

[<strong>Visual</strong> Basic]<br />

Using entities As New AdventureWorksEntities()<br />

Dim contacts As ObjectQuery(Of StoreContact) =<br />

entities.StoreContacts<br />

Dim query As IQueryable(Of StoreContact) = From c In contacts<br />

Select c<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

}<br />

ObjectQuery contacts = entities.StoreContacts;<br />

IQueryable query = from c in contacts<br />

select c;<br />

…<br />

You can also pass parameters to LINQ queries. The following code example returns all of the<br />

StoreContacts entities in the Adventure Works database <strong>with</strong> a contact type of 5.<br />

[<strong>Visual</strong> Basic]<br />

Using entities As New AdventureWorksEntities()<br />

Dim typeID As Integer = 5<br />

Dim contacts As ObjectQuery(Of StoreContact) =<br />

entities.StoreContacts<br />

Dim query As IQueryable(Of StoreContact) = From c In contacts<br />

Where c.ContactTypeID = 5<br />

Select c<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]


3-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

}<br />

int typeID = 5;<br />

ObjectQuery contacts = entities.StoreContacts;<br />

IQueryable query = from c in contacts<br />

where c.ContactTypeID = 5<br />

select c;<br />

…<br />

The following code example shows how you can also return data as primitive types instead of as entity<br />

objects. This is useful if you do not need access to all of the data that is held in the entity object, for<br />

example, to populate a list in the user interface.<br />

[<strong>Visual</strong> Basic]<br />

Using entities As New AdventureWorksEntities()<br />

Dim typeID As Integer = 5<br />

Dim contacts As ObjectQuery(Of StoreContact) =<br />

entities.StoreContacts<br />

Dim query As IQueryable(Of StoreContact) = From c In contacts<br />

Where c.ContactTypeID = 5<br />

Select c.ContactID<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

int typeID = 5;<br />

ObjectQuery contacts = entities.StoreContacts;<br />

IQueryable query = from c in contacts<br />

where c.ContactTypeID = 5<br />

select c.ContactID;<br />

…<br />

}<br />

Note: To follow best practice, you should adopt the habit of creating unit tests for your data access code.<br />

This topic is described later in this module.<br />

Question: How do you obtain a typed ObjectQuery instance?<br />

Additional Reading<br />

For more examples that show how to use LINQ to Entities, see the LINQ to Entities page at<br />

http://go.microsoft.com/fwlink/?LinkId=196094.<br />

For more information about the methods that LINQ to Entities supports, see the Supported and<br />

Unsupported LINQ Methods (LINQ to Entities) page at http://go.microsoft.com/fwlink/?LinkID=194023.


Querying Entity <strong>Data</strong> 3-9<br />

For more information about the behavior of expressions in LINQ to Entities, see the Expressions in LINQ to<br />

Entities Queries at http://go.microsoft.com/fwlink/?LinkID=194024.<br />

For an example that shows how to page through data, see the How to: Page Through Query Results<br />

(Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194025.


3-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Executing the Query<br />

Key Points<br />

After you have defined your query, you need to execute the query and use the results. Materialization is<br />

the term that is used to refer to the process of returning the query results to your application as entity<br />

objects.<br />

Materializing Objects<br />

The following code example shows how you can materialize entity objects by running the LINQ query and<br />

returning the results in a collection. You can also iterate through the returned entities individually by<br />

using a for…each construct. You can return the list of Contacts to a client application for display in the<br />

user interface, or to be consumed by a business process.<br />

[<strong>Visual</strong> Basic]<br />

Using entities = New AdventureWorksEntities()<br />

Dim contacts As ObjectQuery(Of Contact) = entities.Contacts<br />

contacts.MergeOption = MergeOption.NoTracking<br />

Dim query As IQueryable(Of Contact) = From c In contacts<br />

Select c<br />

Dim results As List(Of Contact) = query.ToList()<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

ObjectQuery contacts = entities.Contacts;


}<br />

contacts.MergeOption = MergeOption.NoTracking;<br />

IQueryable query = from c in contacts<br />

select c;<br />

List results = query.ToList();<br />

…<br />

Querying Entity <strong>Data</strong> 3-11<br />

Note: The ObjectContext object raises the ObjectMaterialized event whenever an entity object is<br />

created from data in the database as part of a query or load operation.<br />

The following table describes how the different MergeOption values affect the way in which data is<br />

loaded into the ObjectContext object.<br />

MergeOption Description<br />

AppendOnly Objects that already exist in the object context are not loaded from the data<br />

source. This is the default behavior for queries.<br />

OverwriteChanges Objects are always loaded from the data source. Any property changes that are<br />

made to objects in the object context are overwritten by the data source values.<br />

PreserveChanges Objects are always loaded from the data source. However, any property changes<br />

that are made to objects in the object context are preserved.<br />

NoTracking Objects are maintained in a Detached state and are not tracked in the<br />

ObjectStateManager object.<br />

You can access all of the properties of the entity objects that you defined in the conceptual model. This<br />

gives you access to the column data from the underlying database. The following code example shows<br />

how you can access the last name of a contact.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As Contact = results(0)<br />

Dim lastName As String = contact.LastName<br />

[<strong>Visual</strong> C#]<br />

Contact contact = results[0];<br />

string lastName = contact.LastName;<br />

You can also navigate to other entities by using the navigation properties that are defined in the entity<br />

model. The Entity Framework will only materialize entities that are retrieved in this way when they are first<br />

accessed programmatically; this behavior is known as lazy loading. The following code example shows<br />

how you can access all of the sales order header information for a contact if you need to use the sales<br />

order data in your client application.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As Contact = results(0)<br />

Dim orders As EntityCollection(Of SalesOrderHeader) =<br />

contact.SalesOrderHeaders<br />

[<strong>Visual</strong> C#]<br />

Contact contact = results[0];


3-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

EntityCollection orders = contact.SalesOrderHeaders;<br />

If you build your entity model by using the <strong>Visual</strong> <strong>Studio</strong> tools, the Entity Framework will automatically<br />

enable lazy loading. Otherwise, you have to configure the ObjectContext object to perform lazy loading,<br />

as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

entities.ContextOptions.LazyLoadingEnabled = True<br />

[<strong>Visual</strong> C#]<br />

entities.ContextOptions.LazyLoadingEnabled = true;<br />

Note: You may want to wrap the contents of the entity object in a custom business object before passing<br />

the data to another tier in your application.<br />

Entity Keys<br />

Every entity object has an EntityKey property that contains a unique identifying value for that entity.<br />

Typically, the EDM maps the EntityKey property to the primary key column in the underlying database.<br />

After the Entity Framework has materialized an object, the ObjectContext object caches the entity object<br />

for the lifetime of the ObjectContext object . Therefore, you can subsequently access the entity object<br />

<strong>with</strong>out the need to requery the database.<br />

To troubleshoot a problem, or to better understand how the Entity Framework is processing a query, you<br />

can ask the Entity Framework about the commands that it sends to the underlying database. The<br />

following code example shows how you can view the commands that the Entity Framework sends to the<br />

underlying database. You could include calls to ToTraceString as a part of any logging feature that you<br />

add to your data access layer code.<br />

[<strong>Visual</strong> Basic]<br />

Console.WriteLine(DirectCast(query,<br />

ObjectQuery(Of Contact)).ToTraceString())<br />

[<strong>Visual</strong> C#]<br />

Console.WriteLine(((ObjectQuery)query).ToTraceString());<br />

Question: Define the term materialization.<br />

Additional Reading<br />

For more information about how to enable lazy loading, see the How to: Use Lazy Loading to Load<br />

Related Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194026.<br />

For more information about how to explicitly load related objects, see the How to: Explicitly Load Related<br />

Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194027.<br />

For more information about deferred and immediate execution, see the Query Execution page at<br />

http://go.microsoft.com/fwlink/?LinkID=194028.


Lesson 2<br />

Retrieving <strong>Data</strong> by Using Entity SQL<br />

Querying Entity <strong>Data</strong> 3-13<br />

Entity SQL is an alternative to using LINQ to Entities. It enables you to use an SQL-like language to query<br />

your conceptual model. Conceptual models represent data as entities and relationships, and Entity SQL<br />

enables you to query those entities and relationships by using a syntax that is similar to traditional SQL,<br />

and to return entity objects.<br />

Entity SQL is useful when you need to construct queries dynamically at run time; when you want to store<br />

the query as a part of the model definition; or if you are already an expert in SQL-based query languages.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Understand the syntax of Entity SQL.<br />

• Describe how to use parameters and execute queries by using Entity SQL.


3-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Entity SQL Syntax<br />

Key Points<br />

Entity SQL is a database-independent dialect of SQL that works directly <strong>with</strong> your conceptual model. It<br />

directly supports EDM concepts such as inheritance and relationships, and returns data as entity objects to<br />

your application. The Entity Framework converts Entity SQL into database-specific queries to run against<br />

the underlying database.<br />

Entity SQL queries are defined by using strings in your application code.<br />

The following code example shows a simple query that returns all of the Contacts entities in the<br />

Adventure Works EDM.<br />

SELECT VALUE c from AdventureWorksEntities.Contacts as c<br />

When you execute this query, it will return a collection of Contact entity objects.<br />

AdventureWorksEntities.Contacts refers to the Contacts entity set that is defined in the<br />

AdventureWorksEntities EDM. As you can see, the syntax is very similar to traditional SQL.<br />

The following code example shows how you can sort the results, in this example, by LastName.<br />

SELECT VALUE c from AdventureWorksEntities.Contacts as c Order By c.LastName<br />

You can use aggregate functions, for example, Average, Sum, Min, Max, and Count. The following code<br />

example shows how you can calculate an average of the TotalDue values for each contact.<br />

SELECT contactID, AVG(order.TotalDue)<br />

FROM AdventureWorksEntities.SalesOrderHeaders<br />

AS order GROUP BY order.Contact.ContactID as contactID<br />

Your Entity SQL queries can use join operators to link data when no navigation property is defined in the<br />

entity model. Entity SQL supports cross joins, inner joins, and outer joins just like traditional SQL.


Specifying Parameters<br />

Querying Entity <strong>Data</strong> 3-15<br />

Your Entity SQL queries can also contain placeholders for parameter values. The placeholder name always<br />

begins <strong>with</strong> an at sign (@). The following code example shows a parameter placeholder called<br />

@lastName. You must provide a suitable value for this placeholder at run time.<br />

SELECT VALUE c from AdventureWorksEntities.Contacts as c WHERE c.LastName=@lastName<br />

Querying <strong>Data</strong> <strong>with</strong> Table-Per-Type Inheritance<br />

The Adventure Works EDM uses table-per-type inheritance to model the different types of rewards that<br />

customers can claim, such as Airmiles rewards, supermarket rewards, and Adventure Works rewards. The<br />

following code example shows how Entity SQL syntax supports this type of model. The query only returns<br />

rewards of the AdventureWorksRewards subtype.<br />

SELECT VALUE r FROM OfType(AdventureWorksEntities.Rewards,<br />

AdventureWorksModel.AdventureWorksReward) AS r<br />

You can page through large collections of entities by using the SKIP and LIMIT clauses in your Entity SQL<br />

query.<br />

Question: List some of the advantages and disadvantages of using Entity SQL instead of LINQ for Entities.<br />

Additional Reading<br />

For more information about Entity SQL, see the Entity SQL Language page at<br />

http://go.microsoft.com/fwlink/?LinkID=194029.


3-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Using Entity SQL<br />

Key Points<br />

The following code example shows how you can execute queries that are defined by using Entity SQL.<br />

They run in the same way as LINQ to Entities queries, by using an ObjectQuery object. This example<br />

shows how to retrieve all of the Contact entities from the Adventure Works database. Notice that, in this<br />

case, you must instantiate a new ObjectQuery object, passing the query string and the ObjectContext<br />

object to the constructor.<br />

[<strong>Visual</strong> Basic]<br />

Using entities = New AdventureWorksEntities()<br />

Dim queryString As String =<br />

“SELECT VALUE c from AdventureWorksEntities.Contacts as c"<br />

Dim query As ObjectQuery(Of Contact) =<br />

New ObjectQuery(Of Contact)(queryString, entities)<br />

query.MergeOption = MergeOption.NoTracking<br />

Dim results As List(Of Contact) = query.ToList()<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

string queryString =<br />

@"SELECT VALUE c from AdventureWorksEntities.Contacts as c";<br />

ObjectQuery query =<br />

new ObjectQuery(queryString, entities);<br />

query.MergeOption = MergeOption.NoTracking;<br />

List results = query.ToList();<br />


}<br />

Querying Entity <strong>Data</strong> 3-17<br />

Note: Any syntax errors in your Entity SQL are likely to cause an EntitySQLException exception at run<br />

time.<br />

If you are building an Entity SQL query dynamically, you need to take special care to avoid any<br />

EntitySQLException exceptions. In this case, you should consider using the query builder methods of<br />

ObjectQuery instead of constructing an Entity SQL query string at run time.<br />

Using Parameters<br />

If your Entity SQL code contains parameter placeholders, you must provide values of the correct type<br />

before you execute the query. The following code example shows how you can return contact entities<br />

based on the contactID value to your client application.<br />

[<strong>Visual</strong> Basic]<br />

Using entities = New AdventureWorksEntities()<br />

Dim contactID As Integer = 2019<br />

Dim queryString As String =<br />

"SELECT VALUE r from AdventureWorksEntities.RewardsClaimed" &<br />

"as r WHERE r.ContactID=@contactID"<br />

Dim query As ObjectQuery(Of Contact) =<br />

New ObjectQuery(Of Contact)(queryString, entities)<br />

query.Parameters.Add(New ObjectParameter("contactID", contactID))<br />

query.MergeOption = MergeOption.NoTracking<br />

Dim results As List(Of Contact) = query.ToList()<br />

…<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

int contactID = 2019;<br />

string queryString =<br />

@"SELECT VALUE r from AdventureWorksEntities.RewardsClaimed<br />

as r WHERE r.ContactID=@contactID";<br />

ObjectQuery query =<br />

new ObjectQuery(queryString, entities);<br />

query.Parameters.Add(new ObjectParameter("contactID", contactID));<br />

query.MergeOption = MergeOption.NoTracking;<br />

List results = query.ToList();<br />

…<br />

}<br />

Question: Why should you use the ObjectParameter class to supply a parameter instead of performing<br />

string manipulation on the query string?<br />

Additional Reading<br />

For more information about query plan caching, see the Query Plan Caching (Entity SQL) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194030.


3-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 3<br />

Retrieving <strong>Data</strong> by Using the EntityClient Provider<br />

LINQ to Entities and Entity SQL return entity objects from queries that are executed against the<br />

conceptual model. In subsequent modules, you will see how you can use these entity objects to perform<br />

data modifications. The EntityClient provider for the Entity Framework enables you to return read-only<br />

data as rowsets. This may provide performance benefits at the expense of reduced functionality.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the components of the EntityClient provider for the Entity Framework.<br />

• Describe how to connect to the EDM by using an EntityConnection object.<br />

• Show how to create and execute a query by using an EntityCommand object.<br />

• <strong>Access</strong> the query results by using an Entity<strong>Data</strong>Reader object.


Understanding the EntityClient Provider for the Entity Framework<br />

Key Points<br />

Querying Entity <strong>Data</strong> 3-19<br />

The EntityClient provider enables you to return read-only entity data as rowsets instead of as entity<br />

objects. It runs against the EDM, and enables detailed control over the connections and commands that<br />

you use to retrieve data. The EntityClient provider classes are defined in the System.<strong>Data</strong>.Entity<br />

namespace. You should consider using the EntityClient provider if you need to access data as rowsets<br />

rather than entity objects, or if query performance is critical to your application.<br />

You use the EntityConnection class to connect to the EDM by using a connection string.<br />

You use the EntityCommand class to define queries in Entity SQL to retrieve data.<br />

You use the Entity<strong>Data</strong>Reader class to process the results from running a query.<br />

Note: If you are familiar <strong>with</strong> using providers such as the ADO.NET provider in traditional ADO.NET, you<br />

will find that the EntityClient provider operates in a similar fashion.<br />

Question: When should you consider using the EntityClient provider?


3-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Connecting to the Model by Using an EntityConnection Object<br />

Key Points<br />

When you use LINQ to Entities and Entity SQL, the ObjectContext class is responsible for opening and<br />

closing connections to the underlying database. However, when you use the EntityClient provider, it<br />

becomes your responsibility to manage the connection. You can then use this connection <strong>with</strong><br />

EntityCommand objects to query the database.<br />

The following code example shows one way to establish a connection to the Adventure Works database in<br />

preparation for executing an EntityCommand query.<br />

[<strong>Visual</strong> Basic]<br />

Using connection = New EntityConnection("name=AdventureWorksEntities")<br />

' Open the connection<br />

connection.Open()<br />

…<br />

connection.Close()<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (EntityConnection connection = new<br />

EntityConnection("name=AdventureWorksEntities"))<br />

{<br />

// Open the connection<br />

connection.Open();<br />

…<br />

connection.Close();<br />

}


Querying Entity <strong>Data</strong> 3-21<br />

The connection string in the app.config file specifies both the model to use <strong>with</strong> its metadata and<br />

mapping information, and the connection details to the underlying database. The following code example<br />

shows an example connection string.<br />

<br />

<br />

<br />

Question: How does working <strong>with</strong> the EntityConnection class differ from working <strong>with</strong> the<br />

ObjectContext class in relation to opening connections to the database?<br />

Additional Reading<br />

For more information about building connection strings, see the Connection Strings (Entity Framework)<br />

page at http://go.microsoft.com/fwlink/?LinkID= 194031.


3-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Creating Queries by Using an EntityCommand Object<br />

Key Points<br />

You use an EntityCommand object to execute an Entity SQL command against the entity model. The<br />

Entity SQL command can be query text or the name of a stored procedure that you have imported into<br />

the entity model. The parameters to the EntityCommand constructor specify the Entity SQL command<br />

and the connection to use.<br />

The following code example shows how you can define a query to return a list of Rewards Claimed<br />

records for a specific contact. It demonstrates how to provide the contactID parameter to the<br />

EntityCommand objects by using an EntityParameter object.<br />

[<strong>Visual</strong> Basic]<br />

Using connection = New EntityConnection("name=AdventureWorksEntities")<br />

connection.Open()<br />

Dim queryString As String = "SELECT VALUE r from " &<br />

"AdventureWorksEntities.RewardsClaimed as r WHERE " &<br />

"r.ContactID=@contactID"<br />

Using cmd = New EntityCommand(queryString, connection)<br />

Dim param As EntityParameter = New EntityParameter()<br />

param.ParameterName = "contactID"<br />

param.Value = contactID<br />

cmd.Parameters.Add(param)<br />

…<br />

End Using<br />

connection.Close()<br />

End Using


[<strong>Visual</strong> C#]<br />

using (EntityConnection connection = new<br />

EntityConnection("name=AdventureWorksEntities"))<br />

{<br />

connection.Open();<br />

string queryString = "SELECT VALUE r from<br />

AdventureWorksEntities.RewardsClaimed as r WHERE<br />

r.ContactID=@contactID";<br />

using (EntityCommand cmd =<br />

new EntityCommand(queryString, connection))<br />

{<br />

EntityParameter param = new EntityParameter();<br />

param.ParameterName = "contactID";<br />

param.Value = contactID;<br />

cmd.Parameters.Add(param);<br />

…<br />

}<br />

connection.Close();<br />

}<br />

Querying Entity <strong>Data</strong> 3-23<br />

You can also create an EntityCommand object from the EntityConnection object, as the following code<br />

example shows.<br />

[<strong>Visual</strong> Basic]<br />

Dim cmd As EntityCommand = connection.CreateCommand()<br />

[<strong>Visual</strong> C#]<br />

EntityCommand cmd = connection.CreateCommand();<br />

Question: What are the parameters to the EntityCommand constructor?<br />

Additional Reading<br />

For further information about EntityCommand objects and the EntityClient provider, see the EntityClient<br />

Provider for the Entity Framework page at http://go.microsoft.com/fwlink/?LinkID=194032.


3-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Processing Query Results by Using an Entity<strong>Data</strong>Reader Object<br />

Key Points<br />

The EntityCommand object returns data as primitive types rather than entity objects. If you are familiar<br />

<strong>with</strong> using Command objects in traditional ADO.NET, you will find that EntityCommand objects operate<br />

in a similar fashion. You can execute the command in one of three ways as described in the following<br />

table.<br />

EntityCommand methods Description<br />

ExecuteReader() Returns multiple rows and columns.<br />

ExecuteScalar() Returns the first row/column entry.<br />

ExecuteNonQuery() Executes a command <strong>with</strong> no return value.<br />

When you use the ExecuteReader method, you must handle the data that the query returns in an<br />

Entity<strong>Data</strong>Reader object. The Entity<strong>Data</strong>Reader object returns a single row at a time, and enables you<br />

to access the individual fields and their metadata. The important methods and properties of this object<br />

include:<br />

• The Read method, which returns the next row in the Entity<strong>Data</strong>Reader object. You must call this<br />

method before trying to access any data.<br />

• The various Get methods, which return metadata and typed data from the current row.<br />

• The GetBytes method, which reads a stream of bytes from the specified column.<br />

• The HasRows Boolean property, which indicates whether the Entity<strong>Data</strong>Reader object has one or<br />

more rows.<br />

• The FieldCount property, which tells you how many columns there are in the current row.<br />

The following code example shows the use of an Entity<strong>Data</strong>Reader object to process the list of Rewards<br />

Claimed records returned by a query against the Adventure Works database. The


Querying Entity <strong>Data</strong> 3-25<br />

CommandBehavior.Sequential<strong>Access</strong> parameter of the ExecuteReader method is the default behavior and<br />

indicates that the data will be read in a forward-only mode, providing the best performance.<br />

[<strong>Visual</strong> Basic]<br />

Dim claim As New RewardsClaimed()<br />

Using connection = New EntityConnection("name=AdventureWorksEntities")<br />

connection.Open()<br />

Dim queryString As String = "SELECT VALUE r from " &<br />

"AdventureWorksEntities.RewardsClaimed as r WHERE " &<br />

"r.ContactID=@contactID"<br />

Using cmd = New EntityCommand(queryString, connection)<br />

Dim param = New EntityParameter()<br />

param.ParameterName = "contactID"<br />

param.Value = contactID<br />

cmd.Parameters.Add(param)<br />

Using reader As Entity<strong>Data</strong>Reader =<br />

cmd.ExecuteReader(CommandBehavior.Sequential<strong>Access</strong>)<br />

While reader.Read()<br />

End While<br />

End Using<br />

End Using<br />

connection.Close()<br />

End Using<br />

Dim r As IExtended<strong>Data</strong>Record = reader<br />

claim.ClaimID = reader.GetInt32(0)<br />

claim.RewardID = reader.GetInt32(1)<br />

claim.PointsUsed = reader.GetInt32(2)<br />

claim.ContactID = reader.GetInt32(3)<br />

[<strong>Visual</strong> C#]<br />

RewardsClaimed claim = new RewardsClaimed();<br />

using (EntityConnection connection =<br />

new EntityConnection("name=AdventureWorksEntities"))<br />

{<br />

connection.Open();<br />

string queryString = "SELECT VALUE r from<br />

AdventureWorksEntities.RewardsClaimed as r WHERE<br />

r.ContactID=@contactID";<br />

using (EntityCommand cmd =<br />

new EntityCommand(queryString, connection))<br />

{<br />

EntityParameter param = new EntityParameter();<br />

param.ParameterName = "contactID";<br />

param.Value = contactID;<br />

cmd.Parameters.Add(param);<br />

using (Entity<strong>Data</strong>Reader reader =<br />

cmd.ExecuteReader(CommandBehavior.Sequential<strong>Access</strong>))<br />

{<br />

while (reader.Read())


3-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

{<br />

IExtended<strong>Data</strong>Record r = reader;<br />

claim.ClaimID = reader.GetInt32(0);<br />

claim.RewardID = reader.GetInt32 (1);<br />

claim.PointsUsed = reader.GetInt32(2);<br />

claim.ContactID = reader.GetInt32 (3);<br />

}<br />

}<br />

}<br />

connection.Close();<br />

Question: Why does using the Entity<strong>Data</strong>Reader class give good performance?<br />

Additional Reading<br />

For further information about the Execute methods of the EntityCommand object, see the<br />

EntityCommand Class page at http://go.microsoft.com/fwlink/?LinkID=194033.<br />

For further information about the Entity<strong>Data</strong>Reader class, see the Entity<strong>Data</strong>Reader Class page at<br />

http://go.microsoft.com/fwlink/?LinkID=194034.<br />

For further information about EntityCommand objects and the EntityClient provider, see the EntityClient<br />

Provider for the Entity Framework page at http://go.microsoft.com/fwlink/?LinkID=194035.


Lesson 4<br />

Retrieving <strong>Data</strong> by Using Stored Procedures<br />

Querying Entity <strong>Data</strong> 3-27<br />

You can use stored procedures to aid security, provide predictability, and encapsulate logic <strong>with</strong> data<br />

inside a database. An EDM can reference stored procedures by using function imports. You can invoke<br />

these stored procedures by using the ObjectContext object for the EDM.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe how to invoke stored procedures in the EDM.<br />

• Describe how to handle stored procedures <strong>with</strong> output parameters.


3-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Invoking Stored Procedures<br />

Key Points<br />

You can use stored procedures as part of the mapping in your entity model to support retrieving (and<br />

updating) entities. You do not need to do anything special to use these types of stored procedure in your<br />

application code. You can continue to query entities by using LINQ to Entities or Entity SQL.<br />

If you import stored procedures <strong>with</strong> input or output parameters into the entity model as function<br />

imports, you can call these stored procedures directly from your application code as methods of the<br />

ObjectContext class.<br />

Note: To create the function import, you must specify the stored procedure to call and the return type of<br />

the stored procedure. The function import is not shown in the ADO.NET Entity <strong>Data</strong> Model Designer<br />

(Entity Designer), but <strong>with</strong> the Entity Designer open, you can view the function and the stored procedure<br />

details in the Model Browser window.<br />

The Adventure Works EDM might contain an imported stored procedure called CountAirmilesClaims<br />

that returns the number of Airmiles reward claims that a contact has placed. It takes a single input<br />

parameter that is the contactID, and returns an integer value that is the number of claims that are<br />

recorded for the contact. The following code example shows how to invoke this stored procedure.<br />

[<strong>Visual</strong> Basic]<br />

Using entities = New AdventureWorksEntities()<br />

Return DirectCast(<br />

entities.CountAirmilesClaims(contactID).First(), Integer)<br />

End Using<br />

[<strong>Visual</strong> C#]


using (AdventureWorksEntities entities = new AdventureWorksEntities())<br />

{<br />

return (int)entities.CountAirmilesClaims(contactID).First();<br />

}<br />

Querying Entity <strong>Data</strong> 3-29<br />

Question: What are the two ways in which you can use stored procedures in your entity model?


3-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Invoking Stored Procedures <strong>with</strong> Output Parameters<br />

Key Points<br />

Stored procedures can have both input and output parameters. To handle output parameters, you use the<br />

ObjectParameter class.<br />

The following code example shows a stored procedure <strong>with</strong> one input parameter and one output<br />

parameter.<br />

CREATE PROCEDURE [dbo].[GetRewardName]<br />

@ID int,<br />

@RewardName nvarchar(50) OUTPUT<br />

AS<br />

SELECT @RewardName = Name FROM Reward<br />

WHERE RewardID = @ID<br />

If you import the stored procedure into the EDM, you can use the following code example to invoke the<br />

stored procedure and retrieve the reward name for the reward <strong>with</strong> a reward ID of 1.<br />

[<strong>Visual</strong> Basic]<br />

Using context = New AdventureWorksEntities()<br />

Dim name As New ObjectParameter("RewardName", GetType(String))<br />

context.GetRewardName(1, name)<br />

Console.WriteLine(name.Value)<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities ())<br />

{


}<br />

ObjectParameter name = new ObjectParameter("RewardName",<br />

typeof(String));<br />

context.GetRewardName(1, name);<br />

Console.WriteLine(name.Value);<br />

Querying Entity <strong>Data</strong> 3-31<br />

Question: Which class do you need to use to handle an output parameter for a stored procedure that is<br />

embedded in your EDM?


3-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 5<br />

Unit Testing Your <strong>Data</strong> <strong>Access</strong> Code<br />

In many development environments, it is standard practice to create unit tests for your code. <strong>Data</strong> access<br />

code that uses the Entity Framework is no exception. Organizing your data access code appropriately can<br />

streamline the way in which you create your unit tests. Centralizing all of your data access code in a data<br />

access layer component is one approach to take. <strong>Visual</strong> <strong>Studio</strong> has many built-in tools to help you create<br />

and manage your suite of unit tests.<br />

You must also establish a procedure for ensuring that the database is in a known state before you run any<br />

tests because many tests will involve comparing the data in the database <strong>with</strong> known values.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe an approach for unit testing your entity model.<br />

• Create a unit test to run against your entity model.


Establishing a Unit-Testing Environment<br />

Key Points<br />

Querying Entity <strong>Data</strong> 3-33<br />

It is important to follow a well-documented and systematic process to ensure that your testing is accurate<br />

and repeatable. The following steps outline the tasks that you can perform to establish a unit-testing<br />

environment for your data access code:<br />

1. Develop a set of scripts, or backup and restore operations, which you can use to return the database<br />

to a known state before you run any unit tests that will be referencing data in the database.<br />

2. Organize your code so that all of your data access code is found in a single class or component,<br />

typically, a data access layer that can be tested in isolation.<br />

3. Add a unit test project to your <strong>Visual</strong> <strong>Studio</strong> solution. This project is where you will write your unit test<br />

code and manage your unit tests.<br />

4. In the unit test project, you should write methods that create suitable dummy data that you can use if<br />

you need to check values in the database.<br />

5. In the unit test project, write your unit tests to verify that your data access code functions correctly.<br />

6. Establish procedures to ensure that the unit tests are run on a regular basis, or as a part of your<br />

standard build. <strong>Visual</strong> <strong>Studio</strong> also enables you to run unit tests on an informal basis; this is particularly<br />

useful to help you check that any refactoring that you perform will not break your application.<br />

Question: Will all of your unit tests for your data access layer need to touch the database?<br />

Additional Reading<br />

For more information about the unit-testing tools in <strong>Visual</strong> <strong>Studio</strong>, see the Verifying Code by Using Unit<br />

Tests page at http://go.microsoft.com/fwlink/?LinkID=194036.


3-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Creating Unit Tests<br />

Key Points<br />

When you create a unit test project in <strong>Visual</strong> <strong>Studio</strong>, it creates the framework that is required to create,<br />

run, and manage your unit tests. You can add new skeleton tests for methods by right-clicking in a code<br />

file and then clicking Create Unit Test.<br />

Before you create your unit test, you may need to define some test data that you will use as a comparison<br />

<strong>with</strong> the data that you retrieve from the database. The following code example shows a unit test method<br />

that creates two sample RewardsClaimed objects that you can use in your unit tests.<br />

[<strong>Visual</strong> Basic]<br />

Private Function GetLocalRewardsClaimedList() As List(Of RewardsClaimed)<br />

Dim rewards As New List(Of RewardsClaimed)()<br />

Dim claim1 As New RewardsClaimed With<br />

{<br />

.ClaimID = 2,<br />

.PointsUsed = 20000,<br />

.RewardID = 2,<br />

.ContactID = 2<br />

}<br />

rewards.Add(claim1)<br />

Dim claim2 As New RewardsClaimed With<br />

{<br />

.ClaimID = 11,<br />

.PointsUsed = 25000,<br />

.RewardID = 21,<br />

.ContactID = 2<br />

}<br />

rewards.Add(claim2)<br />

Return rewards<br />

End Function


[<strong>Visual</strong> C#]<br />

private List GetLocalRewardsClaimedList()<br />

{<br />

List rewards = new List();<br />

}<br />

RewardsClaimed claim1 = new RewardsClaimed<br />

{<br />

ClaimID = 2,<br />

PointsUsed = 20000,<br />

RewardID = 2,<br />

ContactID = 2<br />

};<br />

rewards.Add(claim1);<br />

RewardsClaimed claim2 = new RewardsClaimed<br />

{<br />

ClaimID = 11,<br />

PointsUsed = 25000,<br />

RewardID = 21,<br />

ContactID = 2<br />

};<br />

rewards.Add(claim2);<br />

return rewards;<br />

Querying Entity <strong>Data</strong> 3-35<br />

Note: This code example uses instances of your entity classes. If your data access layer uses data transfer<br />

objects, you should create instances of the data transfer objects instead.<br />

You can then fill in the automatically generated skeleton unit test methods <strong>with</strong> code to perform your<br />

unit test logic. The following code example compares two records that are retrieved from the EDM by<br />

using a method in the data access layer against the sample data that is created in the<br />

GetLocalRewardsClaimedList method.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetRewardsClaimedListTest()<br />

Dim dal As New <strong>Data</strong><strong>Access</strong>Layer()<br />

Dim contactID As Integer = 2<br />

Dim expected As List(Of RewardsClaimed) =<br />

Me.GetLocalRewardsClaimedList()<br />

Dim actual As List(Of RewardsClaimed) =<br />

dal.GetRewardsClaimedList(contactID)<br />

For i As Integer = 0 To expected.Count - 1<br />

Next i<br />

End Sub<br />

Assert.AreEqual(expected(i).PointsUsed, actual(i).PointsUsed)<br />

dal.Dispose()


3-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetRewardsClaimedListTest()<br />

{<br />

}<br />

<strong>Data</strong><strong>Access</strong>Layer dal = new <strong>Data</strong><strong>Access</strong>Layer();<br />

int contactID = 2;<br />

List expected = this.GetLocalRewardsClaimedList();<br />

List actual =<br />

dal.GetRewardsClaimedList(contactID);<br />

for (int i = 0; i < expected.Count; i++)<br />

{<br />

Assert.AreEqual(expected[i].PointsUsed, actual[i].PointsUsed);<br />

}<br />

dal.Dispose();<br />

You can run this unit test by using any of the facilities in <strong>Visual</strong> <strong>Studio</strong> for running unit tests.<br />

Note: Notice that the test creates a new instance of the data access layer at the beginning, and disposes<br />

of it at the end. This avoids any potential interaction between individual tests.<br />

Note: This example unit test does not modify any data in the database. If you are creating a unit test to<br />

test data modification functionality, you should reset the data at the end of the test. This will avoid any<br />

potential problems if you change the order of the tests or if you add new tests.<br />

Question: Why should you create a new instance of your data access layer component for each test?


Demonstration: Adding a Unit Test Project<br />

Key Points<br />

• Add a test project to a solution.<br />

• Add a unit test for a method.<br />

Demonstration Steps<br />

Querying Entity <strong>Data</strong> 3-37<br />

1. Log on to the 10265A-GEN-DEV-03 virtual machine as Student <strong>with</strong> the password Pa$$w0rd<br />

2. Open <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010.<br />

3. In <strong>Visual</strong> <strong>Studio</strong>, open the solution named UnitTestDemo in the E:\Demofiles\Mod03\Demo1\Starter<br />

folder.<br />

4. Add a Test project named DALTest to the solution.<br />

5. Open the <strong>Data</strong><strong>Access</strong>Layer code file in the DAL project.<br />

6. Add a unit test for the GetContactList method.<br />

7. Review the GetContactListTest method.<br />

8. Save and close the solution.<br />

Question: How can you identify a unit test method in your unit test project?


3-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab: Querying Entity <strong>Data</strong><br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Query an entity model by using LINQ to Entities.<br />

• Filter data by using LINQ to Entities.<br />

• Query an entity model by using Entity SQL.<br />

• Query an entity model by using the EntityClient provider for the Entity Framework.<br />

• Query an entity model by using a stored procedure.<br />

Introduction<br />

In this lab, you will use several different techniques to execute queries against your entity model. In<br />

addition, you will learn how to create unit tests for your data access code.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-03 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


Lab Scenario<br />

Querying Entity <strong>Data</strong> 3-39<br />

Adventure Works implements an entity model to support its customer reward program. You have been<br />

asked to implement a data access layer to provide the functionality that the customer rewards client<br />

application requires. In this first phase, you only need to implement the functionality that is necessary to<br />

retrieve data from the model.<br />

You must add functionality to retrieve contact entities, reward entities, rewards claimed data, and the<br />

number of orders that a contact has placed. You must include unit tests for all of your methods, and verify<br />

that the client application displays the retrieved data correctly.


3-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 1: Retrieving All Contact Entities<br />

Scenario<br />

In this exercise, you will create a query that retrieves all of the contacts from the Adventure Works<br />

database. You will do this by using LINQ to Entities to query the entity model.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the Adventure Works database for the lab.<br />

2. Open the starter project for this exercise.<br />

3. Add code to retrieve all of the contacts.<br />

4. Add a unit test to verify your code.<br />

5. Build and test the application.<br />

Task 1: Prepare the Adventure Works database for the lab<br />

1. Log on to the 10265A-GEN-DEV-03 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. Run AWReset.bat in the E:\Labfiles folder.<br />

Task 2: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex1\Starter or<br />

E:\Labfiles\Lab03\VB\Ex1\Starter folder.<br />

Task 3: Add code to retrieve all of the contacts<br />

1. In <strong>Visual</strong> <strong>Studio</strong>, review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex1 - Retrieve all<br />

contacts item in the task list. This task is located in the first GetContactList method.<br />

3. Delete the existing code in the GetContactList method.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

Note: The entities variable is a private field in the <strong>Data</strong><strong>Access</strong>Layer class. Your code should perform all<br />

data access operations by using this context object.<br />

b. Create and define a LINQ to Entities query to select all Contact entities.<br />

c. Execute the query <strong>with</strong> MergeOption set to NoTracking.<br />

d. Return the results.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 4: Add a unit test to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex1 - Create a unit<br />

test for GetContactList item in the task list. This task is located in the GetContactListTest method.<br />

3. Delete the existing code in the GetContactListTest method.


Querying Entity <strong>Data</strong> 3-41<br />

4. Write a unit test to compare the first 10 contacts returned by your query <strong>with</strong> the 10 contacts<br />

returned by the GetLocalCustomerListFirstTen method. Be sure to release all resources at the end of<br />

the test.<br />

5. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 5: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.<br />

3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into<br />

the data grid. Verify that the application functions as expected.<br />

4. Close the application.<br />

5. Run all of the tests in the solution.<br />

6. Verify that all of the tests succeed, including GetContactListTest.<br />

7. Close the solution.


3-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 2: Retrieving Contact Entities by Using a Filter<br />

Scenario<br />

In this exercise, you will create a query that retrieves all of the contacts from the Adventure Works<br />

database that have a specified last name. You will do this by using a LINQ to Entities query that takes a<br />

parameter that specifies the name to match against.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Add code to retrieve contacts by last name.<br />

3. Add a unit test to verify your code.<br />

4. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex2\Starter or<br />

E:\Labfiles\Lab03\VB\Ex2\Starter folder.<br />

Task 2: Add code to retrieve contacts by last name<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex2 - Retrieve contacts<br />

by last name item in the task list. This task is located in the second GetContactList method.<br />

3. Delete the existing code in the GetContactList method.<br />

4. Add code to the method that performs the following tasks:<br />

a. Instantiate the entities context object if it is currently null.<br />

b. Create and define a LINQ to Entities query to retrieve Contact entities by last name.<br />

c. Execute the query <strong>with</strong> MergeOption set to NoTracking.<br />

d. Return the results.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 3: Add a unit test to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex2 - Create a unit<br />

test for GetContactList by last name item in the task list. This task is located in the<br />

GetAContactListTest method.<br />

3. Delete the existing code in the GetAContactListTest method.<br />

4. Write a unit test to compare the first contacts returned by your query, using “Adina” as the last name<br />

parameter, <strong>with</strong> the contacts returned by the GetLocalCustomerList method. Be sure to release all<br />

resources at the end of the test.<br />

5. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 4: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.<br />

3. In the AdventureWorks Rewards window, in the Name box, type Ward and then click Search. Verify<br />

that the application functions as expected and loads the correct contacts into the data grid.


4. Close the application.<br />

5. Run all of the tests in the solution.<br />

6. Verify that all of the tests succeed, including GetAContactListTest.<br />

7. Close the solution.<br />

Querying Entity <strong>Data</strong> 3-43


3-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 3: Retrieving RewardsClaimed Entities<br />

Scenario<br />

In this exercise, you will create a query that retrieves from the Adventure Works database all of the<br />

rewards that an individual contact has claimed. You will do this by using an Entity SQL query that takes a<br />

parameter that specifies the contact ID of the contact to match against.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Add code to retrieve rewards claimed by contact ID.<br />

3. Add a unit test to verify your code.<br />

4. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex2\Starter or<br />

E:\Labfiles\Lab03\VB\Ex3\Starter folder.<br />

Task 2: Add code to retrieve rewards claimed by contact ID<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex3 - Retrieve rewards<br />

claimed by contact ID item in the task list. This task is located in the GetRewardsClaimedList<br />

method.<br />

3. Delete the existing code in the GetRewardsClaimedList method.<br />

4. Add code that performs the following tasks:<br />

a. Instantiate the entities context object if it is currently null.<br />

b. Create and define an Entity SQL query to retrieve RewardsClaimed entities by contact ID.<br />

c. Execute the query <strong>with</strong> MergeOption set to NoTracking.<br />

d. Return the results.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 3: Add a unit test to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex3 - Create a unit<br />

test for GetRewardsClaimedList item in the task list. This task is located in the<br />

GetRewardsClaimedListTest method.<br />

3. Delete the existing code in the GetRewardsClaimedListTest method.<br />

4. Write a unit test to compare the RewardsClaimed entities returned by your query, using a contactID<br />

of 2 as the parameter value, <strong>with</strong> the RewardsClaimed entities returned by the<br />

GetLocalRewardsClaimedList method. Be sure to release all resources at the end of the test.<br />

5. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 4: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.


Querying Entity <strong>Data</strong> 3-45<br />

3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.<br />

Then, click a contact in the data grid to display rewards claimed by that contact in the second data<br />

grid.<br />

4. Close the application.<br />

5. Run all of the tests in the solution.<br />

6. Verify that all of the tests succeed, including GetRewardsClaimedListTest.<br />

7. Close the solution.


3-46 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 4: Querying the Rewards Family of Entities<br />

Scenario<br />

In this exercise, you will create a query that retrieves details of AdventureWorksReward rewards only.<br />

You will do this by using an Entity SQL query that takes a parameter that specifies the reward ID to match<br />

against, and retrieves rewards of the specified type.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Add code to retrieve reward details by reward ID.<br />

3. Add a unit test to verify your code.<br />

4. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex4\Starter or<br />

E:\Labfiles\Lab03\VB\Ex4\Starter folder.<br />

Task 2: Add code to retrieve reward details by reward ID<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex4 - Get a reward by<br />

reward ID item in the task list. This task is located in the GetReward method.<br />

3. Delete the existing code in the GetReward method.<br />

4. Use the EntityConnection, EntityCommand, and Entity<strong>Data</strong>Reader classes to connect to the entity<br />

model, retrieve the details of a reward by rewardID, and then return the result as a Reward entity.<br />

Set the MergeOption of the query to NoTracking.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 3: Add a unit test to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex4 - Create a unit<br />

test for GetReward item in the task list. This task is located in the GetRewardTest method.<br />

3. Delete the existing code in the GetRewardTest method.<br />

4. Write a unit test to compare the Reward entity returned by your query, using a rewardID of 21 as<br />

the parameter value, <strong>with</strong> the Reward entity returned by the GetLocalReward<strong>Data</strong> method. Be sure<br />

to release all resources at the end of the test.<br />

5. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 4: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.<br />

3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.<br />

Click a contact in the data grid to display reward claims in the second data grid. Then, click a rewards<br />

claim in the second data grid to display the reward details on the form.<br />

4. Close the application.<br />

5. Run all of the tests in the solution.


6. Verify that all of the tests succeed, including GetRewardTest.<br />

7. Close the solution.<br />

Querying Entity <strong>Data</strong> 3-47


3-48 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 5: Executing a Stored Procedure<br />

Scenario<br />

In this exercise, you will invoke a stored procedure that returns the number of orders that a contact has<br />

placed. You will do this by calling a method that wraps an imported function in the entity model.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Add code to retrieve the number of orders that a contact has placed.<br />

3. Add a unit test to verify your code.<br />

4. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex5\Starter or<br />

E:\Labfiles\Lab03\VB\Ex5\Starter folder.<br />

Task 2: Add code to retrieve the number of orders that a contact has placed<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex5 - Call the<br />

CountOrders stored procedure item in the task list. This task is located in the CountOrders method.<br />

3. Delete the existing code in the CountOrders method.<br />

4. Add code that performs the following tasks:<br />

a. Instantiate the entities context object if it is currently null.<br />

b. Invoke the CountOrders method on the context object.<br />

c. Return the results.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 3: Add a unit test to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex5 - Create a unit<br />

test for CountOrders item in the task list. This task is located in the CountOrdersTest method.<br />

3. Delete the existing code in the CountOrdersTest method.<br />

4. Write a unit test to verify that the contact <strong>with</strong> a contact ID of 2 has placed four orders.<br />

5. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 4: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.<br />

3. In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid.<br />

Then, click a contact in the data grid to display the number of orders placed by that contact on the<br />

form.<br />

4. Close the application.<br />

5. Run all of the tests in the solution.<br />

6. Verify that all of the tests succeed, including CountOrdersTest.<br />

7. Close <strong>Visual</strong> <strong>Studio</strong>.


Lab Review<br />

Review Questions<br />

Querying Entity <strong>Data</strong> 3-49<br />

1. Which class do you use to define a query that returns entity objects?<br />

2. What type does the EntityClient provider use to return data from a query?<br />

3. Which query operator enables you to work <strong>with</strong> entities that are organized in a table-per-type<br />

hierarchy?<br />

4. Where can you find an imported function in the EDM?


3-50 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. What are the two query technologies that enable you to retrieve entity objects from your model?<br />

2. What structure does the EntityClient provider for the Entity Framework use to return data?<br />

3. What is the role of the ObjectContext object?<br />

Best Practices Related to Querying Your Entity Model <strong>with</strong> the Entity Framework<br />

Supplement or modify the following best practices for your own work situations:<br />

• To enable as much compile-time checking as possible, you should consider using LINQ to Entities.<br />

• Entity SQL is useful when you need to construct queries dynamically at run time, when you want to<br />

store the query as a part of the model definition, or if you are already an expert in SQL-based query<br />

languages.<br />

• If performance is critical, and you only need to retrieve read-only data as rowsets, consider using the<br />

EntityClient provider.<br />

• Use stored procedures that are embedded in your EDM to enforce security, provide predictability,<br />

and encapsulate logic on data inside the database.<br />

• Organize your data access code into a single data access layer class or component.<br />

• Create unit tests for all of the public methods in your data access layer.


Module 4<br />

Creating, Updating, and Deleting Entity <strong>Data</strong><br />

Contents:<br />

Lesson 1: Understanding Change Tracking in the Entity Framework 4-3<br />

Lesson 2: Modifying <strong>Data</strong> in an Entity <strong>Data</strong> Model 4-9<br />

Lab: Creating, Updating, and Deleting Entity <strong>Data</strong> 4-21<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-1


4-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

Most data-aware solutions will need to provide update functionality to their users. The Entity Framework<br />

provides a change tracking mechanism to track the changes to the data in the model and you must write<br />

code to explicitly persist the changes to the database.<br />

This module introduces you to the ways in which the Entity Framework enables you to modify data in<br />

your database. You apply changes to the entities managed by the ObjectContext class. The<br />

ObjectContext class is responsible for tracking all changes to entities and then persisting these changes<br />

to the database on request.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Describe how the Entity Framework implements change tracking.<br />

• Describe how to modify data in the entity model, and persist the changes to the database.


Creating, Updating, and Deleting Entity <strong>Data</strong> 4-3<br />

Lesson 1<br />

Understanding Change Tracking in the Entity<br />

Framework<br />

In this lesson, you will learn how the ObjectContext class performs data modification tasks for your<br />

application. The ObjectContext class, or the derived version created from your Entity <strong>Data</strong> Model (EDM),<br />

handles data modifications in two stages. In the first stage, your code makes changes to the entities and<br />

entity sets managed by the ObjectContext class. The ObjectStateManager class in the ObjectContext<br />

class tracks all of these modifications; in the second stage, it persists these changes to the underlying<br />

database. The Entity Framework generates all of the required database commands, so your code operates<br />

only on common language runtime (CLR) objects.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the role of the ObjectContext and ObjectStateManager classes in the data modification<br />

process.<br />

• Describe the differences between detached and attached entity objects.<br />

• Describe how the ObjectStateManager class tracks changes to entity objects.


4-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The ObjectContext and ObjectStateManager Classes<br />

Key Points<br />

An ObjectContext object contains entity objects that represent data in your underlying database. If you<br />

want to modify data, you first make a change to the contents of the ObjectContext object and then ask<br />

the ObjectContext object to save the changes to the underlying database.<br />

Populating the ObjectContext Object<br />

There are two ways that you can load data into your ObjectContext object. First, you can run an Entity<br />

Structured Query Language (Entity SQL) or Language-Integrated Query (LINQ) to Entities query that<br />

retrieves data from the database.<br />

Note: If you plan to make changes to this data, it is important that you do not use the<br />

MergeOption.NoTracking value for the MergeOption property of the ObjectQuery object when you<br />

execute the query.<br />

The second way that you can load data into the ObjectContext object is by key. The following code<br />

example shows how you can load a contact entity if you know the EntityKey value of the contact that you<br />

want to modify.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As Object = Nothing<br />

Dim key As EntityKey = New EntityKey( _<br />

"AdventureWorksEntities.Contacts", "contactID", contactID)<br />

' Ensure that the contact to modify is loaded.<br />

If entities.TryGetObjectByKey(key, contact) Then<br />

' Use the returned contact entity<br />


End If<br />

[<strong>Visual</strong> C#]<br />

object contact = null;<br />

EntityKey key = new EntityKey("AdventureWorksEntities.Contacts",<br />

"contactID", contactID);<br />

// Ensure that the contact to modify is loaded.<br />

if (entities.TryGetObjectByKey(key, out contact))<br />

{<br />

// Use the returned contact entity<br />

…<br />

}<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-5<br />

If the contact entity is not already in the ObjectContext object, the TryGetObjectByKey method loads<br />

the entity from the database and returns true if it succeeds.<br />

Modifying <strong>Data</strong> in the ObjectContext Object<br />

To create a new entity, you instantiate a new object of the entity type, set the properties of the entity<br />

object, and add it to the ObjectContext object.<br />

To update an existing entity, you get a reference to the entity object and modify the properties of the<br />

entity object.<br />

To delete an existing entity, you get a reference to the entity object and mark it for deletion.<br />

The ObjectContext class uses the ObjectStateManager class to track all of these changes. The<br />

ObjectStateManager class marks newly created objects as new, and it marks deleted objects as deleted.<br />

In the case of updated objects, the ObjectStateManager class marks these objects as changed and stores<br />

both the original and changed versions of the object.<br />

Persisting Modifications to the <strong>Data</strong>base<br />

When you call the SaveChanges method of the ObjectContext class, the Entity Framework generates<br />

and executes the commands to save the changes to the database in the context of a single transaction.<br />

By default, if the SaveChanges method succeeds, the ObjectContext class resets all of the tracking<br />

information maintained by the ObjectStateManager object. You can override this default behavior by<br />

calling the overloaded version of the SaveChanges method <strong>with</strong> a SaveOptions.None parameter.<br />

Question: Describe two ways in which you can load an entity into an ObjectContext object for<br />

modification.


4-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Attaching and Detaching Objects<br />

Key Points<br />

Entity objects can be in an attached or detached state. The ObjectContext class directly manages<br />

attached entity objects and can track changes to them, and persist these changes to the database. The<br />

ObjectContext class does not manage detached objects; for example, a newly instantiated entity object is<br />

detached and cannot be saved to the database until you add it to an ObjectContext object.<br />

The Entity Framework automatically attaches entity objects returned by a query, unless you ran the query<br />

<strong>with</strong> the MergeOption property set to MergeOption.NoTracking. In this case, all of the returned entities<br />

will be detached. In the AdventureWorks application, when you retrieve entities from the EDM, you set<br />

the MergeOption property to MergeOption.NoTracking. Using these detached objects helps you<br />

conserve resources in the application, because the user will only browse many of these entities in the user<br />

interface.<br />

You can attach objects to the ObjectContext object by using the Attach or AttachTo methods of the<br />

ObjectContext class.<br />

You can detach objects from the ObjectContext object by using the Detach method of the<br />

ObjectContext class.<br />

Question: Why do detached entity objects consume fewer resources than attached entity objects?<br />

Additional Reading<br />

For more information about store-generated column values, see the How to: Work <strong>with</strong> Store Generated<br />

Column Values (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194037.


Change Tracking and Identity Resolution<br />

Key Points<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-7<br />

The ObjectContext class uses the ObjectStateManager class to maintain a cache of attached objects and<br />

track changes to these objects. The ObjectContext class uses EntityKey objects to ensure that it holds<br />

only a single copy of any entity in the cache. When you run a query, the ObjectContext class loads only<br />

the missing entities into the cache.<br />

You can control this behavior by using the MergeOption property of a query. The following table shows<br />

how the different values of the MergeOption property affect the way that entities are loaded into the<br />

cache.<br />

MergeOption Description<br />

AppendOnly This is the default behavior. Only entities that do not already exist in the cache<br />

are loaded from the database.<br />

OverwriteChanges All entities are loaded from the database, and any entity property changes in the<br />

cache are overwritten <strong>with</strong> values from the database.<br />

PreserveChanges All entities are loaded from the database, but any entity property changes in the<br />

cache are preserved.<br />

NoTracking Entities are materialized in a detached state and are not tracked by the<br />

ObjectStateManager class.<br />

The ObjectContext class uses the ObjectStateManager class to track all of the changes you make to<br />

attached entities. The ObjectStateManager class maintains a collection of ObjectStateEntry objects.<br />

Each ObjectStateEntry object has a pair of properties to hold the current and original property values of<br />

the entity, a property that holds the key of the entity, and a State property that records whether the<br />

entity has been added, deleted, or modified. When you call the SaveChanges method in the


4-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

ObjectContext class, the Entity Framework uses the collection of ObjectStateEntry objects to perform<br />

the following actions:<br />

• For newly created entities, the Entity Framework runs a command to insert the record into the<br />

database and changes the value of the State property of the ObjectStateEntry object from Added<br />

to Unchanged.<br />

• For updated entities, the Entity Framework runs a command to update the record in the database,<br />

copies the contents of the CurrentValues property of the ObjectStateEntry object to the<br />

OriginalValues property, and changes the value of the State property of the ObjectStateEntry<br />

object from Modified to Unchanged.<br />

• For deleted entities, the Entity Framework runs a command to delete the record in the database and<br />

removes the entity from the context.<br />

Question: What is the default behavior of queries if a merge option is not specified?<br />

Additional Reading<br />

For more information about the SaveChanges method, see the ObjectContext.SaveChanges Method<br />

page at http://go.microsoft.com/fwlink/?LinkID=194038.


Lesson 2<br />

Modifying <strong>Data</strong> in an Entity <strong>Data</strong> Model<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-9<br />

In this lesson, you will learn how to perform create, update, and delete operations on information in your<br />

database by using the Entity Framework.<br />

You create new entities by instantiating new entity objects and adding them to the ObjectContext<br />

object. The ObjectStateManager class tracks this change and inserts a new record into the database<br />

when you call the SaveChanges method.<br />

You update existing entities by modifying the properties of an attached entity object. The<br />

ObjectStateManager class tracks this change and updates the original record in the database when you<br />

call the SaveChanges method.<br />

You delete entities by marking them as deleted in the ObjectContext class. The ObjectStateManager<br />

class tracks this change and deletes the original record from the database when you call the<br />

SaveChanges method.<br />

An Adventure Works client application can use this functionality to enable users to modify data in the<br />

database for all of the entity types defined in the AdventureWorks EDM.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Create a new entity object and persist the entity to the database.<br />

• Update an entity object and persist the changes to the database.<br />

• Delete an entity object and persist the change to the database.<br />

• Use stored procedures to persist changes to entities to the database.


4-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Creating and Saving a New Entity<br />

Key Points<br />

You can create a new entity object in two ways. The first way is to instantiate a new entity object and then<br />

use an Add method to add the entity to the ObjectContext object. The following code example shows<br />

how to use the AddObject method of the ObjectContext class to create a new Contact entity in the<br />

AdventureWorks EDM.<br />

[<strong>Visual</strong> Basic]<br />

' Instantiate a new Contact entity.<br />

Dim contact As New Contact()<br />

' Populate the properties of the Contact entity.<br />

…<br />

' Add the entity to the ObjectContext object.<br />

' This also attaches the entity.<br />

entities.AddObject("Contact", contact)<br />

[<strong>Visual</strong> C#]<br />

// Instantiate a new Contact entity.<br />

Contact contact = new Contact();<br />

// Populate the properties of the Contact entity.<br />

…<br />

// Add the entity to the ObjectContext object.<br />

// This also attaches the entity.<br />

entities.AddObject("Contact", contact);<br />

The Entity Framework also generates a custom Add method. The following code example shows the<br />

AddToContacts method in the AdventureWorks EDM.


[<strong>Visual</strong> Basic]<br />

' Instantiate a new Contact entity.<br />

Dim contact As New Contact()<br />

' Populate the properties of the Contact entity.<br />

…<br />

' Add the entity to the ObjectContext object.<br />

' This also attaches the entity.<br />

entities.AddToContacts(contact)<br />

[<strong>Visual</strong> C#]<br />

// Instantiate a new Contact entity.<br />

Contact contact = new Contact();<br />

// Populate the properties of the Contact entity.<br />

…<br />

// Add the entity to the ObjectContext object.<br />

// This also attaches the entity.<br />

entities.AddToContacts(contact);<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-11<br />

The second way to create a new entity is by using the custom Create method generated by the Entity<br />

Framework. The following code example shows the CreateContacts method in the AdventureWorks EDM.<br />

[<strong>Visual</strong> Basic]<br />

' Instantiate a new Contact entity.<br />

Dim contact As Contact = contact.CreateContact(0, True, _<br />

"Ronald", "Adina", 0, "xyz", "abc", _<br />

Guid.NewGuid(), DateTime.Now, 1000)<br />

' Add the entity to the ObjectContext object.<br />

' This also attaches the entity.<br />

entities.AddToContacts(contact)<br />

[<strong>Visual</strong> C#]<br />

// Instantiate a new Contact entity.<br />

Contact contact = Contact.CreateContact(0, true,<br />

"Ronald", "Adina", 0, "xyz", "abc",<br />

Guid.NewGuid(), DateTime.Now, 1000);<br />

// Add the entity to the ObjectContext object.<br />

// This also attaches the entity.<br />

entities.AddToContacts(contact);<br />

This generated method has a parameter for every property that cannot be null. If you create the entity by<br />

using the constructor, you must ensure that you provide a value for every property that cannot be null, as<br />

specified in your EDM.<br />

Note: If the key is an identity key, the key value created by the database will be applied after the<br />

SaveChanges method has been called. Otherwise, you must provide a unique key value.<br />

To save any entities you have created and added to the ObjectContext object, you call the SaveChanges<br />

method of the ObjectContext class. This method will generate the necessary database insert statements<br />

to persist the entity.


4-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

You must handle any exceptions thrown by the SaveChanges method. For example, if two objects have<br />

the same user-specified key value, an InvalidOperationException exception occurs when the<br />

SaveChanges method is called. If this occurs, you should assign unique key values and retry the<br />

operation. This method can also throw an OptimisticConcurrencyException exception.<br />

Note: To reload an entity or a collection of entities from the database after calling the SaveChanges<br />

method, you can call the Refresh method of the ObjectContext class.<br />

Question: What types of error should your code handle when you save a new entity to the database?


Updating and Saving an Entity<br />

Key Points<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-13<br />

You can update an existing entity object in two ways. The first way is to modify the properties of an<br />

attached entity object. The following code example shows how to set the Name property of a Reward<br />

entity in the AdventureWorks EDM.<br />

[<strong>Visual</strong> Basic]<br />

Reward.Name = "Bonus Points"<br />

[<strong>Visual</strong> C#]<br />

Reward.Name = "Bonus Points";<br />

The second way is to copy the properties of a detached object to an attached object. The following code<br />

example shows how to copy the properties of the detached StoreContact entity object to the attached<br />

storeContactToModify object.<br />

[<strong>Visual</strong> Basic]<br />

' Get the key of the StoreContact entity you are modifying.<br />

Dim key As EntityKey = entities.CreateEntityKey("StoreContacts", Contact)<br />

Dim storeContactToModify As Object = Nothing<br />

' Ensure that the StoreContact entity to modify is loaded.<br />

If entities.TryGetObjectByKey(key, storeContactToModify) Then<br />

End If<br />

' Copy all the changes over.<br />

entities.ApplyCurrentValues(key.EntitySetName, Contact)


4-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> C#]<br />

// Get the key of the StoreContact entity you are modifying.<br />

EntityKey key = entities.CreateEntityKey("StoreContacts", contact);<br />

object storeContactToModify;<br />

// Ensure that the StoreContact entity to modify is loaded.<br />

if (entities.TryGetObjectByKey(key, out storeContactToModify))<br />

{<br />

// Copy all the changes over.<br />

entities.ApplyCurrentValues(key.EntitySetName, contact);<br />

}<br />

To save any entities you have modified in the ObjectContext object, you call the SaveChanges method<br />

of the ObjectContext class. This method will generate the necessary database update statements to<br />

persist the entity.<br />

You must handle any exceptions thrown by the SaveChanges method. For example, if the change results<br />

in a referential integrity violation in the database, the Entity Framework will throw an UpdateException<br />

exception. In the AdventureWorks EDM, you may get a referential integrity violation when you save a<br />

StoreContact entity if another user has updated the related Contact record. Remember that all of the<br />

changes made by a single call to the SaveChanges method occur in the scope of a transaction.<br />

Note: To reload an entity or a collection of entities from the database after calling the SaveChanges<br />

method, you can call the Refresh method of the ObjectContext class.<br />

Question: What type of exception does the Entity Framework throw if your change results in a referential<br />

integrity violation?<br />

Additional Reading<br />

For more information about executing business logic during property changes, see the How to: Execute<br />

Business Logic During Scalar Property Changes (Entity Framework) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194039.


Deleting an Entity from an Entity Set<br />

Key Points<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-15<br />

You use the DeleteObject method of either the ObjectContext class or the entity set to mark an entity<br />

for deletion. The following code example demonstrates how to delete a StoreContact entity from the<br />

AdventureWorks EDM.<br />

[<strong>Visual</strong> Basic]<br />

Dim storeContactToDelete As Object = Nothing<br />

Dim key As EntityKey = _<br />

New EntityKey("AdventureWorksEntities.StoreContacts", _<br />

"contactID", contactID)<br />

' Make sure that the entity to modify is loaded<br />

If entities.TryGetObjectByKey(key, storeContactToDelete) Then<br />

' Delete the object<br />

entities.DeleteObject(storeContactToDelete)<br />

End If<br />

[<strong>Visual</strong> C#]<br />

object storeContactToDelete = null;<br />

EntityKey key = new EntityKey("AdventureWorksEntities.StoreContacts",<br />

"contactID", contactID);<br />

// Make sure that the entity to modify is loaded<br />

if (entities.TryGetObjectByKey(key, out storeContactToDelete))<br />

{<br />

// Delete the object<br />

entities.DeleteObject(storeContactToDelete);


4-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

If the EDM defines a relationship to another dependent entity, and if the relationship is marked <strong>with</strong><br />

in the conceptual model, the Entity Framework will automatically delete<br />

any dependent entities. You can use this to delete the RewardsClaimed entities related to a Contact<br />

entity in the AdventureWorks EDM.<br />

To delete from the database any entities marked for deletion in the ObjectContext object, you call the<br />

SaveChanges method of the ObjectContext class. This method will generate and execute the necessary<br />

database delete statements.<br />

You must handle any exceptions thrown by the SaveChanges method. For example, deleting a record can<br />

cause a referential integrity violation in the database, which will result in an UpdateException exception.<br />

Remember that all of the changes made by a single call to the SaveChanges method occur <strong>with</strong>in the<br />

scope of a transaction.<br />

Question: How can you automate cascading delete behavior?<br />

Additional Reading<br />

For more information about cascading deletes in the Entity Framework, see the OnDelete Element (CSDL)<br />

page at http://go.microsoft.com/fwlink/?LinkID=194040.


Using Stored Procedures to Persist Changes to the <strong>Data</strong>base<br />

Key Points<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-17<br />

The Entity Framework enables you to specify stored procedures to use when you modify entity data. You<br />

add these stored procedures to your entity model, and the stored procedures replace the methods<br />

generated by the Entity Framework. Stored procedures are called implicitly, so no changes are required to<br />

the data model defined in the conceptual schema or to your application code.<br />

Inserting <strong>Data</strong> by Using Stored Procedures<br />

The following code example shows how the insert stored procedure for the RewardsClaim table is<br />

defined in your EDM.<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

The following code example shows how to invoke this stored procedure from your application code when<br />

you add a new claim in the AdventureWorks EDM.<br />

[<strong>Visual</strong> Basic]<br />

' Add RewardsClaimed entity to the entity set.<br />

entities.AddToRewardsClaimed(claim)<br />

' Save all the changes to the database.<br />

entities.SaveChanges()


4-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> C#]<br />

// Add RewardsClaimed entity to the entity set.<br />

entities.AddToRewardsClaimed(claim);<br />

// Save all the changes to the database.<br />

entities.SaveChanges();<br />

Updating <strong>Data</strong> by Using Stored Procedures<br />

The following code example shows how the update stored procedure for the RewardsClaim table is<br />

defined in your EDM.<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

The following code example shows how to invoke this stored procedure from your application code when<br />

you have modified the points claimed for a RewardsClaimed entity.<br />

[<strong>Visual</strong> Basic]<br />

rewardsClaimed.PointsUsed = 500<br />

' Save the changes to the database<br />

entities.SaveChanges()<br />

[<strong>Visual</strong> C#]<br />

rewardsClaimed.PointsUsed = 500;<br />

// Save the changes to the database<br />

entities.SaveChanges();<br />

Deleting <strong>Data</strong> by Using Stored Procedures<br />

The following code example shows how the delete stored procedure for the RewardsClaim table is<br />

defined in the AdventureWorks EDM.<br />

<br />

<br />

<br />

<br />

<br />

The following code example shows how to invoke this stored procedure from your application code.<br />

[<strong>Visual</strong> Basic]


' Delete the object<br />

entities.DeleteObject(rewardClaimToDelete)<br />

' Save the changes to the database<br />

entities.SaveChanges()<br />

[<strong>Visual</strong> C#]<br />

// Delete the object<br />

entities.DeleteObject(rewardClaimToDelete);<br />

// Save the changes to the database<br />

entities.SaveChanges();<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-19<br />

Question: What are the advantages of using stored procedures to persist changes in entities to the<br />

database?


4-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Demonstration: Adding Stored Procedures to the Model<br />

Key Points<br />

• Import a stored procedure from the AdventureWorks database.<br />

• Map the insert function of the RewardsClaimed entity to the stored procedure.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-04 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Demofiles folder, run Demo.bat to create the stored procedure for this demonstration.<br />

3. Start Microsoft <strong>Visual</strong> <strong>Studio</strong>® 2010.<br />

4. Open the ImportSPDemo solution.<br />

5. Open the AdventureWorksEDM model in the Entity Designer.<br />

6. Run the Update Wizard to add the uspInsertRewardsClaim stored procedure to the model.<br />

7. Map the insert function of the RewardsClaimed entity to the uspInsertRewardsClaim stored<br />

procedure.<br />

8. Map the stored procedure parameters to the appropriate entity properties.<br />

9. Save and close the solution.<br />

10. Close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: What three functions are available for mapping to a stored procedure?


Creating, Updating, and Deleting Entity <strong>Data</strong> 4-21<br />

Lab: Creating, Updating, and Deleting Entity <strong>Data</strong><br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Create, update, and delete entity data by using the default behavior of the EDM.<br />

• Create, update, and delete entity data by using stored procedures embedded in the EDM.<br />

Introduction<br />

In this lab, you will create, modify, and delete entity objects in the ObjectContext object. You will then<br />

persist these changes to the database. You will also use stored procedures embedded in the EDM to<br />

perform the database modifications. Note that data validation will be covered in a later lab.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-04 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


4-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

Adventure Works implements an entity model to support its customer reward program. You have been<br />

asked to extend the data access layer to provide the functionality required by the customer rewards client<br />

application. In this second phase, you must implement the functionality necessary to support creating,<br />

updating, and deleting contacts, rewards, and claims.


Exercise 1: Maintaining Contact and Reward <strong>Data</strong><br />

Scenario<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-23<br />

In this exercise, you will update the data access layer to support creating, updating, and deleting contact<br />

and reward entities. You will use the default behavior of the EDM, leaving the EDM to generate the<br />

required database commands. You must also ensure that you delete any related claims when you delete a<br />

contact.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the AdventureWorks database for the lab.<br />

2. Open the starter project.<br />

3. Add code to add a new contact.<br />

4. Add code to update a contact.<br />

5. Add code to delete a contact.<br />

6. Add unit tests to verify your code.<br />

7. Add code to add a new reward.<br />

8. Add code to update a reward.<br />

9. Add code to delete a reward.<br />

10. Add unit tests to verify your code.<br />

11. Build and test the application.<br />

Task 1: Prepare the AdventureWorks database for the lab<br />

1. Log on to the 10265A-GEN-DEV-04 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run AWReset.bat.<br />

Task 2: Open the starter project<br />

1. Open <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010.<br />

2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\CS\Ex1\Starter or<br />

E:\Labfiles\Lab04\VB\Ex1\Starter folder.<br />

Task 3: Add code to add a new contact<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Add a new Contact in<br />

the task list. This task is located in the AddContact method.<br />

3. In the AddContact method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

Note: The entities variable is a private field in the <strong>Data</strong><strong>Access</strong>Layer class. Your code should perform all<br />

data access operations by using this context object.<br />

b. Encrypt the password in the contact passed as a parameter by calling the EncryptPassword<br />

method.<br />

c. Set the ModifiedDate property of the contact to the current date and time.<br />

d. Assign a globally unique identifier (GUID) to the rowguid property of the contact.<br />

e. Add the contact to the Contacts entityset.


4-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

f. Save the changes to the database and return the new ContactID value.<br />

g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 4: Add code to update a contact<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Modify an existing<br />

Contact in the task list. This task is located in the UpdateContact method.<br />

3. In the UpdateContact method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Get the EntityKey property of the detached contact passed to the method. This detached<br />

contact contains the modified properties.<br />

c. Use the TryGetObjectByKey method to load the correct contact into the context.<br />

d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a<br />

parameter.<br />

e. Save the changes to the database and return true.<br />

f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 5: Add code to delete a contact<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Delete an existing<br />

Contact in the task list. This task is located in the DeleteContact method.<br />

3. In the DeleteContact method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Use the contactID variable passed as a parameter to create the EntityKey object of the contact to<br />

delete.<br />

c. Use the TryGetObjectByKey method to load the correct contact into the context.<br />

d. Delete all of the related RewardsClaimed entities belonging to the contact from the context<br />

e. Delete the contact from the context.<br />

f. Save all of the changes to the database and return true.<br />

g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 6: Add unit tests to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest file by double-clicking the comment TODO: Ex1 - Add a test for<br />

AddContact in the task list. This task is located in the AddContactTest method.


3. In the AddContactTest method, delete the existing code.<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-25<br />

4. Add a unit test to verify the behavior of the AddContact method. Use the CreateTestContact<br />

method to create a contact to add to the database, and use the GetContactById method to retrieve<br />

the contact from the database. Ensure that you remove the contact and release any resources at the<br />

end of the test.<br />

5. Locate the ModifyContactTest method by double-clicking the comment TODO: Ex1 - Add a test<br />

for ModifyContact in the task list. This task is located in the ModifyContactTest method.<br />

6. In the ModifyContactTest method, delete the existing code.<br />

7. Add a unit test to verify the behavior of the ModifyContact method. Use the CreateTestContact<br />

method to create a contact to add to the database, which you can then modify, and use the<br />

GetContactById method to retrieve the contact from the database. Ensure that you remove the<br />

contact and release any resources at the end of the test.<br />

8. Locate the DeleteContactTest method by double-clicking the comment TODO: Ex1 - Add a test for<br />

DeleteContact in the task list. This task is located in the DeleteContactTest method.<br />

9. In the DeleteContactTest method, delete the existing code.<br />

10. Add a unit test to verify the behavior of the DeleteContact method. Use the CreateTestContact<br />

method to create a contact to add to the database, which you can then delete, and use the<br />

GetContactById method to try to retrieve the contact from the database. Ensure that you release any<br />

resources at the end of the test.<br />

11. Save the <strong>Data</strong><strong>Access</strong>LayerTest file.<br />

Task 7: Add code to add a new reward<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Add a new Reward in<br />

the task list. This task is located in the AddReward method.<br />

3. In the AddReward method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

Note: The entities variable is a private field in the <strong>Data</strong><strong>Access</strong>Layer class. Your code should perform all<br />

data access operations by using this context object.<br />

b. Get the next available RewardID value by calling the GetNextRewardID method.<br />

c. Add the reward to the Rewards entity set.<br />

d. Save the changes to the database and return the RewardID value.<br />

e. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 8: Add code to update a reward<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Modify an existing<br />

Reward in the task list. This task is located in the UpdateReward method.


4-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

3. In the UpdateReward method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Get the EntityKey property of the detached contact passed to the method. This detached<br />

contact contains the modified properties.<br />

c. Use the TryGetObjectByKey method to load the correct contact into the context.<br />

d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a<br />

parameter.<br />

e. Save the changes to the database and return true.<br />

f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 9: Add code to delete a reward<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Delete an existing<br />

Reward in the task list. This task is located in the DeleteReward method.<br />

3. In the DeleteReward method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Use the rewardID value passed as a parameter to create the EntityKey object of the reward to<br />

delete.<br />

c. Use the TryGetObjectByKey method to load the correct reward into the context.<br />

d. Delete the reward from the context.<br />

e. Save the change to the database and return true.<br />

f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 10: Add unit tests to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest file by double-clicking the comment TODO: Ex1 - Add a test for<br />

AddReward in the task list. This task is located in the AddRewardTest method.<br />

3. In the AddRewardTest method, delete the existing code.<br />

4. Add a unit test to verify the behavior of the AddReward method. Use the<br />

CreateAdventureWorksReward<strong>Data</strong> and CreateSupermarketReward<strong>Data</strong> methods to create<br />

rewards to add to the database, and use the GetRewardById method to retrieve the rewards from<br />

the database. Ensure that you remove the rewards and release any resources at the end of the test.<br />

5. Locate the ModifyRewardTest method by double-clicking the comment TODO: Ex1 - Add a test<br />

for ModifyReward in the task list. This task is located in the ModifyRewardTest method.<br />

6. In the ModifyRewardTest method, delete the existing code.


Creating, Updating, and Deleting Entity <strong>Data</strong> 4-27<br />

7. Add a unit test to verify the behavior of the ModifyReward method. Use the<br />

CreateSupermarketReward<strong>Data</strong> method to create a reward to add to the database, which you can<br />

then modify, and use the GetRewardById method to retrieve the reward from the database. Ensure<br />

that you remove the reward and release any resources at the end of the test.<br />

8. Locate the DeleteRewardTest method by double-clicking the comment TODO: Ex1 - Add a test for<br />

DeleteReward in the task list. This task is located in the DeleteRewardTest method.<br />

9. In the DeleteRewardTest method, delete the existing code.<br />

10. Add a unit test to verify the behavior of the DeleteReward method. Use the<br />

CreateAdventureWorksReward<strong>Data</strong> method to create a reward to add to the database, which you<br />

can then delete. Ensure that you release any resources at the end of the test.<br />

11. Save the <strong>Data</strong><strong>Access</strong>LayerTest file.<br />

Task 11: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed.<br />

4. Start the application in Debug mode.<br />

5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model<br />

into the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add<br />

and modify Adventure Works reward data.<br />

6. Close the application.<br />

7. Close the solution.


4-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 2: Maintaining RewardsClaim <strong>Data</strong><br />

Scenario<br />

In this exercise, you will update the data access layer to support creating, updating, and deleting claim<br />

entities. You will import stored procedures into your EDM that will be used to perform the changes to the<br />

database. You must also ensure that you adjust the number of points a contact has when you make a<br />

change to the claims associated <strong>with</strong> the contact.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Add data modification stored procedures to your model.<br />

3. Add code to add a new RewardsClaim record.<br />

4. Add code to update a RewardsClaim record.<br />

5. Add code to delete a RewardsClaim record.<br />

6. Add unit tests to verify your code.<br />

7. Build and test the application.<br />

Task 1: Open the starter project<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\CS\Ex2\Starter or<br />

E:\Labfiles\Lab04\VB\Ex2\Starter folder<br />

Task 2: Add data modification stored procedures to your model<br />

1. Open the AdventureWorksEDM model in the Entity Designer.<br />

2. Run the Update Wizard to add the uspInsertRewardsClaim, uspUpdateRewardsClaim, and<br />

uspDeleteRewardsClaim stored procedures to the model.<br />

3. Map the RewardsClaimed entity to the stored procedures in the following table.<br />

Function Stored Procedure<br />

Insert uspInsertRewardsClaim<br />

Update uspUpdateRewardsClaim<br />

Delete uspDeleteRewardsClaim<br />

4. Map the stored procedure parameters to the entity properties as shown in the following table.<br />

Stored Procedure Parameter Property<br />

uspInsertRewardsClaim claimID : int ClaimID : Int32<br />

uspInsertRewardsClaim pointsUsed : int PointsUsed : Int32<br />

uspInsertRewardsClaim rewardID : int RewardID : Int32<br />

uspInsertRewardsClaim contactID : int ContactID : Int32<br />

uspUpdateRewardsClaim claimID : int ClaimID : Int32<br />

uspUpdateRewardsClaim pointsUsed : int PointsUsed : Int32<br />

uspUpdateRewardsClaim rewardID : int RewardID : Int32<br />

uspUpdateRewardsClaim contactID : int ContactID : Int32<br />

uspDeleteRewardsClaim claimID : int ClaimID : Int32


5. Save the AdventureWorksEDM model.<br />

Task 3: Add code to add a new RewardsClaim record<br />

1. Review the task list.<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-29<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex2 - Add a new<br />

RewardsClaimed entity in the task list. This task is located in the CreateRewardsClaim method.<br />

3. In the CreateRewardsClaim method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

Note: The entities variable is a private field in the <strong>Data</strong><strong>Access</strong>Layer class. Your code should perform all<br />

data access operations by using this context object.<br />

b. Set the ClaimID value by calling the GetNextClaimID method.<br />

c. Add the claim to the RewardsClaimed entity set.<br />

d. Decrement the points for the associated contact by the points used for the claim.<br />

e. Save all of the changes to the database and return the new ClaimID value.<br />

f. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 4: Add code to update a RewardsClaim record<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex2 - Update a<br />

RewardsClaimed entity in the task list. This task is located in the UpdateRewardsClaim method.<br />

3. In the UpdateRewardsClaim method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Get the EntityKey property of the detached claim passed to the method. This detached claim<br />

contains the modified properties.<br />

c. Use the TryGetObjectByKey method to load the correct claim into the context.<br />

d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a<br />

parameter.<br />

e. Adjust the points for the associated contact by the difference between the old and the new<br />

points for the claim.<br />

f. Save all of the changes to the database and return true.<br />

g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 5: Add code to delete a RewardsClaim record<br />

1. Review the task list.


4-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex2 - Delete a<br />

RewardsClaimed entity in the task list. This task is located in the DeleteRewardsClaim method.<br />

3. In the DeleteRewardsClaim method, delete the existing code.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null, and if it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Use the claimID value passed as a parameter to create the EntityKey object of the claim to<br />

delete.<br />

c. Use the TryGetObjectByKey method to load the correct claim into the context.<br />

d. Give the points of the claim back to the associated contact.<br />

e. Delete the claim from the context.<br />

f. Save all of the changes to the database and return true.<br />

g. Handle any InvalidOperationException or UpdateException exceptions by throwing a new<br />

DALException exception.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 6: Add unit tests to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest file by double-clicking the comment TODO: Ex2 - Add a test for<br />

CreateRewardsClaim in the task list. This task is located in the CreateRewardsClaimTest method.<br />

3. In the CreateRewardsClaimTest method, delete the existing code.<br />

4. Add a unit test to verify the behavior of the CreateRewardsClaim method. Use the<br />

CreateLocalClaim method to create a claim to add to the database, and use the<br />

GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you remove<br />

the rewards and release any resources at the end of the test.<br />

5. Locate the UpdateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a<br />

test for UpdateRewardsClaim in the task list. This task is located in the UpdateRewardsClaimTest<br />

method.<br />

6. In the UpdateRewardsClaimTest method, delete the existing code.<br />

7. Add a unit test to verify the behavior of the UpdateRewardsClaim method. Use the<br />

CreateLocalClaim method to create a claim to add to the database, which you can then modify, and<br />

use the GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you<br />

remove the claim and release any resources at the end of the test.<br />

8. Locate the DeleteRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a<br />

test for DeleteRewardsClaim in the task list. This task is located in the DeleteRewardsClaimTest<br />

method.<br />

9. In the DeleteRewardsClaimTest method, delete the existing code.<br />

10. Add a unit test to verify the behavior of the DeleteRewardsClaim method. Use the<br />

CreateLocalClaim method to create a claim to add to the database, which you can then delete.<br />

Ensure that you release any resources at the end of the test.<br />

11. Save the <strong>Data</strong><strong>Access</strong>LayerTest file.<br />

Task 7: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.


3. Verify that all of the tests succeed, including the GetContactListTest test.<br />

4. Start the application in Debug mode.<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-31<br />

5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model<br />

into the data grid. Verify that you can add, modify, and delete claims and that the points for the<br />

contact are adjusted correctly.<br />

6. Close the application.<br />

7. Close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


4-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Review<br />

Review Questions<br />

1. Which method do you use to load entity by key into the ObjectContext object?<br />

2. Must you modify your code to work <strong>with</strong> stored procedures in the EDM?<br />

3. Which exceptions should you handle in your data access update code?<br />

4. Does the SaveChanges method support saving changes to multiple entities at the same time?<br />

5. If your entity has an identity column for its primary key in the database, how do you ensure that the<br />

new key value is returned when you call the SaveChanges method?


Module Review and Takeaways<br />

Review Questions<br />

Creating, Updating, and Deleting Entity <strong>Data</strong> 4-33<br />

1. What are the differences between detached and attached entity objects?<br />

2. How do you persist changes to the database after modifying entity objects in the ObjectContext<br />

object?<br />

3. What are the two main tasks you must perform if you want your EDM to use stored procedures for<br />

data modifications?<br />

Best Practices Related to Modifying Entities <strong>with</strong> the Entity Framework<br />

Supplement or modify the following best practices for your own work situations:<br />

• When you query data that may be modified, be clear about whether the entities returned from the<br />

query will be attached or detached.<br />

• Any data modification should handle UpdateException and InvalidOperationException exceptions.<br />

• For performance reasons, you should consider batching changes together where possible.<br />

• You should consider using stored procedures to modify your data whenever possible.<br />

• You should create unit tests for all of the public methods in your data access layer.


4-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010


Module 5<br />

Handling Multi-User Scenarios by Using Object Services 5-1<br />

Handling Multi-User Scenarios by Using Object Services<br />

Contents:<br />

Lesson 1: Handling Concurrency in the Entity Framework 5-3<br />

Lesson 2: Transactional Support in the Entity Framework 5-14<br />

Lab: Handling Multi-User Scenarios by Using Object Services 5-24


5-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

<strong>Data</strong> applications that support multiple users can face concurrency issues when different users access the<br />

data simultaneously. The Entity Framework provides a concurrency model and transactional capabilities to<br />

help you overcome these issues.<br />

This module introduces the concurrency model and describes how the Entity Framework can make use of<br />

transactions to ensure data integrity.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Describe the optimistic concurrency model that the Entity Framework uses.<br />

• Manage transactions in applications that use the Entity Framework.


Handling Multi-User Scenarios by Using Object Services 5-3<br />

Lesson 1<br />

Handling Concurrency in the Entity Framework<br />

When two or more users modify the same data simultaneously, concurrency conflicts occur. You must<br />

handle these conflicts and allow the application and users to recover gracefully.<br />

This lesson explains how the Entity Framework detects concurrency conflicts. It also describes how the<br />

Entity Framework implements an optimistic concurrency model and explains how you can implement<br />

conflict resolution in your application code.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the problem associated <strong>with</strong> concurrent access to data.<br />

• Describe how the Entity Framework detects concurrency conflicts in a database.<br />

• Configure your Entity <strong>Data</strong> Model (EDM) to detect concurrency conflicts.<br />

• Handle concurrency conflicts in your application code.<br />

• Create unit tests to verify your concurrency-resolution logic.


5-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Concurrent <strong>Data</strong> <strong>Access</strong><br />

Key Points<br />

Any multi-user application that works <strong>with</strong> stored data must have a strategy to deal <strong>with</strong> concurrent<br />

access to its data. The Entity Framework has a clear set of rules that it follows to detect possible conflicts.<br />

You can summarize the process that the Entity Framework uses to modify data as follows:<br />

1. <strong>Data</strong> that may be modified by a user is loaded into the cache maintained by the ObjectContext<br />

object, either by running a query or by loading records by key.<br />

2. Your application code can then modify data in the cache. The ObjectStateManager object tracks<br />

these changes for the ObjectContext object.<br />

3. The changes made to data in the cache are persisted to the database when your application code<br />

calls the SaveChanges method on the ObjectContext object. It is at this point that the Entity<br />

Framework may encounter a concurrency conflict, because during the time that an entry was in the<br />

cache, another user may have modified it.<br />

For example, User1 loads the contact record for the contact called Ward into the cache. At the same time,<br />

User2 also loads the record for the contact called Ward into the cache for his or her application. At this<br />

point, User1 changes the value in the CurrentPoints property of the contact from 1,000 to 800 and calls<br />

the SaveChanges method, which writes the record back to the database <strong>with</strong> the new value in the<br />

CurrentPoints property of 800. However, in User2's cache, the value in the CurrentPoints property is still<br />

1,000. When User2 takes away 100 points, leaving 900, and then calls the SaveChanges method, this will<br />

overwrite the change made by User1. The Entity Framework must have some way of detecting that<br />

another user has modified the record.


How the Entity Framework Detects Concurrency Conflicts<br />

Key Points<br />

Handling Multi-User Scenarios by Using Object Services 5-5<br />

The Entity Framework uses an optimistic concurrency model, which means that it does not hold any locks<br />

in the database. When the Entity Framework saves changes to the database, it checks to see whether<br />

anyone has modified the record since it retrieved the record and placed it in the cache. If the Entity<br />

Framework detects a change, it throws an OptimisticConcurrencyException exception.<br />

By default, the Entity Framework saves changes <strong>with</strong>out checking for concurrency conflicts. There are two<br />

ways that you can make the Entity Framework check for concurrency conflicts when it saves changes:<br />

1. In the EDM, you can set the ConcurrencyMode property of an entity property to Fixed. This tells the<br />

Entity Framework to check whether the value of this field has changed whenever it tries to save or<br />

delete a record. For example, if the StoreContact entity has a ModifiedDate column that is updated<br />

<strong>with</strong> the current date and time whenever the record is saved, you should set the ConcurrencyMode<br />

property value to Fixed for this entity property.<br />

2. If the EDM uses a stored procedure to make a change to the database, the Entity Framework will<br />

throw an OptimisticConcurrencyException exception if the stored procedure reports that it<br />

changed zero rows. You must design your stored procedures so that they check whether another<br />

process has modified the record in the database since the ObjectContext object originally retrieved<br />

the record.<br />

The following code example shows an example stored procedure that will enable concurrency checking<br />

when you save a StoreContact entity to the database. You can see that the stored procedure has two<br />

ModifiedDate parameters: one contains the original date and the other the changed date. The logic in the<br />

stored procedure checks to see whether the ModifiedDate field of the record in the database has<br />

changed since the ObjectContext object originally retrieved it by comparing the value of the record's<br />

ModifiedDate field in the database <strong>with</strong> the value of the modifiedDateOriginal parameter.<br />

CREATE PROCEDURE [Sales].[uspUpdateStoreContact]<br />

@customerID [int],<br />

@contactID [int],


5-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

@contactTypeID [int],<br />

@modifiedDateOriginal [datetime],<br />

@modifiedDateChanged [datetime] AS<br />

BEGIN<br />

UPDATE [Sales].[StoreContact]<br />

SET [CustomerID] = @customerID<br />

,[ContactID] = @contactID<br />

,[ContactTypeID] = @contactTypeID<br />

,[ModifiedDate] = GETDATE()<br />

WHERE [CustomerID] = @customerID and<br />

[ContactID] = @contactID and<br />

[ModifiedDate] = @modifiedDateOriginal<br />

Question: What advantages do stored procedures offer over the use of the ConcurrencyMode property<br />

as a mechanism for detecting concurrency conflicts?<br />

Additional Reading<br />

For more information about stored procedure support in the Entity Framework, see the Stored Procedure<br />

Support (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194041.<br />

For more information about the RowsAffectedParameter parameter, see the RowsAffectedParameter<br />

(EntityTypeMapping) page at http://go.microsoft.com/fwlink/?LinkID=194042.


Demonstration: Setting Concurrency Options in the Model<br />

Key Points<br />

• Configure concurrency options in the AdventureWorks EDM.<br />

Handling Multi-User Scenarios by Using Object Services 5-7<br />

• Set the ConcurrencyMode property of the ModifiedDate entity property of the SalesTerritory<br />

entity.<br />

• Use a stored procedure to detect concurrency issues on the StoreContact entity.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-05 virtual machine as Student <strong>with</strong> the password Pa$$w0rd<br />

2. In the E:\Demofiles folder, run Demo.bat to create the stored procedure for this demonstration.<br />

3. Open <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010.<br />

4. In <strong>Visual</strong> <strong>Studio</strong> 2010, open the ConcurrencyDemo.sln solution in the<br />

E:\Demofiles\Mod05\Demo1\Starter folder.<br />

5. Open the AdventureWorksEDM model in the Entity Designer.<br />

6. Set the Concurrency Mode property to Fixed for the ModifiedDate entity property of the<br />

SalesTerritory entity.<br />

7. Use the Model Browser to update the model from the database and import the<br />

uspUpdateStoreContact stored procedure.<br />

8. Map the update function of the StoreContact entity to the uspUpdateStoreContact stored<br />

procedure.<br />

9. Save AdventureWorksEDM.edmx.<br />

Question: What column types are suitable candidates for setting the ConcurrencyMode property to<br />

Fixed?


5-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Handling Optimistic Concurrency Exceptions<br />

Key Points<br />

The Entity Framework uses an optimistic concurrency model to detect concurrency conflicts when the<br />

ObjectContext object attempts to save modified data back to the database. If the Entity Framework<br />

detects a concurrency conflict, it throws an OptimisticConcurrencyException exception for you to trap<br />

and handle in your code. This topic describes a strategy you can use to handle this exception.<br />

When the Entity Framework detects a concurrency conflict, you must decide how to resolve it. There are<br />

three basic approaches, and which one you choose depends on the specific requirements of your<br />

application. The three approaches are as follows:<br />

1. Discard the changes.<br />

2. Accept the changes, and overwrite the data in the database.<br />

3. Notify the user that the Entity Framework cannot save the change because someone else has<br />

modified the data, and ask the user whether to overwrite the data in the database.<br />

The ObjectContext class has a Refresh method that supports implementation of any of these<br />

approaches. You can use the Refresh method to put the cache in the ObjectContext object into a<br />

suitable state before retrying the SaveChanges method.<br />

The Refresh method refreshes the data in the cache for either a single entity object or a collection of<br />

entity objects. You control how the Refresh method refreshes the cache by using the RefreshMode<br />

parameter. The following table shows the possible values of the RefreshMode parameter.<br />

RefreshMode name Description<br />

ClientWins Property changes made to objects in the cache are not replaced <strong>with</strong> values from<br />

the data source. On the next call to the SaveChanges method, these changes are<br />

sent to the data source.<br />

StoreWins Property changes made to objects in the cache are replaced <strong>with</strong> values from the


RefreshMode name Description<br />

data source.<br />

Handling Multi-User Scenarios by Using Object Services 5-9<br />

The following code example shows how to handle a concurrency conflict detected by the Entity<br />

Framework when it attempts to save a SalesOrderHeader record. In this example, the user's change<br />

overwrites the current values in the database.<br />

[<strong>Visual</strong> Basic]<br />

Using context As New AdventureWorksEntities()<br />

Try<br />

Dim orders As ObjectQuery(Of SalesOrderHeader) =<br />

context.SalesOrderHeaders.Where(<br />

"it.CreditCardApprovalCode IS NULL").Top("100")<br />

' Reset the order status to 4 = Rejected.<br />

order.Status = 4<br />

' Try to save changes, which may cause a conflict.<br />

context.SaveChanges()<br />

Catch ex As OptimisticConcurrencyException<br />

End Try<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

' Resolve the concurrency conflict by refreshing the<br />

' ObjectContext object before re-saving changes.<br />

context.Refresh(RefreshMode.ClientWins, orders)<br />

' Save changes.<br />

context.SaveChanges()<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

try<br />

{<br />

ObjectQuery orders =<br />

context.SalesOrderHeaders.Where(<br />

"it.CreditCardApprovalCode IS NULL").Top("100");<br />

// Reset the order status to 4 = Rejected.<br />

order.Status = 4;<br />

// Try to save changes, which may cause a conflict.<br />

context.SaveChanges();<br />

}<br />

catch (OptimisticConcurrencyException)<br />

{<br />

// Resolve the concurrency conflict by refreshing the<br />

// ObjectContext object before re-saving changes.<br />

context.Refresh(RefreshMode.ClientWins, orders);


5-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

}<br />

// Save changes.<br />

context.SaveChanges();<br />

This code example shows only how to handle concurrency conflicts. How to perform additional data<br />

validation when you save changes to the database will be explained in a later module.<br />

Question: How is it possible to implement a hybrid resolution to a concurrency conflict? For example, you<br />

save a SalesOrderHeader entity, and the Entity Framework detects a conflict. You want to use all of the<br />

property values from the database except for the value of the Status property, which you do want to<br />

change. How do you achieve this?


Creating Unit Tests to Verify Concurrency Behavior<br />

Key Points<br />

Handling Multi-User Scenarios by Using Object Services 5-11<br />

Testing how your application handles concurrency conflicts can be challenging, especially because the<br />

effects of concurrency conflicts can sometimes be quite difficult to identify. You should create unit tests to<br />

verify that your application behaves correctly in as many different concurrency scenarios as possible. You<br />

should try to identify all of the scenarios and their expected outcomes before you write your tests.<br />

For example, in the AdventureWorks EDM, it is possible that a user may try to save changes to a contact<br />

that another user has already modified.<br />

The following code example shows how the UpdateContact method handles an<br />

OptimisticConcurrencyException exception by refreshing the context <strong>with</strong> data from the database.<br />

[<strong>Visual</strong> Basic]<br />

Catch ex As OptimisticConcurrencyException<br />

End Try<br />

' The contact may have been modified, so<br />

' get the latest version from the database.<br />

entities.Refresh(RefreshMode.StoreWins, contactToModify)<br />

' Try to save all of the changes again.<br />

entities.SaveChanges()<br />

' Ensure that the correct datetime is in the context.<br />

entities.Refresh(RefreshMode.StoreWins, contactToModify)<br />

Return True<br />

[<strong>Visual</strong> C#]


5-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

catch (OptimisticConcurrencyException)<br />

{<br />

}<br />

// The contact may have been modified, so<br />

// get the latest version from the database.<br />

entities.Refresh(RefreshMode.StoreWins, contactToModify);<br />

// Try to save all of the changes again.<br />

entities.SaveChanges();<br />

// Ensure that the correct datetime is in the context.<br />

entities.Refresh(RefreshMode.StoreWins, contactToModify);<br />

return true;<br />

The following code example shows a strategy for testing this behavior. It uses two instances of the data<br />

access layer component to simulate two different users. The first user creates a new contact, the second<br />

user modifies the contact, and when the first user also tries to modify the contact, the Entity Framework<br />

detects a concurrency conflict.<br />

[<strong>Visual</strong> Basic]<br />

_<br />

Public Sub UpdateContactConcurrencyTest()<br />

' Create two instances of the <strong>Data</strong><strong>Access</strong>Layer<br />

' to represent two users.<br />

Dim user1 As New <strong>Data</strong><strong>Access</strong>Layer()<br />

Dim user2 As New <strong>Data</strong><strong>Access</strong>Layer()<br />

' User1 creates a contact—this loads the contact<br />

' into the context.<br />

Dim contactID As Integer = user1.AddContact(CreateTestContact())<br />

' User2 modifies the contact.<br />

Dim user2ModifiedContact As Contact = CreateTestContact()<br />

user2ModifiedContact.ContactID = contactID<br />

user2ModifiedContact.CurrentPoints = 2000<br />

user2.UpdateContact(user2ModifiedContact)<br />

' User1 updates the contact—getting a conflict.<br />

Dim user1ModifiedContact As Contact = CreateTestContact()<br />

user1ModifiedContact.CurrentPoints = 3000<br />

user1ModifiedContact.ContactID = contactID<br />

user1.UpdateContact(user1ModifiedContact)<br />

' Get the contact from the database.<br />

Dim actualContact As Contact = GetContactById(contactID)<br />

' Perform the test.<br />

Assert.AreEqual(2000, actualContact.CurrentPoints)


End Sub<br />

' Tidy up—delete the contact.<br />

user1.DeleteContact(contactID)<br />

user1.Dispose()<br />

user2.Dispose()<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void UpdateContactConcurrencyTest()<br />

{<br />

// Create two instances of the <strong>Data</strong><strong>Access</strong>Layer<br />

// to represent two users.<br />

<strong>Data</strong><strong>Access</strong>Layer user1 = new <strong>Data</strong><strong>Access</strong>Layer();<br />

<strong>Data</strong><strong>Access</strong>Layer user2 = new <strong>Data</strong><strong>Access</strong>Layer();<br />

}<br />

// User1 creates a contact—this loads the contact<br />

// into the context.<br />

int contactID = user1.AddContact(CreateTestContact());<br />

// User2 modifies the contact.<br />

Contact user2ModifiedContact = CreateTestContact();<br />

user2ModifiedContact.ContactID = contactID;<br />

user2ModifiedContact.CurrentPoints = 2000;<br />

user2.UpdateContact(user2ModifiedContact);<br />

// User1 updates the contact—getting a conflict.<br />

Contact user1ModifiedContact = CreateTestContact();<br />

user1ModifiedContact.CurrentPoints = 3000;<br />

user1ModifiedContact.ContactID = contactID;<br />

user1.UpdateContact(user1ModifiedContact);<br />

// Get the contact from the database.<br />

Contact actualContact = GetContactById(contactID);<br />

// Perform the test.<br />

Assert.AreEqual(2000, actualContact.CurrentPoints);<br />

// Tidy up—delete the contact.<br />

user1.DeleteContact(contactID);<br />

user1.Dispose();<br />

user2.Dispose();<br />

Handling Multi-User Scenarios by Using Object Services 5-13<br />

Question: What other types of testing can you carry out to verify the behavior of your concurrency<br />

conflict-resolution code?


5-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

Transactional Support in the Entity Framework<br />

When an application needs to update more than one table in a database as one unit of work, it is<br />

imperative that either all of the tables are updated or none are updated. The Entity Framework provides<br />

transactions that enable you to ensure that this occurs.<br />

This lesson explains how you can use transactions <strong>with</strong> the Entity Framework to help ensure the integrity<br />

of your data. The Entity Framework can make use of transactions when it saves data modifications to the<br />

database.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe how the Entity Framework supports the use of transactions.<br />

• Manage transactions in your application code.<br />

• Retry transactions when they fail.


Transactions and the Entity Framework<br />

Key Points<br />

Handling Multi-User Scenarios by Using Object Services 5-15<br />

<strong>Data</strong>bases use transactions to ensure the consistency of the data stored in the database. A transaction is a<br />

unit of work that consists of one or more database commands that must complete as a unit.<br />

The following table describes three types of transaction.<br />

Transaction type Description<br />

Implicit You do not need to write any code to use an implicit transaction. For example, an<br />

UPDATE statement in a database may be automatically executed in an implicit<br />

transaction to guarantee the integrity of the database.<br />

Explicit You mark the beginning and end of the transaction in your code, and you control<br />

when a transaction should be rolled back. You use explicit transactions to<br />

implement specific data integrity rules for your application. You can write explicit<br />

transactions in stored procedures or in your application code.<br />

Distributed A distributed transaction is similar to an explicit transaction, except that the<br />

transaction spans two or more data sources. These data sources may include<br />

databases, message queues, or some other component that understands commit<br />

and rollback semantics. Although you code the transaction in your application<br />

code, a separate coordinator component manages the transaction across the<br />

various data sources.<br />

The Entity Framework can make use of transactions when the ObjectContext object interacts <strong>with</strong> the<br />

underlying database.<br />

Note: The data sources that are used in a distributed transaction do not have to be databases. For<br />

example, Message Queuing (also known as MSMQ) queues can participate in a transaction.


5-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Note: The Entity Framework uses transactions only during operations against the data source. Changes<br />

made to entity objects in the context are not transacted, and any changes are visible outside the context.<br />

Question: How many operations should you include in a transaction?<br />

Additional Reading<br />

For more information about the atomic, consistent, isolated, and durable (ACID) properties of<br />

transactions, see the ACID properties page at http://go.microsoft.com/fwlink/?LinkID=194043.


Managing Transactions in the Entity Framework<br />

Key Points<br />

Handling Multi-User Scenarios by Using Object Services 5-17<br />

When you call the SaveChanges method on your ObjectContext object, the Entity Framework creates a<br />

new transaction for the operations it performs against the database. If your context contains two new<br />

contact entities, three updated claims, and one claim marked for deletion, all six operations will be part of<br />

an implicit transaction against the database. If any of the operations fail, the Entity Framework will roll<br />

back the transaction. The Entity Framework does not make any changes to the entity objects in the<br />

context until the transaction has committed; this ensures that the state of the entity objects in the context<br />

is consistent <strong>with</strong> the state of the data in the database.<br />

If your application logic requires an explicit transaction, you should use the TransactionScope class. In<br />

the AdventureWorks EDM, you do not know the contactID property of a new contact until you save the<br />

Contact entity to the database. This is because the Contact table uses an identity column for the<br />

contactID column. Your application may need to create both a Contact entity and a Store Contact entity<br />

at the same time, and you want to ensure that you do not create a Store Contact record <strong>with</strong>out its<br />

matching Contact record. The following code example shows how you can use an explicit transaction to<br />

achieve this. Notice that if an error occurs, the TransactionScope object handles the rollback for you, and<br />

the calls to the SaveChanges method automatically participate in the transaction defined by the<br />

TransactionScope object.<br />

[<strong>Visual</strong> Basic]<br />

Public Function CreateNewStoreContact(ByVal contact As Contact, ByVal storecontact As<br />

StoreContact) As Boolean<br />

' Check you have an ObjectContext object.<br />

If entities Is Nothing Then entities = New AdventureWorksEntities()<br />

Using scope As New TransactionScope()<br />

Try


5-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

' Add the contact.<br />

entities.AddToContacts(contact)<br />

entities.SaveChanges()<br />

' Now you know the ContactID.<br />

storecontact.ContactID = contact.ContactID<br />

entities.AddToStoreContacts(storecontact)<br />

entities.SaveChanges()<br />

scope.Complete()<br />

Return True<br />

Catch ex As Exception<br />

End Try<br />

End Using<br />

End Function<br />

[<strong>Visual</strong> C#]<br />

Console.WriteLine("Transaction Rolled Back {0}",<br />

ex.Message)<br />

Return False<br />

public bool CreateNewStoreContact(Contact contact, StoreContact storecontact)<br />

{<br />

// Check you have an ObjectContext object.<br />

if (entities == null) entities = new AdventureWorksEntities();<br />

using (TransactionScope scope = new TransactionScope())<br />

{<br />

try<br />

{<br />

}<br />

// Add the contact.<br />

entities.AddToContacts(contact);<br />

entities.SaveChanges();<br />

// Now you know the ContactID.<br />

storecontact.ContactID = contact.ContactID;<br />

entities.AddToStoreContacts(storecontact);<br />

entities.SaveChanges();<br />

scope.Complete();<br />

return true;<br />

catch (Exception ex)<br />

{<br />

Console.WriteLine("Transaction Rolled Back {0}",<br />

ex.Message);<br />

return false;


}<br />

}<br />

}<br />

Handling Multi-User Scenarios by Using Object Services 5-19<br />

You can extend this approach to implement a distributed transaction. In the following code example, the<br />

transaction is now a distributed transaction that includes placing a message in a message queue.<br />

[<strong>Visual</strong> Basic]<br />

Public Function CreateNewStoreContact(ByVal contact As Contact, ByVal storecontact As<br />

StoreContact) As Boolean<br />

' Check you have an ObjectContext object.<br />

If entities Is Nothing Then entities = New AdventureWorksEntities()<br />

Using scope As New TransactionScope()<br />

Try<br />

' Add the contact.<br />

entities.AddToContacts(contact)<br />

entities.SaveChanges()<br />

' Now you know the contact ID.<br />

storecontact.ContactID = contact.ContactID<br />

entities.AddToStoreContacts(storecontact)<br />

entities.SaveChanges()<br />

If Not MessageQueue.Exists("NotifyQueue") Then<br />

MessageQueue.Create("NotifyQueue")<br />

End If<br />

Using q As New MessageQueue("NotifyQueue")<br />

Dim msg As System.Messaging.Message =<br />

New System.Messaging.Message(String.Format(<br />

"" &<br />

"" &<br />

"", contact.ContactID,<br />

storecontact.CustomerID))<br />

q.Send(msg)<br />

End Using<br />

scope.Complete()<br />

Return True<br />

Catch ex As Exception<br />

End Try<br />

End Function<br />

Console.WriteLine("Transaction Rolled Back {0}",<br />

ex.Message)<br />

Return False<br />

[<strong>Visual</strong> C#]<br />

public bool CreateNewStoreContact(Contact contact, StoreContact storecontact)<br />

{<br />

// Check you have an ObjectContext object.


5-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

if (entities == null) entities = new AdventureWorksEntities();<br />

using (TransactionScope scope = new TransactionScope())<br />

{<br />

try<br />

{<br />

// Add the contact.<br />

entities.AddToContacts(contact);<br />

entities.SaveChanges();<br />

}<br />

// Now you know the contact ID.<br />

storecontact.ContactID = contact.ContactID;<br />

entities.AddToStoreContacts(storecontact);<br />

entities.SaveChanges();<br />

if (!MessageQueue.Exists("NotifyQueue"))<br />

{<br />

MessageQueue.Create("NotifyQueue");<br />

}<br />

using (MessageQueue q = new MessageQueue("NotifyQueue"))<br />

{<br />

System.Messaging.Message msg =<br />

new System.Messaging.Message(String.Format(<br />

"" +<br />

"" +<br />

"", contact.ContactID,<br />

storecontact.CustomerID));<br />

}<br />

q.Send(msg);<br />

scope.Complete();<br />

return true;<br />

}<br />

catch (Exception ex)<br />

{<br />

Console.WriteLine("Transaction Rolled Back {0}",<br />

ex.Message);<br />

return false;<br />

}<br />

Question: Can you nest TransactionScope objects?<br />

Additional Reading<br />

For more information about writing transaction applications, see the Implementing an Implicit Transaction<br />

using Transaction Scope page at http://go.microsoft.com/fwlink/?LinkID=194044.<br />

For more information about distributed transactions, see the Transaction Management Escalation page at<br />

http://go.microsoft.com/fwlink/?LinkID=194045.


Retrying Failed Transactions<br />

Key Points<br />

Handling Multi-User Scenarios by Using Object Services 5-21<br />

You can implement retry logic if an operation fails inside a transaction. The following code example shows<br />

how to retry the save operation. Notice that you call the SaveChanges method <strong>with</strong> the<br />

SaveOptions.None parameter. This means that the context is not updated and you can retry the save. The<br />

call to the AcceptAllChanges method updates the context after the transaction has completed<br />

successfully.<br />

[<strong>Visual</strong> Basic]<br />

Public Function CreateNewStoreContact(ByVal contact As Contact, ByVal storecontact As<br />

StoreContact) As Boolean<br />

' Check you have an ObjectContext object.<br />

If entities Is Nothing Then entities = New AdventureWorksEntities()<br />

Dim success As Boolean = False<br />

For i As Integer = 0 To 2<br />

Using scope = New TransactionScope()<br />

Try<br />

' Add the contact.<br />

entities.AddToContacts(contact)<br />

' Do not accept the changes in the context.<br />

' in case you have to retry<br />

entities.SaveChanges(SaveOptions.None)<br />

' Now you know the contact ID.<br />

storecontact.ContactID = contact.ContactID<br />

entities.AddToStoreContacts(storecontact)<br />

' Do not accept the changes in the context,<br />

' in case you have to retry.<br />

entities.SaveChanges(SaveOptions.None)


5-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Next i<br />

scope.Complete()<br />

success = True<br />

break()<br />

Catch ex As Exception<br />

End Try<br />

End Using<br />

If (success) Then<br />

Else<br />

End If<br />

End Function<br />

Console.WriteLine("Retrying transaction")<br />

entities.AcceptAllChanges()<br />

Return True<br />

Console.WriteLine("Transaction Failed")<br />

Return False<br />

[<strong>Visual</strong> C#]<br />

public bool CreateNewStoreContact(Contact contact, StoreContact storecontact)<br />

{<br />

// Check you have an ObjectContext object.<br />

if (entities == null) entities = new AdventureWorksEntities();<br />

bool success = false;<br />

for (int i = 0; i


}<br />

}<br />

}<br />

}<br />

success = true;<br />

break;<br />

catch (Exception)<br />

{<br />

Console.WriteLine("Retrying transaction");<br />

}<br />

if (success)<br />

{<br />

entities.AcceptAllChanges();<br />

return true;<br />

}<br />

else<br />

{<br />

Console.WriteLine("Transaction Failed");<br />

return false;<br />

}<br />

Question: Does the ApplyChanges method make any changes to the database?<br />

Handling Multi-User Scenarios by Using Object Services 5-23


5-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab: Handling Multi-User Scenarios by Using Object<br />

Services<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Manage concurrency in a multi-user application that uses the Entity Framework.<br />

• Create and manage transactions in an application that uses the Entity Framework.<br />

Introduction<br />

In this lab, you will update your EDM to define how the Entity Framework detects concurrency conflicts.<br />

You will then add code to your data access layer that resolves any concurrency conflicts. You will also<br />

define a transaction that guarantees the integrity of your data when several updates take place together.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-05 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


Lab Scenario<br />

Handling Multi-User Scenarios by Using Object Services 5-25<br />

Adventure Works implements an EDM to support its customer reward program. You have been asked to<br />

modify the data access layer to ensure that rewards claim data is correctly saved to the database in<br />

circumstances where multiple users edit the same records simultaneously.<br />

You have also been asked to save copies of all updated and inserted rewards claim data to an archive<br />

table to provide an audit trail of the data modifications performed by users.


5-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 1: Handling Concurrency of Rewards Claimed <strong>Data</strong><br />

Scenario<br />

Users have reported that sometimes a contact's reward points do not update correctly when they modify<br />

claims. You have been asked to implement concurrency checking in the data access layer to prevent these<br />

errors. You should create unit tests to verify your solution.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the AdventureWorks database for the lab.<br />

2. Open the starter project for this exercise.<br />

3. Set the concurrency behavior of the Contact and RewardsClaimed entities.<br />

4. Add code to set the ModifiedDate property of the contact.<br />

5. Add code to handle OptimisticConcurrencyException exceptions in the CreateRewardsClaim<br />

method.<br />

6. Add code to handle OptimisticConcurrencyException exceptions in the UpdateRewardsClaim<br />

method.<br />

7. Add code to handle OptimisticConcurrencyException exceptions in the DeleteRewardsClaim<br />

method.<br />

8. Add unit tests to verify your code.<br />

9. Build and test the application.<br />

Task 1: Prepare the AdventureWorks database for the lab<br />

1. Log on to the 10265A-GEN-DEV-05 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run AWReset.bat.<br />

Task 2: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex1\Starter or<br />

E:\Labfiles\Lab05\CS\Ex1\Starter folder.<br />

Task 3: Set the concurrency behavior of the Contact and RewardsClaimed entities<br />

1. Open the AdventureWorksEDM model in the Entity Designer.<br />

2. In the AdventureWorksEDM model, set the Concurrency Mode property of the ModifiedDate<br />

property of the Contact entity to Fixed.<br />

3. In the AdventureWorksEDM model, set the Concurrency Mode property of the TimeStamp property<br />

of the RewardsClaimed entity to Fixed.<br />

4. Save the AdventureWorks EDM model.<br />

Task 4: Add code to set the ModifiedDate property of the contact<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Update the contact's<br />

Modified Date property item in the task list. This task is located in the UpdateContact method.<br />

3. Immediately after the comment, add code that sets the ModifiedDate property of the contact being<br />

saved to the current date and time.<br />

4. Save the <strong>Data</strong><strong>Access</strong>Layer file.


Handling Multi-User Scenarios by Using Object Services 5-27<br />

Task 5: Add code to handle OptimisticConcurrencyException exceptions in the<br />

CreateRewardsClaim method<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Handle the<br />

OptimisticConcurrencyException in CreateRewardsClaim item in the task list. This task is located<br />

in the CreateRewardsClaim method.<br />

3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException<br />

exception. In the catch block, add code that performs the following tasks:<br />

a. Refresh the contact <strong>with</strong> the current values from the database.<br />

b. Deduct the points used for the claim from the contact.<br />

c. Set the ModifiedDate property of the contact to the current date and time.<br />

d. Save the changes to the database.<br />

e. Refresh the contact and the claim <strong>with</strong> the current values from the database.<br />

f. Return the new claimID value.<br />

4. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 6: Add code to handle OptimisticConcurrencyException exceptions in the<br />

UpdateRewardsClaim method<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Handle the<br />

OptimisticConcurrencyException in UpdateRewardsClaim item in the task list. This task is located<br />

in the UpdateRewardsClaim method.<br />

3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException<br />

exception. In the catch block, add code that performs the following tasks:<br />

a. Refresh the contact <strong>with</strong> the current values from the database.<br />

b. Refresh the claim, keeping any changes made in the context.<br />

c. Deduct the points used for the original claim from the contact, and add the points used for the<br />

modified claim to the contact.<br />

d. Set the ModifiedDate property of the contact to the current date and time.<br />

e. Save the changes to the database.<br />

f. Refresh the contact and the claim <strong>with</strong> the current values from the database.<br />

g. Return true.<br />

4. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 7: Add code to handle OptimisticConcurrencyException exceptions in the<br />

DeleteRewardsClaim method<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex1 - Handle the<br />

OptimisticConcurrencyException in DeleteRewardsClaim item in the task list. This task is located<br />

in the DeleteRewardsClaim method.<br />

3. Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException<br />

exception. In the catch block, add code that performs the following tasks:<br />

a. Refresh the contact <strong>with</strong> the current values from the database.<br />

b. Refresh the claim, keeping any changes made in the context.<br />

c. Add the points used for the deleted claim to the contact.


5-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

d. Set the ModifiedDate property of the contact to the current date and time.<br />

e. Save the changes to the database.<br />

f. Refresh the contact <strong>with</strong> the current values from the database.<br />

g. Return true.<br />

4. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 8: Add unit tests to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest file by double-clicking the comment TODO: Ex1 - Create a test to<br />

verify that CreateRewardsClaim handles concurrency issues item in the task list. This task is<br />

located in the CreateRewardsClaimConcurrencyTest method.<br />

3. Using the comments in the CreateRewardsClaimConcurrencyTest method for guidance, write code<br />

that verifies the behavior of the CreateRewardsClaim method when two users modify the same<br />

contact while they are adding new claims to the database.<br />

4. Locate the UpdateRewardsClaimConcurrencyTest method by double-clicking the comment TODO:<br />

Ex1 - Create a test to verify that UpdateRewardsClaim handles concurrency issues item in the<br />

task list. This task is located in the UpdateRewardsClaimConcurrencyTest method.<br />

5. Using the comments in the UpdateRewardsClaimConcurrencyTest method for guidance, write<br />

code that verifies the behavior of the UpdateRewardsClaim method when two users modify the<br />

same contact while they are updating claims to the database.<br />

6. Locate the DeleteRewardsClaimConcurrencyTest method by double-clicking the comment TODO:<br />

Ex1 - Create a test to verify that DeleteRewardsClaim handles concurrency issues item in the<br />

task list. This task is located in the DeleteRewardsClaimConcurrencyTest method.<br />

7. Using the comments in the DeleteRewardsClaimConcurrencyTest method for guidance, write code<br />

that verifies the behavior of the DeleteRewardsClaim method when two users modify the same<br />

contact while they are inserting and deleting claims in the database.<br />

8. Save the <strong>Data</strong><strong>Access</strong>LayerTest file.<br />

Task 9: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed, including the CreateRewardsClaimConcurrencyTest,<br />

UpdateRewardsClaimConcurrencyTest, and DeleteRewardsClaimConcurrencyTest tests.<br />

4. Close the solution.


Handling Multi-User Scenarios by Using Object Services 5-29<br />

Exercise 2: Updating the RewardsClaimed and ArchivedRewardsClaimed<br />

Information by Using a Transaction<br />

Scenario<br />

You have been asked to modify the data access layer to save a copy of every new and updated reward<br />

claimed to an archive table in a separate database as an audit trail. You must ensure that you create the<br />

archived record only if the change to the reward claimed record succeeds. You should create a unit test to<br />

verify that your transaction works correctly.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Create the AdventureWorksArchivedEDM Entity <strong>Data</strong> Model.<br />

3. Modify the CreateRewardsClaim method to save a copy of the claim to the archive table as part of a<br />

transaction.<br />

4. Modify the UpdateRewardsClaim method to save a copy of the claim to the archive table as part of<br />

a transaction.<br />

5. Modify the unit tests to verify your code.<br />

6. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex2\Starter or<br />

E:\Labfiles\Lab05\CS\Ex2\Starter folder.<br />

Task 2: Create the AdventureWorksArchivedEDM Entity <strong>Data</strong> Model<br />

1. Add a new ADO.NET Entity <strong>Data</strong> Model to the DAL project. Generate the data model from the<br />

AdventureWorks Microsoft SQL Server® database and create entities for the<br />

ArchivedRewardsClaimed table.<br />

2. Copy the AdventureWorksArchivedEntities connection string from the App.Config file in the DAL<br />

project to the App.Config file in the DALTest project.<br />

Task 3: Modify the CreateRewardsClaim method to save a copy of the claim to the<br />

archive table as part of a transaction<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex2 - In<br />

CreateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an<br />

ArchivedRewardsClaim item in the task list. This task is located in the CreateRewardsClaim<br />

method.<br />

3. Place the call to the SaveChanges method inside a new TransactionScope code block. In the<br />

TransactionScope code block, after the call to the SaveChanges method, add code to perform the<br />

following tasks:<br />

a. Create a new AdventureWorksArchivedEntities context called archivedEntities.<br />

b. In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a<br />

copy of the data in the RewardsClaimed entity.<br />

c. Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.<br />

d. Save all of the changes in the archivedEntities context.<br />

e. At the end of the TransactionScope code block, call the Complete method of the<br />

TransactionScope object.


5-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

4. Add code to the CreateRewardsClaim method to handle the TransactionAbortedException<br />

exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 -<br />

In CreateRewardsClaim, handle TransactionAbortedException item in the task list.<br />

5. Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh<br />

the contact and the claim from the database, and throw a new DALException exception to report the<br />

error.<br />

6. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 4: Modify the UpdateRewardsClaim method to save a copy of the claim to the<br />

archive table as part of a transaction<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the comment TODO: Ex2 - In<br />

UpdateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an<br />

ArchivedRewardsClaim item in the task list. This task is located in the UpdateRewardsClaim<br />

method.<br />

3. Place the call to the SaveChanges method inside a new TransactionScope code block. In the<br />

TransactionScope code block, after the call to the SaveChanges method, add code to perform the<br />

following tasks:<br />

a. Create a new AdventureWorksArchivedEntities context called archivedEntities.<br />

b. In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a<br />

copy of the data in the RewardsClaimed entity.<br />

c. Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.<br />

d. Save all of the changes in the archivedEntities context.<br />

e. At the end of the TransactionScope code block, call the Complete method of the<br />

TransactionScope object.<br />

4. Add code to the UpdateRewardsClaim method to handle the TransactionAbortedException<br />

exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 -<br />

In UpdateRewardsClaim, handle TransactionAbortedException item in the task list.<br />

5. Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh<br />

the contact and the claim from the database, and throw a new DALException exception to report the<br />

error.<br />

6. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 5: Modify the unit tests to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest file by double-clicking the comment TODO: Ex2 - Count the<br />

archived claims before the insert item in the task list. This task is located in the<br />

CreateRewardsClaimTest method.<br />

3. Immediately after the comment, add code to count the number of archived claims by calling the<br />

CountArchivedRewardsClaimed method.<br />

4. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived<br />

claims after the insert and test item in the task list. This task is located in the<br />

CreateRewardsClaimTest method.


Handling Multi-User Scenarios by Using Object Services 5-31<br />

5. Immediately after the comment, add code to count the number of archived claims by calling the<br />

CountArchivedRewardsClaimed method, and verify that the number of archived rewards has<br />

increased by one.<br />

6. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived<br />

claims before the update item in the task list. This task is located in the UpdateRewardsClaimTest<br />

method.<br />

7. Immediately after the comment, add code to count the number of archived claims by calling the<br />

CountArchivedRewardsClaimed method.<br />

8. Navigate to the next comment by double-clicking the comment TODO: Ex2 - Count the archived<br />

claims after the update and test item in the task list. This task is located in the<br />

UpdateRewardsClaimTest method.<br />

9. Immediately after the comment, add code to count the number of archived claims by calling the<br />

CountArchivedRewardsClaimed method, and verify that the number of archived rewards has<br />

increased by one.<br />

10. Save the <strong>Data</strong><strong>Access</strong>LayerTest file.<br />

Task 6: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed, including the CreateRewardsClaimTest and<br />

UpdateRewardsClaimTest tests.<br />

4. Close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


5-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Review<br />

Review Questions<br />

1. What do you need to ensure for any entity property that you decide to use for concurrency checking?<br />

2. What should you do in the handler for an OptimisticConcurrencyException exception?<br />

3. What are the two options available to you when you refresh an entry in the cache?<br />

4. How do you indicate that a transaction controlled by using a TransactionScope object has<br />

completed?<br />

5. How do you initiate a distributed transaction?


Module Review and Takeaways<br />

Review Questions<br />

Handling Multi-User Scenarios by Using Object Services 5-33<br />

1. What are two ways that the Entity Framework can detect concurrency conflicts when you modify data<br />

in the database?<br />

2. Why does the Entity Framework use an optimistic instead of a pessimistic concurrency model?<br />

3. Outline a strategy to create unit tests that verify your concurrency-resolution logic.<br />

4. If you are using a TransactionScope object to define a transaction, how can you force a transaction<br />

to roll back?<br />

Best Practices Related to Handling Multi-User Scenarios by Using Object Services<br />

Supplement or modify the following best practices for your own work situations:<br />

• Use stored procedures in your EDM to perform data modifications and identify any concurrency<br />

conflicts.<br />

• Add columns such as Timestamp to your database tables to help identify concurrency conflicts.<br />

• Use the Refresh method whenever you must ensure that you have the latest data in the cache.<br />

• Use the TransactionScope class to manage explicit transactions in your code.<br />

• If you want to implement retry behavior when a transaction fails, use the<br />

SaveChanges(SaveOptions.None) and ApplyChanges methods.<br />

• Create unit tests to verify all of your concurrency and transactional behavior.


5-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010


Module 6<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-1<br />

Building Optimized <strong>Solutions</strong> by Using Object Services<br />

Contents:<br />

Lesson 1: The Stages of Query Execution 6-3<br />

Lesson 2: Change Tracking and Object Materialization 6-8<br />

Lesson 3: Using Compiled Queries 6-14<br />

Lesson 4: Using Design-Time Generated Entity Framework Views 6-19<br />

Lesson 5: Monitoring Performance 6-23<br />

Lesson 6: Performing Asynchronous <strong>Data</strong> Modifications 6-28<br />

Lab: Building Optimized <strong>Solutions</strong> by Using Object Services 6-33


6-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

This module describes best practices for designing and building a scalable, optimized data access layer by<br />

using Object Services. The module introduces several techniques that you can use to optimize the<br />

performance of queries that execute against the conceptual model. It also describes a strategy that you<br />

can use to run data modification operations asynchronously.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Describe how the Entity Framework executes queries.<br />

• Understand the impact of tracking and object materialization on query performance.<br />

• Use compiled queries.<br />

• Use design-time generated views.<br />

• Monitor query performance.<br />

• Perform asynchronous data modifications.


Lesson 1<br />

The Stages of Query Execution<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-3<br />

Most applications are designed to be as highly performing as possible. Executing queries against a<br />

database can be a lengthy task, so it is important to understand which stages in the query execution<br />

process are likely to be most time-consuming and what techniques are available to reduce those times.<br />

This lesson explains the key stages in the query execution process that the Entity Framework uses to<br />

retrieve data from the underlying database and identifies the options available to maximize the<br />

performance of the process.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Explain what happens when the Entity Framework executes a query.<br />

• Describe where to make performance enhancements to queries.


6-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The Query Execution Process<br />

Key Points<br />

When a client executes a query against the conceptual model, the Entity Framework must perform a<br />

sequence of operations to return entity data to the client. The following list shows the key stages of query<br />

operation:<br />

1. Loading metadata<br />

2. Opening the database connection<br />

3. Generating views<br />

4. Preparing the query<br />

5. Executing the query<br />

6. Loading and validating types<br />

7. Initializing change tracking<br />

8. Materializing the objects<br />

Loading Metadata<br />

Although loading the metadata into a MetadataWorkspace object is a moderately expensive operation,<br />

it is not likely to have an impact on the performance of an individual query, because there is only a single,<br />

global instance of the MetadataWorkspace object in an application domain. The Entity Framework<br />

populates this instance once, and all ObjectContext objects in the application domain then share it.<br />

Opening the <strong>Data</strong>base Connection<br />

Opening a database connection is relatively expensive, and by default, the ObjectContext object<br />

manages this connection on your behalf, opening and closing the connection as required. You can control<br />

the connection manually through the ObjectContext object's Connection property; in some<br />

circumstances, you may be able to improve performance by using manual control.


Building Optimized <strong>Solutions</strong> by Using Object Services 6-5<br />

If the underlying provider supports connection pooling, the cost of opening connections is spread across<br />

the pool. This reduces the cost of opening an individual connection.<br />

Generating Views<br />

The Entity Framework uses a set of query views to access the database. The Entity Framework creates<br />

these views once per application domain, and multiple ObjectContext instances can share them. The cost<br />

of generating these views is high; consequently, it is possible to generate them in advance and add them<br />

to the project at design time.<br />

Preparing the Query<br />

The cost of preparing a query for execution is typically moderate, although it varies according to the<br />

complexity of the query. The Entity Framework prepares queries by generating a command tree and<br />

defining the shape of the results. This preparation must be completed once for each unique query. If you<br />

define queries by using Entity Structured Query Language (Entity SQL), the Entity Framework caches the<br />

prepared version automatically, which speeds up subsequent executions of the same query. If you use<br />

Language-Integrated Query (LINQ) to Entities, you must manually compile the query before the Entity<br />

Framework can cache the prepared version.<br />

Executing the Query<br />

The cost of executing a query is typically low, but varies according to the complexity of the query.<br />

Subsequent executions of the same query may be faster if the underlying database caches query plans.<br />

Loading and Validating Types<br />

The cost of loading and validating types against the types defined in the conceptual model is low, and it<br />

happens once per ObjectContext instance.<br />

Initializing Change Tracking<br />

The cost of tracking an individual object is low, but it is incurred for every object that the query returns.<br />

There is no cost incurred if the query uses the NoTracking merge option.<br />

Materializing the Objects<br />

The cost of materializing an object is moderate, but this cost is not necessarily incurred for every object<br />

that a query returns. There is no cost if the returned entity already exists in the ObjectContext object and<br />

the query uses either of the AppendOnly or PreserveChanges merge options.<br />

Note: Queries created by using the EntityClient provider do not materialize objects.<br />

Question: Why do queries created by using the EntityClient provider not materialize objects?<br />

Additional Reading<br />

For more information about managing connections in the Entity Framework, see the Managing<br />

Connections and Transactions (Entity Framework) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194046.<br />

For more information about managing connections in the Entity Framework, see the Query Execution<br />

page at http://go.microsoft.com/fwlink/?LinkID=194047.


6-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Performance Enhancement Options<br />

Key Points<br />

This topic outlines a set of strategies that you can adopt to improve the performance of queries in your<br />

Entity Framework application.<br />

Use the NoTracking Merge Option for Queries<br />

When an ObjectContext instance tracks and manages an entity object, there is an associated cost. If you<br />

do not plan to update or delete the entities, you can use the NoTracking merge option when you run a<br />

query and create detached entity objects.<br />

Understanding when the Entity Framework materializes entity objects can also help you to manage the<br />

performance impact of queries.<br />

Compile LINQ Queries<br />

If you plan to execute the same or similar LINQ query repeatedly in your application, you can reduce the<br />

preparation cost. To do this, compile the query and use the compiled version. Compiled queries can have<br />

parameters.<br />

Generate Views at Design Time<br />

View creation represents a significant up-front cost, because it must occur before the Entity Framework<br />

can process any queries in your application. You can generate views at design time and add the generated<br />

views to your project.<br />

Limit the Amount of Returned <strong>Data</strong><br />

As <strong>with</strong> any application that deals <strong>with</strong> data, you should try to retrieve just the data that your application<br />

requires. You should consider how your queries load related objects and examine options that will enable<br />

you to return your data a page at a time.


Limit the Scope of Your ObjectContext Instance<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-7<br />

The ObjectContext class manages the attached entities that your application loads from the database.<br />

You should think about the useful lifetime of a specific instance of the ObjectContext class in your<br />

application.<br />

Manually Manage Your <strong>Data</strong>base Connections<br />

By default, the ObjectContext class opens and closes connections on behalf of your application. If your<br />

application makes large numbers of calls to the SaveChanges method, you may improve your<br />

application's performance by manually opening a connection before you begin to make the calls and then<br />

closing the connection when you have completed the calls.<br />

Question: Disposing of ObjectContext instances can free up resources in your application. What<br />

negative impact on performance may occur if you frequently dispose of ObjectContext instances?


6-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

Change Tracking and Object Materialization<br />

Change tracking and object materialization can adversely affect the performance of your application.<br />

However, there are techniques that you can use to minimize their effect.<br />

This lesson describes how change tracking and object materialization impact the performance of your<br />

application and explains the strategies that are available to manage this performance impact.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the life cycle of entities in the ObjectContext object's cache.<br />

• Explain when the Entity Framework materializes entity objects.<br />

• Describe the impact of change tracking on performance.


The Life Cycle of Entity Objects<br />

Key Points<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-9<br />

Entity objects are standard common language runtime (CLR) objects that represent types of data in the<br />

underlying database. Entity objects enable you to work <strong>with</strong> that data by using the constructs and<br />

abstractions of the <strong>Microsoft®</strong> .NET Framework rather than those of the database. To be able to improve<br />

the performance of an application that uses the Entity Framework, it is important to understand the life<br />

cycle of these entity objects.<br />

Detached and Attached Entity Objects<br />

The Entity Framework delivers much of its functionality through the ObjectContext class, which manages<br />

a cache of entity objects. Entity objects in this cache are referred to as attached objects and support<br />

change tracking and identity resolution. Entity objects not in the cache are known as detached objects.<br />

Creating Attached Entity Objects<br />

There are three ways to create an attached entity object:<br />

1. Run a query that uses an AppendOnly, PreserveChanges, or OverwriteChanges merge option.<br />

2. Load an entity object into the cache by using a Load method.<br />

3. Attach a detached entity object.<br />

<strong>Access</strong>ing Attached Entity Objects<br />

The ObjectContext class organizes entity objects into collections known as entity sets. Entity sets often<br />

represent tables in the underlying database. You can iterate over these entity sets by using standard<br />

programming constructs.<br />

Every entity object has a unique key that is represented by an EntityKey object. You can directly access an<br />

entity object by its key.


6-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Modifying Entity Objects<br />

Entity objects expose read/write properties that represent column values in the underlying database. If<br />

you change a property value, mark an entity object for deletion, or create a new entity object, the Entity<br />

Framework tracks this change by using an ObjectStateEntry object. The ObjectContext instance uses<br />

this change-tracking information to persist the change to the underlying database when you call the<br />

SaveChanges method.<br />

Detaching Entity Objects<br />

It is possible to detach an entity object from an ObjectContext instance. When you detach an entity<br />

object, the corresponding ObjectStateEntry object is removed from the ObjectContext instance.<br />

Removing Entity Objects from Memory<br />

An ObjectContext instance holds entity objects in the cache for the duration of the life of the<br />

ObjectContext instance.<br />

Question: What factors should you consider when you decide how long to keep an ObjectContext<br />

instance in memory?<br />

Additional Reading<br />

For more information about the ObjectContext class, see the Identity Resolution, State Management, and<br />

Change Tracking (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194048.<br />

For more information about the EntityKey class, see the Working <strong>with</strong> Entity Keys (Entity Framework)<br />

page at http://go.microsoft.com/fwlink/?LinkID=194049.


Entity Materialization<br />

Key Points<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-11<br />

When you execute a LINQ to Entities query or an Entity SQL query, the Entity Framework materializes the<br />

results from the database into CLR types. The CLR type will be one of the following:<br />

• A collection of zero or more typed entity objects.<br />

• A projection of complex types from the conceptual model.<br />

• A CLR type that the conceptual model supports.<br />

• An inline collection.<br />

• An anonymous type.<br />

The Entity Framework materializes entity objects when one of the following operations takes place:<br />

• You enumerate the ObjectQuery object by using a foreach statement (Microsoft <strong>Visual</strong> C#®) or a<br />

For Each statement (Microsoft <strong>Visual</strong> Basic®).<br />

• You enumerate the ObjectQuery object by using a collection operation such as a ToArray,<br />

ToCollection, or ToList method.<br />

• You call the Execute method of the ObjectQuery object.<br />

• You apply LINQ operators, such as First or Any, to the outermost part of the query.<br />

The MergeOption property of the query object determines which results the Entity Framework<br />

materializes into entity objects. The following table describes the different MergeOption values.<br />

MergeOption value Description<br />

AppendOnly Objects that do not exist in the object context are attached to the context. This is<br />

the default option.<br />

If an object is already in the context, the current and original values of the


6-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

MergeOption value Description<br />

object's properties in the entry are not overwritten <strong>with</strong> data source values.<br />

OverwriteChanges Objects that do not exist in the object context are attached to the context.<br />

If an object is already in the context, the current and original values of the<br />

object's properties in the entry are overwritten <strong>with</strong> data source values.<br />

PreserveChanges Objects that do not exist in the object context are attached to the context.<br />

If the state of the entity is Unchanged, the current and original values in the<br />

entry are overwritten <strong>with</strong> data source values.<br />

If the state of the entity is Modified, the current values of modified properties are<br />

not overwritten <strong>with</strong> data source values. The original values of unmodified<br />

properties are overwritten <strong>with</strong> the values from the data source.<br />

NoTracking Objects are maintained in a Detached state and are not tracked in the<br />

ObjectStateManager object.<br />

Note: The ObjectContext class raises the ObjectMaterialized event when an entity object is<br />

materialized.<br />

Question: Of the three MergeOption values (AppendOnly, OverwriteChanges, and PreserveChanges),<br />

which is likely to have the lowest impact on the performance of a query?


The Impact of Change Tracking on Performance<br />

Key Points<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-13<br />

The ObjectContext class maintains change-tracking information for attached entity objects by using<br />

ObjectStateEntry objects. An ObjectStateEntry object holds the following information for each attached<br />

entity object:<br />

• An EntityKey instance that determines the unique identity of an entity object.<br />

• An EntityState object that records whether the entity object has been modified.<br />

• Information about any related entity objects.<br />

• The name of the entity set that the entity object is a member of.<br />

• The current and original values of the properties of the entity object.<br />

• The names of any modified properties.<br />

The Entity Framework incurs a cost when it creates this change-tracking data during object<br />

materialization. There is also an ongoing cost in maintaining the change-tracking data when you make<br />

changes to entity objects. You can eliminate both of these costs if you use the NoTracking merge option<br />

when you execute a query.<br />

Question: What functionality do you lose if you use the NoTracking merge option <strong>with</strong> a query?


6-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 3<br />

Using Compiled Queries<br />

You can improve the performance of LINQ queries by precompiling them before executing them. This<br />

avoids the default behavior where they are compiled upon every execution.<br />

This lesson describes the performance benefits that you can gain by using compiled LINQ to Entities<br />

queries in your application. It also explains how to write the code that performs the compilation.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the benefits of compiled queries.<br />

• Explain how to compile a query.


The Benefits of Compiled Queries<br />

Key Points<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-15<br />

The Adventure Works client application uses a small number of queries to retrieve data from the database<br />

for use in the user interface (UI). For example, the client application uses a query to retrieve a contact<br />

entity by key; it then uses another query to retrieve all of the claims for the contact. These queries appear<br />

to be different each time that you execute them, because they retrieve data for different contacts;<br />

however, you are repeatedly executing the same two basic queries <strong>with</strong> different parameter values.<br />

If you use standard LINQ to Entities queries, the Entity Framework will compile these queries every time<br />

you run them, resulting in a significant performance hit for your application. If you compile these queries,<br />

you will incur the cost of compilation only once per query, when it is first run.<br />

Question: What types of query will benefit from explicit compilation?


6-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

How to Compile a Query<br />

Key Points<br />

To compile a LINQ to Entities query, you call the Compile method of the CompiledQuery class. The<br />

Compile method returns a delegate that references the compiled query. This delegate is one of the<br />

generic Func delegates, which can accommodate up to 16 query parameters, a return value, and a<br />

reference to an ObjectContext instance.<br />

The following code example shows how to compile and run a query that retrieves all of the<br />

RewardsClaimed entities from the AdventureWorks database.<br />

[<strong>Visual</strong> Basic]<br />

' Define the delegate to reference the compiled query.<br />

Shared compiled1 As Func(Of AdventureWorksEntities, ObjectQuery(Of<br />

RewardsClaimed)) =<br />

CompiledQuery.Compile(Of AdventureWorksEntities,<br />

ObjectQuery(Of RewardsClaimed))(Function(ctx) ctx.RewardsClaimed)<br />

Shared Sub Example1()<br />

Using entities As New AdventureWorksEntities()<br />

' Run the query.<br />

' The query is compiled the first time it is run.<br />

Dim claims As IQueryable(Of RewardsClaimed) =<br />

compiled1.Invoke(entities)<br />

For Each item In claims<br />

Console.WriteLine("ClaimID: {0}, ", item.ClaimID)<br />

Next<br />

End Using<br />

End Sub<br />

[<strong>Visual</strong> C#]


Define the delegate to reference the compiled query.<br />

static Func<br />

compiled1 =<br />

CompiledQuery.Compile(ctx =>ctx.RewardsClaimed);<br />

static void Example1()<br />

{<br />

using (AdventureWorksEntities entities =<br />

new AdventureWorksEntities())<br />

{<br />

// Run the query.<br />

// The query is compiled the first time it is run.<br />

IQueryable claims =<br />

compiled1.Invoke(entities);<br />

foreach (var item in claims)<br />

{<br />

Console.WriteLine("ClaimID: {0}, ", item.ClaimID);<br />

}<br />

}<br />

}<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-17<br />

The following code example shows how to compile and run a query that takes an integer parameter to<br />

specify which RewardsClaimed entities to retrieve from the database.<br />

[<strong>Visual</strong> Basic]<br />

' Define the delegate to reference the compiled query.<br />

Shared compiled2 As Func(Of AdventureWorksEntities, Int32, IQueryable(Of<br />

RewardsClaimed)) =<br />

CompiledQuery.Compile(Of AdventureWorksEntities, Int32,<br />

IQueryable(Of RewardsClaimed))(<br />

Function(ctx, contactID) From claim In ctx.RewardsClaimed<br />

Where claim.ContactID = contactID<br />

Select claim)<br />

Shared Sub Example2()<br />

Using entities As New AdventureWorksEntities()<br />

Dim contactID As Integer = 23<br />

End Sub<br />

' Run the query.<br />

' The query is compiled the first time it is run.<br />

Dim claims As IQueryable(Of RewardsClaimed) =<br />

compiled2.Invoke(entities, contactID)<br />

For Each item In claims<br />

Console.WriteLine("ClaimID: {0}, ", item.ClaimID)<br />

Next<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

// Define the delegate to reference the compiled query.<br />

static Func compiled2 =<br />

CompiledQuery.Compile(<br />

(ctx, contactID) => from claim in ctx.RewardsClaimed<br />

where claim.ContactID == contactID<br />

select claim);<br />

static void Example2()


6-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

{<br />

}<br />

using (AdventureWorksEntities entities =<br />

new AdventureWorksEntities())<br />

{<br />

int contactID = 23;<br />

}<br />

// Run the query.<br />

// The query is compiled the first time it is run.<br />

IQueryable claims =<br />

compiled2.Invoke(entities, contactID);<br />

foreach (var item in claims)<br />

{<br />

Console.WriteLine("ClaimID: {0}, ", item.ClaimID);<br />

}<br />

The following code example shows how to compile and run a query that returns a calculated value from<br />

the database. In this example, the query calculates the average number of points for a claim.<br />

[<strong>Visual</strong> Basic]<br />

' Define the delegate to reference the compiled query.<br />

Shared compiled3 As Func(Of AdventureWorksEntities, Double) =<br />

CompiledQuery.Compile(Of AdventureWorksEntities, Double)(<br />

Function(ctx) ctx.RewardsClaimed.Average(Function(claim) claim.PointsUsed))<br />

Shared Sub Example3()<br />

Using entities As New AdventureWorksEntities()<br />

' Run the query.<br />

' The query is compiled the first time it is run.<br />

Dim averagePoints As Double = compiled3.Invoke(entities)<br />

Console.WriteLine("Average Points: {0}, ", averagePoints)<br />

End Using<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

// Define the delegate to reference the compiled query.<br />

static Func compiled3 =<br />

CompiledQuery.Compile(<br />

ctx => ctx.RewardsClaimed.Average(claim => claim.PointsUsed));<br />

static void Example3()<br />

{<br />

using (AdventureWorksEntities entities =<br />

new AdventureWorksEntities())<br />

{<br />

// Run the query.<br />

// The query is compiled the first time it is run.<br />

Double averagePoints = compiled3.Invoke(entities);<br />

Console.WriteLine("Average Points: {0}, ", averagePoints);<br />

}<br />

}<br />

Question: How can you overcome the limit of 16 parameters for a compiled query?


Building Optimized <strong>Solutions</strong> by Using Object Services 6-19<br />

Lesson 4<br />

Using Design-Time Generated Entity Framework<br />

Views<br />

When you execute a query, the Entity Framework generates views to access the database. You can<br />

improve performance by generating these views at design time.<br />

This lesson explains the benefits and drawbacks of using design-time generated views in your application.<br />

It also explains how to create and use these views.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the benefits and drawbacks of design-time generated views.<br />

• Create design-time generated views.


6-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The Benefits and Drawbacks of Design-Time Generated Views<br />

Key Points<br />

Before the Entity Framework can execute a query, it must generate the set of views that it uses to access<br />

the database. It is expensive to create these views, and to reduce this cost, it is possible to generate the<br />

views at design time and include them in the project.<br />

To automate view creation, you must use pre-build and post-build steps in your project, which increases<br />

the complexity of your build environment.<br />

If you use Microsoft <strong>Visual</strong> <strong>Studio</strong>® to create ASP.NET Web sites that access your Entity <strong>Data</strong> Model<br />

(EDM), you cannot use pre-build and post-build steps. To automate the creation of views in this<br />

environment, you should consider one of the following options:<br />

• Place your EDM in a separate class library.<br />

• Use an ASP.NET Web application project.<br />

Note: You must modify the connection string in your App.Config or Web.Config file if you use views<br />

generated at design time.<br />

You can manually generate the views by using the EDM Generator tool.<br />

Question: When are views created if you do not use pre-generated views?<br />

Additional Reading<br />

For more information about using the EDM Generator tool, see the EDM Generator (EdmGen.exe) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194050.<br />

For more information about using a model defined in a class library, see the How to: Use a Model Defined<br />

in a Class Library (Entity <strong>Data</strong> Model Tools) page at http://go.microsoft.com/fwlink/?LinkID=194051.


Building Optimized <strong>Solutions</strong> by Using Object Services 6-21<br />

Demonstration: Generating Views at Design Time to Improve Query<br />

Performance<br />

Key Points<br />

• Open the existing application and configure it to copy the model and mapping files to the output<br />

directory.<br />

• Use EdmGen.exe to add view generation to the DAL project during the pre-build event.<br />

• Add the views to the solution.<br />

• Update the connection strings to use the design-time generated views.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-06 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. Run E:\Demofiles\Demo.bat to set up the database for this demonstration.<br />

3. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

4. In <strong>Visual</strong> <strong>Studio</strong> 2010, open the ViewGeneration.sln solution in the<br />

E:\Demofiles\Mod06\Demo1\Starter folder.<br />

5. Change the Metadata Artifact Processing property of the AdventureWorksArchivedEDM.edmx file<br />

to Copy Output Directory.<br />

6. Save the file and build the solution.<br />

7. Add view generation to the DAL project by using EdmGen.exe to generate the views during the prebuild<br />

event as the following code example shows.<br />

[<strong>Visual</strong> C#]<br />

"%windir%\Microsoft.NET\Framework\v4.0.30319\EdmGen.exe" /nologo /language:CSharp<br />

/mode:ViewGeneration "/inssdl:$(TargetDir)AdventureWorksArchivedEDM.ssdl"<br />

"/incsdl:$(TargetDir)AdventureWorksArchivedEDM.csdl"<br />

"/inmsl:$(TargetDir)AdventureWorksArchivedEDM.msl"<br />

"/outviews:$(ProjectDir)AdventureWorksArchivedEDM.Views.cs"


6-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Note: This command is one long line.<br />

8. Save all files in the solution and then build the solution.<br />

9. In Solution Explorer, right-click the DAL project, point to Add, and then click Existing Item.<br />

10. In the Add Existing Item - DAL dialog box, click AdventureWorksArchivedEDM.Views.cs, and<br />

then click Add.<br />

11. Build the solution.<br />

12. In App.Config for the TimingTests project, update the connection strings to use the new metadata<br />

resources.<br />

Question: If you pre-generate views in a class library project, what changes do you need to make in any<br />

client applications that use the class library?


Lesson 5<br />

Monitoring Performance<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-23<br />

Whenever you are trying to improve the performance of an application, it is useful to monitor the<br />

application to provide a benchmark for the performance before modifications and compare it <strong>with</strong> a<br />

reading after you have updated the code. In Entity Framework data access, you should also monitor the<br />

SQL statements that are generated to check for complex, time-consuming queries.<br />

This lesson explains how to monitor the SQL statements that the Entity Framework generates to interact<br />

<strong>with</strong> the underlying database. You can analyze problems more efficiently if you know which SQL<br />

statements the Entity Framework is running against your database. The lesson also describes the<br />

performance monitoring counters that can provide you <strong>with</strong> useful information about the performance of<br />

your Entity Framework application.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Explain how to log the SQL statements that the Entity Framework executes.<br />

• Describe the key Performance Monitor counters for monitoring the performance of data access<br />

operations.


6-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Logging SQL Statements Generated by the Entity Framework<br />

Key Points<br />

The ObjectQuery and EntityCommand classes have a ToTraceString method that returns the text of the<br />

SQL statements that the Entity Framework uses to retrieve data from the database. This information is<br />

useful when you analyze performance problems, especially those that arise from complex queries or<br />

complex mapping scenarios.<br />

The following code example shows how to view the SQL statements that the Entity Framework runs<br />

against the database when your application uses a LINQ to Entities query.<br />

[<strong>Visual</strong> Basic]<br />

''' <br />

''' Returns a list of all of the Contact entities from the database.<br />

''' <br />

''' <br />

Public Function GetContactList() As List(Of Contact)<br />

' Check you have an ObjectContext object.<br />

If entities Is Nothing Then entities = New AdventureWorksEntities()<br />

' Create a query from the entity set.<br />

Dim contacts As ObjectQuery(Of Contact) = entities.Contacts<br />

contacts.MergeOption = MergeOption.NoTracking<br />

' Define the query.<br />

var(Query = From c In contacts<br />

Select c)<br />

' Display the database commands for the query.<br />

Console.WriteLine(DirectCast(Query, ObjectQuery(Of Contact)).ToTraceString())<br />

' Execute the query.<br />

Dim results As List(Of Contact) = Query.ToList()<br />

' Return the results in a list.


Return results<br />

End Function<br />

[<strong>Visual</strong> C#]<br />

/// <br />

/// Returns a list of all of the Contact entities from the database.<br />

/// <br />

/// <br />

public List GetContactList()<br />

{<br />

}<br />

// Check you have an ObjectContext object.<br />

if (entities == null) entities = new AdventureWorksEntities();<br />

// Create a query from the entity set.<br />

ObjectQuery contacts = entities.Contacts;<br />

contacts.MergeOption = MergeOption.NoTracking;<br />

// Define the query.<br />

var query = from c in contacts<br />

select c;<br />

// Display the database commands for the query.<br />

Console.WriteLine(((ObjectQuery)query).ToTraceString());<br />

// Execute the query.<br />

List results = query.ToList();<br />

// Return the results in a list.<br />

return results;<br />

Question: What alternatives to the ToTraceString method may be available?<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-25


6-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Using Performance Monitor<br />

Key Points<br />

There are no performance counters available specifically for the Entity Framework. To monitor the<br />

performance of your application's data access operations, you should use the ADO.NET performance<br />

counters and any performance counters for your database.<br />

The .NET Framework includes ADO.NET performance counters for the System.<strong>Data</strong>.SqlClient and<br />

System.<strong>Data</strong>.OracleClient providers. These performance counters provide detailed information about<br />

your application's use of database connections and connection pools. The screen shot on the slide<br />

associated <strong>with</strong> this topic shows a user adding the System.<strong>Data</strong>.SqlClient counters to the Performance<br />

Monitor tool in Windows® 7.<br />

The following table describes the ADO.NET performance counters.<br />

Performancecounter Description<br />

HardConnectsPerSecond The number of connections per second to a database<br />

server in your application.<br />

HardDisconnectsPerSecond The number of disconnections per second from a database<br />

server in your application.<br />

NumberOfActiveConnectionPoolGroups The number of unique connection pool groups that are<br />

active. The number of unique connection strings in the<br />

application domain determines the value of this counter.<br />

NumberOfActiveConnectionPools The total number of connection pools.<br />

NumberOfActiveConnections The number of active connections that your application<br />

currently uses.<br />

NumberOfFreeConnections The number of connections available for use in the


Performancecounter Description<br />

connection pools.<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-27<br />

NumberOfInactiveConnectionPoolGroups The number of unique connection pool groups that are<br />

marked for pruning. The number of unique connection<br />

strings in the application domain controls determines the<br />

value of this counter.<br />

NumberOfInactiveConnectionPools The number of inactive connection pools that have not<br />

had any recent activity and are waiting to be disposed of.<br />

NumberOfNonPooledConnections The number of active connections that are not pooled.<br />

NumberOfPooledConnections The number of active connections that the connection<br />

pooling infrastructure currently manages.<br />

NumberOfReclaimedConnections The number of connections that have been reclaimed<br />

through garbage collection. Application performance is<br />

impaired if you do not explicitly close or dispose of<br />

connections.<br />

NumberOfStasisConnections The number of connections currently awaiting completion<br />

of an action. These connections are unavailable for use by<br />

your application.<br />

SoftConnectsPerSecond The number of active connections that your application<br />

pulls from the connection pool every second.<br />

SoftDisconnectsPerSecond The number of active connections that your application<br />

returns to the connection pool every second.<br />

The performance counters NumberOfFreeConnections, NumberOfActiveConnections,<br />

SoftDisconnectsPerSecond, and SoftConnectsPerSecond are not enabled by default. To enable these<br />

counters, add the information in the following code example to the application's configuration file.<br />

<br />

<br />

<br />

<br />

<br />

Question: Which performance counter can you monitor to verify that your application correctly closes<br />

connections?<br />

Additional Reading<br />

For more information about the ADO.NET performance counters, see the Performance Counters<br />

(ADO.NET) page at http://go.microsoft.com/fwlink/?LinkID=194052.<br />

For more information about runtime profiling, see the Runtime Profiling page at<br />

http://go.microsoft.com/fwlink/?LinkID=194053.


6-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 6<br />

Performing Asynchronous <strong>Data</strong> Modifications<br />

Persisting changes to a database can be a slow process due to many factors such as network speed and<br />

server speed. You can improve the perceived performance of an application by persisting these changes<br />

while enabling users to continue working on their next task.<br />

This lesson explains how asynchronous data modifications can improve the performance of client<br />

applications. The lesson describes a pattern that uses the BackgroundWorker class to perform data<br />

modifications asynchronously on behalf of your application.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Explain the benefits of asynchronous data modifications.<br />

• Describe how to perform asynchronous data modifications.


The Benefits of Asynchronous <strong>Data</strong> Modifications<br />

Key Points<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-29<br />

<strong>Data</strong> modifications can take a noticeable amount of time to complete, resulting in a poor user experience.<br />

For example, in the Adventure Works Rewards Claims application, users may find that it takes an<br />

unacceptable amount of time to add a claim to a contact. If you perform this modification<br />

asynchronously, you can return control to the client application as soon as you start the data modification<br />

process. This will enable the user to continue to use the application while the changes are persisted to the<br />

underlying database.<br />

The Entity Framework performs data modifications in two stages. In the first stage, you modify data in the<br />

cache that the ObjectContext object maintains. You can perform this data modification synchronously,<br />

because this type of operation is typically fast. In the second stage, you persist changes to the database by<br />

calling the SaveChanges method. This operation can be slow, especially if you have batched together<br />

many changes in the ObjectContext instance. You can perform this operation asynchronously.<br />

If the Entity Framework detects any errors during the call to the SaveChanges method, you must handle<br />

these errors automatically or find a way to report them back to the UI. If you report them back to the UI,<br />

you must remember that the user may be in the middle of some other task.<br />

Question: Will your client application have access to the changed data values before the asynchronous<br />

method has finished persisting data to the database?


6-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

How to Perform Asynchronous <strong>Data</strong> Modifications<br />

Key Points<br />

The following code example shows how to add a new reward entity to the database asynchronously. The<br />

example uses a BackgroundWorker object to add the new reward entity. When the operation is<br />

complete, the background task notifies the UI of the change by calling the OnModification method to<br />

fire an event that is handled in the UI.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub AddReward(ByVal reward As Reward)<br />

Try<br />

' Use a BackgroundWorker object to perform the data modification.<br />

BackgroundWorker(bw = New BackgroundWorker())<br />

bw.WorkerSupportsCancellation = False<br />

bw.WorkerReportsProgress = False<br />

' Any unhandled exceptions will be passed to the<br />

' RunWorkerCompleted evaent handler.<br />

AddHandler bw.DoWork,<br />

Sub(o, args)<br />

Using entities As New AdventureWorksEntities()<br />

reward.RewardID = GetNextRewardID()<br />

entities.AddToRewards(reward)<br />

entities.SaveChanges()<br />

args.Result = reward.RewardID<br />

Catch ex As InvalidOperationException<br />

Throw New DALException(<br />

"There was a problem adding the new Reward", ex)<br />

Catch ex As UpdateException


End Sub<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-31<br />

Throw New DALException(<br />

"There was a problem saving the new Reward to the database", ex)<br />

End Try<br />

End Using<br />

' Check for errors and notify the UI.<br />

AddHandler bw.RunWorkerCompleted,<br />

Sub(o, args)<br />

Integer),<br />

End Sub<br />

If args.Error IsNot Nothing Then<br />

On<strong>Data</strong>ModificationCompleted(False,<br />

args.Error.Message, -1)<br />

Else<br />

On<strong>Data</strong>ModificationCompleted(True,<br />

"Deleted Contact <strong>with</strong> ContactID: " & DirectCast(args.Result,<br />

End If<br />

End Sub<br />

DirectCast(args.Result, Integer))<br />

' Perform the modification on the background thread.<br />

bw.RunWorkerAsync()<br />

[<strong>Visual</strong> C#]<br />

public void AddReward(Reward reward)<br />

{<br />

// Use a BackgroundWorker object to perform the data modification.<br />

BackgroundWorker bw = new BackgroundWorker();<br />

bw.WorkerSupportsCancellation = false;<br />

bw.WorkerReportsProgress = false;<br />

// Any unhandled exceptions will be passed to the<br />

// RunWorkerCompleted evaent handler.<br />

bw.DoWork += (o, args) =><br />

{<br />

using (AdventureWorksEntities entities =<br />

new AdventureWorksEntities())<br />

{<br />

try<br />

{<br />

}<br />

reward.RewardID = GetNextRewardID();<br />

entities.AddToRewards(reward);<br />

entities.SaveChanges();<br />

args.Result = reward.RewardID;<br />

catch (InvalidOperationException ex)<br />

{<br />

throw new DALException(


6-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

};<br />

}<br />

}<br />

"There was a problem adding the new Reward",<br />

ex);<br />

catch (UpdateException ex)<br />

{<br />

throw new DALException(<br />

"There was a problem saving the new Reward to<br />

the database", ex);<br />

}<br />

// Check for errors and notify the UI.<br />

bw.RunWorkerCompleted += (o, args) =><br />

{<br />

if (args.Error != null)<br />

On<strong>Data</strong>ModificationCompleted(false,<br />

args.Error.Message, -1);<br />

else<br />

On<strong>Data</strong>ModificationCompleted(true,<br />

"Deleted Contact <strong>with</strong> ContactID: " + (int)args.Result,<br />

(int)args.Result);<br />

};<br />

// Perform the modification on the background thread.<br />

bw.RunWorkerAsync();<br />

Using the RunWorkerCompleted event enables you to pass exceptions from the background thread to<br />

the main thread and safely notify the UI of the result.<br />

Note: The ObjectContext class is not thread-safe, so be careful when you create background threads to<br />

perform asynchronous data modifications.<br />

Question: Will any data be saved to the database if the call to the SaveChanges method throws an<br />

exception?<br />

Additional Reading<br />

For more information about the BackgroundWorker component, see the BackgroundWorker<br />

Component Overview page at http://go.microsoft.com/fwlink/?LinkID=194054.


Building Optimized <strong>Solutions</strong> by Using Object Services 6-33<br />

Lab: Building Optimized <strong>Solutions</strong> by Using Object<br />

Services<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Compare the performance of different query implementations.<br />

• Create, update, and delete entity data asynchronously.<br />

Introduction<br />

In this lab, you will write code to analyze the performance of a query that you will implement by using<br />

different technologies and options. You will compare the results of the same query when you implement<br />

it by using LINQ to Entities, compiled LINQ to Entities, and Entity SQL. You will explore the effect of<br />

change tracking and generating views at design time on query performance. You will also perform data<br />

modifications asynchronously.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-06 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


6-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

Adventure Works implements an entity model to support its customer reward program. Users of the client<br />

application have complained about the poor performance of some operations.<br />

You have been asked to evaluate the various options for running queries that the Entity Framework offers.<br />

You have also been asked to modify the existing data access layer to perform data modifications<br />

asynchronously.


Building Optimized <strong>Solutions</strong> by Using Object Services 6-35<br />

Exercise 1: Improving the Performance of Query Operations<br />

Scenario<br />

In this exercise, you will create a console application to explore the performance of different query<br />

implementations in the data access layer and analyze the results.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the AdventureWorks database for the lab.<br />

2. Open the starter project for this exercise.<br />

3. Print timing information during query execution.<br />

4. Add code to define a compiled LINQ query.<br />

5. Add code to invoke the compiled LINQ query.<br />

6. Add code to retrieve all of the contact entities by using Entity SQL.<br />

7. Modify the GetContactList method to check the NoTracking variable.<br />

8. Build and test the application.<br />

9. Pre-generate views to improve query performance.<br />

Task 1: Prepare the AdventureWorks database for the lab<br />

1. Log on to the 10265A-GEN-DEV-06 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run AWReset.bat.<br />

Task 2: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex1\Starter or<br />

E:\Labfiles\Lab06\CS\Ex1\Starter folder.<br />

Task 3: Print timing information during query execution<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Return all contacts<br />

<strong>with</strong> timings item in the task list. This task is located in the GetContactListDetail method.<br />

3. Delete the comment in the GetContactListDetail method.<br />

4. Write code that performs the following tasks:<br />

a. Instantiate and start two Stopwatch objects called totaltime and stagetime.<br />

b. Check whether the entities variable is null. If it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

Note: The entities variable is a private field in the <strong>Data</strong><strong>Access</strong>Layer class. Your code should perform all<br />

data access operations by using this context object.<br />

c. Print the value of the ElapsedTime property from the stagetime object, and then restart the<br />

Stopwatch.<br />

d. Retrieve an ObjectQuery object from the context's Contacts property.<br />

e. Print the value of the ElapsedTime property from the stagetime object, and then restart the<br />

Stopwatch.<br />

f. Define and execute a LINQ query to retrieve all of the contacts from the ObjectQuery object,<br />

and then save the results to a List object.


6-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

g. Print the value of the ElapsedTime property from the stagetime object, and then restart the<br />

Stopwatch.<br />

h. Print the value of the ElapsedTime property from the totaltime object, and then restart the<br />

Stopwatch.<br />

i. Return the List object.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 4: Add code to define a compiled LINQ query<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Define the compiled<br />

LINQ query item in the task list. This task is located in the <strong>Data</strong><strong>Access</strong>Layer class.<br />

3. Immediately after the comment, add code to define a compiled LINQ query called compiledQuery<br />

by using a static function. The query should return all of the contact entities from the EDM.<br />

4. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 5: Add code to invoke the compiled LINQ query<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Retrieve all contacts<br />

using the compiled query item in the task list. This task is located in the<br />

GetContactListEntityCompiledLINQ method.<br />

3. Delete the comment in the GetContactListEntityCompiledLINQ method.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Obtain an ObjectQuery object by invoking the compiled LINQ query.<br />

c. If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption<br />

property to NoTracking.<br />

d. Return the contact entities in a List object.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 6: Add code to retrieve all of the contact entities by using Entity SQL<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex1 - Retrieve all<br />

contacts using Entity SQL item in the task list. This task is located in the<br />

GetContactListEntityQuery method.<br />

3. Delete the comment in the GetContactListEntityQuery method.<br />

4. Write code that performs the following tasks:<br />

a. Check whether the entities variable is null. If it is, instantiate it as a new instance of the<br />

AdventureWorksEntities context object.<br />

b. Obtain an ObjectQuery object by creating a query that uses Entity SQL to retrieve all of the<br />

contact entities from the EDM.<br />

c. If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption<br />

property to NoTracking.


d. Return the contact entities in a List object.<br />

5. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-37<br />

Task 7: Modify the GetContactList method to check the NoTracking variable<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex1 - Check<br />

NoTracking item in the task list. This task is located in the GetContactList method.<br />

3. Modify the line of code immediately below the comment to check whether the value of the<br />

NoTracking variable is true before you set the MergeOption property to NoTracking.<br />

4. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 8: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.<br />

3. Observe the timing results obtained by running the TimingTests application:<br />

a. In the command window, on the Detailed Timing for GetContactList() page, make a note of<br />

the Total Time values, and then press ENTER.<br />

b. In the command window, on the Compare Implementations of GetContactList() page, write<br />

down the Average values, and then press ENTER.<br />

Task 9: Pre-generate views to improve query performance<br />

1. Open the AdventureWorksEDM.edmx file and change the Metadata Artifact Processing property to<br />

Copy to Output Directory.<br />

2. Save the AdventureWorksEDM.edmx file and build the solution.<br />

3. Add view generation to the DAL project by using EdmGen.exe to generate the views during the prebuild<br />

event.<br />

4. Add the generated views to the project.<br />

5. Update the connection strings in the TimingTests project to use the new metadata resources.<br />

6. Start the application in Debug mode.<br />

7. Observe the timing results obtained by running the TimingTests application:<br />

a. In the command window, on the Detailed Timing for GetContactList() page, write down the<br />

Total Time values, and then press ENTER.<br />

b. In the command window, on the Compare Implementations of GetContactList() page, write<br />

down the Average values, and then press ENTER.<br />

8. Close the solution.


6-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 2: Improving the Performance of Update Operations<br />

Scenario<br />

In this exercise, you will modify the data access layer so that you can create, update, and delete claim<br />

entities asynchronously. You will use the BackgroundWorker class to perform the modifications on a<br />

background thread.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Modify the CreateRewardsClaim method to run asynchronously.<br />

3. Modify the UpdateRewardsClaim method to run asynchronously.<br />

4. Modify the DeleteRewardsClaim method to run asynchronously.<br />

5. Modify your unit tests to verify your asynchronous code.<br />

6. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex2\Starter or<br />

E:\Labfiles\Lab06\CS\Ex2\Starter folder.<br />

Task 2: Modify the CreateRewardsClaim method to run asynchronously<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex2 - In<br />

CreateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in<br />

the CreateRewardsClaim method.<br />

3. Immediately after the comment, write code that performs the following tasks:<br />

a. Instantiate a new BackgroundWorker object.<br />

b. Set the WorkerSupportsCancellation property to false.<br />

c. Set the WorkerReportsProgress property to false.<br />

4. Locate the next comment TODO: Ex2 - In CreateRewardsClaim, place existing code in DoWork<br />

item in the task list. This task is located in the CreateRewardsClaim method.<br />

5. Assign the existing code in the CreateRewardsClaim method to the BackgroundWorker object's<br />

DoWork event handler by using a lambda expression. Replace the two existing return statements<br />

<strong>with</strong> statements that assign the new claim to the Result property of the DoWork event's args<br />

parameter.<br />

6. Locate the next comment TODO: Ex2 - In CreateRewardsClaim, implement the<br />

BackgroundWorkers RunWorkerCompleted event item in the task list. This task is located in the<br />

CreateRewardsClaim method.<br />

7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted<br />

event handler. If there were errors in the DoWork event, call the On<strong>Data</strong>ModificationCompleted<br />

method <strong>with</strong> false as the first parameter, the error message as the second parameter, and -1 as the<br />

third parameter. If the DoWork event completed <strong>with</strong>out errors, call the<br />

On<strong>Data</strong>ModificationCompleted method <strong>with</strong> true as the first parameter, a success message as the<br />

second parameter, and the claimID property of the new claim as the third parameter.<br />

8. Locate the next comment TODO: Ex2 - In CreateRewardsClaim, start the BackgroundWorker item<br />

in the task list. This task is located in the CreateRewardsClaim method.


Building Optimized <strong>Solutions</strong> by Using Object Services 6-39<br />

9. Immediately after the comment, add code to start the BackgroundWorker component running<br />

asynchronously.<br />

10. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 3: Modify the UpdateRewardsClaim method to run asynchronously<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex2 - In<br />

UpdateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in<br />

the UpdateRewardsClaim method.<br />

3. Immediately after the comment, write code that performs the following tasks:<br />

a. Instantiate a new BackgroundWorker object.<br />

b. Set the WorkerSupportsCancellation property to false.<br />

c. Set the WorkerReportsProgress property to false.<br />

4. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,<br />

place existing code in DoWork item in the task list. This task is located in the<br />

UpdateRewardsClaim method.<br />

5. Assign the existing code in the UpdateRewardsClaim method to the BackgroundWorker object's<br />

DoWork event handler by using a lambda expression. Replace the two existing return statements<br />

<strong>with</strong> statements that assign the updated claim to the Result property of the DoWork event's args<br />

parameter.<br />

6. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,<br />

implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is<br />

located in the UpdateRewardsClaim method.<br />

7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted<br />

event handler. If there were errors in the DoWork event, call the On<strong>Data</strong>ModificationCompleted<br />

method <strong>with</strong> false as the first parameter, the error message as the second parameter, and -1 as the<br />

third parameter. If the DoWork event completed <strong>with</strong>out errors, call the<br />

On<strong>Data</strong>ModificationCompleted method <strong>with</strong> true as the first parameter, a success message as the<br />

second parameter, and the claimID property of the updated claim as the third parameter.<br />

8. Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim,<br />

start the BackgroundWorker item in the task list. This task is located in the UpdateRewardsClaim<br />

method.<br />

9. Immediately after the comment, add code to start the BackgroundWorker component running<br />

asynchronously.<br />

10. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 4: Modify the DeleteRewardsClaim method to run asynchronously<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer code file by double-clicking the comment TODO: Ex2 - In<br />

DeleteRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in<br />

the DeleteRewardsClaim method.<br />

3. Immediately after the comment, write code that performs the following tasks:<br />

a. Instantiate a new BackgroundWorker object.<br />

b. Set the WorkerSupportsCancellation property to false.


6-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

c. Set the WorkerReportsProgress property to false.<br />

4. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,<br />

place existing code in DoWork item in the task list. This task is located in the DeleteRewardsClaim<br />

method.<br />

5. Assign the existing code in the DeleteRewardsClaim method to the BackgroundWorker object's<br />

DoWork event handler by using a lambda expression. Delete the two existing return statements.<br />

6. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,<br />

implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is<br />

located in the DeleteRewardsClaim method.<br />

7. Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted<br />

event handler. If there were errors in the DoWork event, call the On<strong>Data</strong>ModificationCompleted<br />

method <strong>with</strong> false as the first parameter, the error message as the second parameter, and -1 as the<br />

third parameter. If the DoWork event completed <strong>with</strong>out errors, call the<br />

On<strong>Data</strong>ModificationCompleted method <strong>with</strong> true as the first parameter, a success message as the<br />

second parameter, and the claimID property of the deleted claim as the third parameter.<br />

8. Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim,<br />

start the BackgroundWorker item in the task list. This task is located in the DeleteRewardsClaim<br />

method.<br />

9. Immediately after the comment, add code to start the BackgroundWorker component running<br />

asynchronously.<br />

10. Save the <strong>Data</strong><strong>Access</strong>Layer code file.<br />

Task 5: Modify your unit tests to verify your asynchronous code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex2 - In<br />

CreateRewardsClaimTest, call CreateRewardsClaim item in the task list. This task is located in the<br />

CreateRewardsClaimTest method.<br />

3. Review the existing code in the CreateRewardsClaimTest method.<br />

4. Immediately after the comment, call the CreateRewardsClaim method in the data access layer,<br />

passing the test claim called claim as a parameter.<br />

5. Locate the next TODO comment in the CreateRewardsClaimTest method by double-clicking the<br />

comment TODO: Ex2 - In CreateRewardsClaimTest, check the values retrieved from the<br />

database item in the task list.<br />

6. Immediately after the comment, add code to check that the property values of the claim object<br />

match those of the lastClaim object.<br />

7. Locate the first TODO comment in the UpdateRewardsClaimTest method by double-clicking the<br />

comment TODO: Ex2 - In UpdateRewardsClaimTest, modify the claim and save the changes<br />

item in the task list.<br />

8. Review the existing code in the UpdateRewardsClaimTest method.<br />

9. Immediately after the comment, modify the PointsUsed and RewardID properties of the claim<br />

object, and call the UpdateRewardsClaim method in the data access layer, passing the test claim<br />

called claim as a parameter.<br />

10. Locate the next TODO comment in the UpdateRewardsClaimTest method by double-clicking the<br />

comment TODO: Ex2 - In UpdateRewardsClaimTest, check the values retrieved from the<br />

database item in the task list.


Building Optimized <strong>Solutions</strong> by Using Object Services 6-41<br />

11. Immediately after the comment, add code to check that the property values of the claim object<br />

match those of the updatedClaim object and that the value of the updateResult variable is true.<br />

12. Locate the first TODO comment in the DeleteRewardsClaimTest method by double-clicking the<br />

TODO: Ex2 - In DeleteRewardsClaimTest, delete the claim item in the task list.<br />

13. Review the existing code in the DeleteRewardsClaimTest method.<br />

14. Immediately after the comment, call the DeleteRewardsClaim method in the data access layer,<br />

passing the claim object's ClaimID property as a parameter.<br />

15. Locate the next TODO comment in the DeleteRewardsClaimTest method by double-clicking the<br />

comment TODO: Ex2 - In DeleteRewardsClaimTest, check the delete succeeded item in the task<br />

list.<br />

16. Immediately after the comment, add code to check that the value of the deleteResult variable is true.<br />

17. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 6: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed.<br />

4. Start the Customer Rewards application in Debug mode.<br />

5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into<br />

the data grid. Verify that you can add, modify, and delete claims and that the contact’s points are<br />

adjusted correctly.<br />

6. Close the application.<br />

7. Close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


6-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Review<br />

Review Questions<br />

1. In the detailed timings for the GetContactList method results, what is the likely explanation for the<br />

context creation time in the first run, as compared <strong>with</strong> subsequent runs?<br />

2. In the detailed timings for the GetContactList method results, what is the likely explanation for the<br />

query run time in the first run, as compared <strong>with</strong> subsequent runs?<br />

3. In the comparison of implementations of the GetContactList method results, why does the<br />

NoTracking merge option appear to be slow on all runs?<br />

4. How do you write unit tests for asynchronous operations?


Module Review and Takeaways<br />

Review Questions<br />

Building Optimized <strong>Solutions</strong> by Using Object Services 6-43<br />

1. What are the main options that you can use to improve the performance of queries in the Entity<br />

Framework?<br />

2. How can you see which SQL statements the Entity Framework is executing against the underlying<br />

database?<br />

3. What information do the ADO.NET performance counters provide?<br />

Best Practices Related to Building Optimized <strong>Solutions</strong> by Using Object Services<br />

Supplement or modify the following best practices for your own work situations:<br />

• Use the NoTracking merge option when you run queries.<br />

• Compile your LINQ to Entities queries.<br />

• Pre-generate the views in your Entity Framework application.<br />

• Limit the amount of data that you return from queries.<br />

• Limit the scope of your ObjectContext objects.<br />

• Manage your database connections manually.<br />

• Perform data modification operations asynchronously.<br />

• Be aware that the ObjectContext class is not thread-safe.


6-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010


Module 7<br />

Customizing Entities and Building Custom Entity Classes 7-1<br />

Customizing Entities and Building Custom Entity Classes<br />

Contents:<br />

Lesson 1: Overriding Generated Classes 7-3<br />

Lesson 2: Using Templates to Customize Entities 7-14<br />

Lesson 3: Creating and Using Custom Entity Classes 7-26<br />

Lab: Customizing Entities and Building Custom Entity Classes 7-37


7-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

You may find that you want to add custom business logic to your entities to meet your application<br />

requirements. The Entity Framework generates partial classes, so you can extend these to include<br />

whatever code you need. You can also modify the templates that the code generation tools use to<br />

configure entity classes to inherit from additional custom interfaces. Alternatively, if your custom business<br />

logic already exists in a business class, you can use inheritance and attributes to convert the class into an<br />

entity class.<br />

This module describes how to customize and extend entities <strong>with</strong> your own business logic.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Use partial classes and methods to add business logic to generated code.<br />

• Create and use templates to customize code generation.<br />

• Modify existing business classes to take advantage of entity functionality.


Lesson 1<br />

Overriding Generated Classes<br />

Customizing Entities and Building Custom Entity Classes 7-3<br />

Generally, you will want to take advantage of the ease of use of generated entity classes. However, you<br />

will sometimes need to add small blocks of code for custom functionality. This lesson describes how you<br />

can use partial classes and methods to add functionality to the existing classes.<br />

Objectives<br />

After you have completed this lesson, you will be able to:<br />

• Use partial classes.<br />

• Use partial property change methods.<br />

• Use partial events.<br />

• Create extension methods.


7-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Using Partial Classes<br />

Key Points<br />

When you create an Entity <strong>Data</strong> Model (EDM) by using <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® (either manually or<br />

from an existing database), classes are automatically generated based on the ObjectContext and<br />

EntityObject classes. These generated classes contain entity-specific methods and events, but do not<br />

contain business logic. If you add your business logic code to these classes, it will be overwritten if the<br />

classes are later regenerated. However, because the entity classes are partial classes, you can extend them<br />

to add custom functionality to your entities. At compile time, the compiler creates one class from the<br />

multiple partial classes in the same namespace.<br />

Note: When you use partial classes, you should only list the base classes and attributes in one of<br />

the partial classes.<br />

For example, if you have a calculated column in your database, you can easily generate the data for<br />

display to the user in the front end <strong>with</strong>out performing a round trip to save the data and retrieve the<br />

calculated column. To do this, you add a partial class for the entity. In that class, you create a method to<br />

calculate the value and pass it to the user interface. You can then still allow the calculation to be<br />

performed in the database when the data is saved at an appropriate time.<br />

Question: You have added some methods that contain business logic to your entity class, but the code<br />

seems to have disappeared. What is happening?<br />

Additional Reading<br />

For more information about partial classes, see the Partial Classes section of the Partial Classes and<br />

Methods (C# Programming Guide) page at http://go.microsoft.com/fwlink/?LinkID=194055.


Using Partial Property Change Methods<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-5<br />

In addition to partial classes, you can also use partial methods to add custom code to your entities.<br />

The generated code for each entity contains definitions for an OnChanging and an<br />

OnChanged method. Object Services calls these two methods immediately before and after a<br />

property is changed. You can add partial methods for these two methods in a partial class to create<br />

custom logic that executes when properties are changed. For example, in the OnChanging<br />

method, you could write validation code and in the OnChanged method, you could write<br />

change logging code.<br />

Example<br />

To add validation code for the ListPrice property of a Product entity, you create a partial class for the<br />

entity and implement the OnListPriceChanging event in that class as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Partial Class Product<br />

Private Partial Sub OnListPriceChanging(ByVal value As [Decimal])<br />

If value < 0 Then<br />

Throw New Exception("List Price must be greater than zero")<br />

End If<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

partial class Product<br />

{<br />

partial void OnListPriceChanging(Decimal value)<br />

{


7-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

}<br />

if (value < 0)<br />

{<br />

throw new Exception("List Price must be greater than zero");<br />

}<br />

The OnChanging and OnChanged methods also execute during object<br />

materialization, so any existing data in your data source will be validated when it loads.<br />

Question: When does the code in an OnChanged method execute?<br />

Additional Reading<br />

For more information about partial methods, see the Partial Methods section of the Partial Classes and<br />

Methods (C# Programming Guide) page at http://go.microsoft.com/fwlink/?LinkID=194056.


Customizing Entities and Building Custom Entity Classes 7-7<br />

Demonstration: Using the OnChanging Method<br />

Key Points<br />

• Review the partial classes and methods in generated code.<br />

• Review the OnChanging method used to validate user input.<br />

• Review the XAML for a list box.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-07 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. Start <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

3. In <strong>Visual</strong> <strong>Studio</strong> 2010, open the ProductApplication.sln solution in the<br />

E:\Demofiles\Mod07\OnPropertyChangingDemo\ProductApplication folder.<br />

4. Open AWModel.Designer.cs.<br />

5. In the Product class, locate the ListPrice property.<br />

6. Review the code in the set, particularly the call to the OnListPriceChanging method.<br />

7. Note the OnListPriceChanging partial method definition.<br />

8. Open ProductExtension.cs.<br />

9. Note the partial class definition, the partial method definition, and the contents of the<br />

OnListPriceChanging method.<br />

10. Open MainWindow.xaml.<br />

11. Review the XAML for the List Price text box.<br />

12. Run and test the application.<br />

13. Close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: Why don't you need to define that the class in ProductExtension.cs inherits from EntityObject?


7-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Using Partial Events<br />

Key Points<br />

There are four key overridable events that you can use to customize your application.<br />

ObjectContext.SavingChanges<br />

This event executes just before changes that are cached in the ObjectContext object are saved to the<br />

data source. You can use this event to validate data that is stored in the ObjectContext object and the<br />

changes are persisted to the database. The ObjectContext object holds the data for all of the entities in a<br />

model, so the SavingChanges event provides one central place to define all of your validation logic.<br />

The following code example shows how to implement the SavingChanges event handler by overriding<br />

the OnContextCreated partial method.<br />

[<strong>Visual</strong> Basic]<br />

Private Partial Sub OnContextCreated()<br />

AddHandler Me.SavingChanges, AddressOf AWSavingChanges<br />

End Sub<br />

Private Shared Sub AWSavingChanges(ByVal sender As Object, ByVal e As EventArgs)<br />

For Each entry As ObjectStateEntry In DirectCast(sender,<br />

ObjectContext).ObjectStateManager.GetObjectStateEntries(EntityState.Added Or<br />

EntityState.Modified)<br />

If Not entry.IsRelationship AndAlso (entry.Entity.[GetType]() Is<br />

GetType(Product)) Then<br />

Dim orderToCheck As Product = TryCast(entry.Entity, Product)<br />

If orderToCheck.ListPrice < 0 Then<br />

Throw New ApplicationException("List Price must be greater than zero")<br />

End If<br />

End If<br />

Next<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

partial void OnContextCreated()<br />

{


}<br />

this.SavingChanges += new System.EventHandler(AWSavingChanges);<br />

Customizing Entities and Building Custom Entity Classes 7-9<br />

private static void AWSavingChanges(object sender, EventArgs e)<br />

{<br />

foreach (ObjectStateEntry entry in<br />

((ObjectContext)sender).ObjectStateManager.GetObjectStateEntries(EntityState.Added |<br />

EntityState.Modified))<br />

{<br />

if (!entry.IsRelationship && (entry.Entity.GetType() == typeof(Product)))<br />

{<br />

Product orderToCheck = entry.Entity as Product;<br />

if (orderToCheck.ListPrice < 0)<br />

{<br />

throw new ApplicationException("List Price must be greater than zero");<br />

}<br />

}<br />

}<br />

}<br />

EntityObject.PropertyChanging and EntityObject.PropertyChanged<br />

The PropertyChanging and PropertyChanged events execute when any property in the EntityObject<br />

object changes. These class-level events enable you to implement more generic business logic than the<br />

property-level OnChanging and OnChanged methods. If you implement both<br />

the methods and events, the order of code execution is:<br />

1. OnChanging method<br />

2. PropertyChanging event<br />

3. PropertyChanged event<br />

4. OnChanged method<br />

[<strong>Visual</strong> Basic]<br />

Public Sub New()<br />

AddHandler Me.PropertyChanging, AddressOf Product_PropertyChanging<br />

AddHandler Me.PropertyChanged, AddressOf Product_PropertyChanged<br />

End Sub<br />

Private Sub Product_PropertyChanging(ByVal sender As Object, ByVal e As<br />

PropertyChangingEventArgs)<br />

Dim changingProperty As String = e.PropertyName<br />

If e.PropertyName = "ListPrice" Then<br />

' Business logic<br />

End If<br />

End Sub<br />

Private Sub Product_PropertyChanged(ByVal sender As Object, ByVal e As<br />

PropertyChangedEventArgs)<br />

Dim changedProperty As String = e.PropertyName<br />

If e.PropertyName = "ListPrice" Then<br />

' Business logic<br />

End If<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

public Product()<br />

{<br />

this.PropertyChanging += new PropertyChangingEventHandler(Product_PropertyChanging);<br />

this.PropertyChanged += new PropertyChangedEventHandler(Product_PropertyChanged);


7-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

private void Product_PropertyChanging(object sender, PropertyChangingEventArgs e)<br />

{<br />

}<br />

string changingProperty = e.PropertyName;<br />

if (e.PropertyName == "ListPrice")<br />

{<br />

// Business logic<br />

}<br />

private void Product_PropertyChanged(object sender, PropertyChangedEventArgs e)<br />

{<br />

}<br />

string changedProperty = e.PropertyName;<br />

if (e.PropertyName == "ListPrice")<br />

{<br />

// Business logic<br />

}<br />

RelatedEnd.AssociationChanged<br />

You can use the RelatedEnd.AssociationChanged event to respond to a change that was made to a<br />

related end of an association. It is specific to a particular navigation property of an entity and there is no<br />

mechanism for handling all association changes in an entity in one code block, as the following code<br />

example shows.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub New()<br />

AddHandler Me.ProductSubcategoryReference.AssociationChanged, AddressOf<br />

Product_SubCategory_AssociationChanged<br />

End Sub<br />

Private Sub Product_SubCategory_AssociationChanged(ByVal sender As Object, ByVal e As<br />

CollectionChangeEventArgs)<br />

End Sub<br />

If e.Action = CollectionChangeAction.Remove Then<br />

' Business logic<br />

End If<br />

[<strong>Visual</strong> C#]<br />

public Product()<br />

{<br />

this.ProductSubcategoryReference.AssociationChanged += new<br />

CollectionChangeEventHandler(Product_SubCategory_AssociationChanged);<br />

}<br />

private void Product_SubCategory_AssociationChanged(object sender,<br />

CollectionChangeEventArgs e)<br />

{<br />

if (e.Action == CollectionChangeAction.Remove)<br />

{<br />

// Business logic<br />

}<br />

}


Customizing Entities and Building Custom Entity Classes 7-11<br />

Question: What is the difference between the PropertyChanging and PropertyChanged events<br />

compared to the OnChanging and OnChanged methods?


7-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Creating Extension Methods<br />

Key Points<br />

You can use extension methods instead of standard methods to enable Microsoft IntelliSense® support<br />

for your custom methods. In the ADO.NET Entity Framework, there is a wide range of scenarios where<br />

extension methods can enhance your applications.<br />

Example<br />

The following code example shows an extension method named IsDirty that you can use to check<br />

whether data in an entity has changed. It follows the requirements for an extension method, so you can<br />

call it from client code by simply referencing the compiled dynamic-link library (DLL) that contains the<br />

code and adding a using statement for the namespace.<br />

[<strong>Visual</strong> Basic]<br />

Public Class MyExtensions<br />

Private Sub New()<br />

End Sub<br />

_<br />

Public Shared Function IsDirty(ByVal context As ObjectContext, ByVal entity As<br />

EntityObject) As Boolean<br />

Return (entity.EntityState = EntityState.Added) OrElse (entity.EntityState =<br />

EntityState.Deleted) OrElse (entity.EntityState = EntityState.Modified)<br />

End Function<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public static class MyExtensions<br />

{<br />

public static bool IsDirty(this ObjectContext context, EntityObject entity)<br />

{<br />

return (entity.EntityState == EntityState.Added)<br />

|| (entity.EntityState == EntityState.Deleted)


}<br />

}<br />

|| (entity.EntityState == EntityState.Modified);<br />

Customizing Entities and Building Custom Entity Classes 7-13<br />

Question: You have created an extension method, compiled the code, added a reference to the DLL in<br />

your client application, and added a using statement for the namespace, but still your application does<br />

not recognize the method. What could be the problem?<br />

Additional Reading<br />

For more information about extension methods, see the Extension Methods (C# Programming Guide)<br />

page at http://go.microsoft.com/fwlink/?LinkID=194057.


7-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

Using Templates to Customize Entities<br />

You can use templates to customize entities and their behavior. The Entity Framework supports using Text<br />

Template Transformation Toolkit (T4) templates to configure entity classes to inherit from a predefined<br />

interface and thus implement the methods that it contains. By using these templates, you can customize<br />

the entity code generation to make the generated classes implement a required interface.<br />

In this lesson, you will learn how to create and code the template, implement the interface in your entity<br />

code, and call the implementation from client code.<br />

Objectives<br />

After you have completed this lesson, you will be able to:<br />

• Add a code generation template to a model.<br />

• Describe how code generation templates work.<br />

• Define a custom interface in a template.<br />

• Implement a template interface in a model.<br />

• Use a template implementation.


Adding Code Generation Items to a Model<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-15<br />

In an EDM project, you can create a T4 template by adding an ADO.NET EntityObject Generator item to<br />

the project. When you add the item to your project, the code behind the model designer is replaced <strong>with</strong><br />

the code that the template generates. In the code-behind file in <strong>Visual</strong> <strong>Studio</strong>, you will simply see a<br />

comment explaining this.<br />

Add a T4 template to an EDM<br />

1. Open your model in the designer window.<br />

2. Right-click the designer surface, and then click Add Code Generation Item.<br />

3. In the Add New Item dialog box, in the Templates list, click ADO.NET EntityObject Generator.<br />

4. In the Name box, type a name for the item, and then click Add.<br />

5. In the Security Warning message box, click OK.<br />

Before you add the template to the EDM, the Code Generation Strategy property of the model is set to<br />

Default, which means that the default object-layer code generation is switched on. When you add the<br />

template to the EDM, the property changes to None and the default code generation is replaced <strong>with</strong><br />

template code generation.<br />

Whenever you save the template or model, the code in the template runs and the entity class file is<br />

regenerated. You can change this default behavior to not run the template when you save the model by<br />

setting the Transform Related Text property of the model to False.<br />

For background information about T4 templates, see the Code Generation and Text Templates page at<br />

http://go.microsoft.com/fwlink/?LinkId=196095.<br />

Question: You have added a template to your model, but then decided not to use it. When you delete<br />

the template, you find errors in your code. What could be the problem and how can you resolve it?


7-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Code Generation Templates<br />

Key Points<br />

Code generation templates are written in T4 syntax. In <strong>Visual</strong> <strong>Studio</strong>, you can use the text editor to view<br />

and edit the content of the template.<br />

Example<br />

The following code example shows an extract from a code generation template for a model based on a<br />

subset of tables from the AdventureWorks database.<br />

[<strong>Visual</strong> Basic]<br />

...<br />

Imports System<br />

Imports System.<strong>Data</strong>.Objects<br />

Imports System.<strong>Data</strong>.Objects.<strong>Data</strong>Classes<br />

Imports System.<strong>Data</strong>.EntityClient<br />

Imports System.ComponentModel<br />

Imports System.Xml.Serialization<br />

Imports System.Runtime.Serialization<br />

<br />

<br />


...<br />

Next<br />

region.End()<br />

If(Not String.IsNullOrEmpty(namespaceName)) Then<br />

[<strong>Visual</strong> C#]<br />

...<br />

using System;<br />

using System.<strong>Data</strong>.Objects;<br />

using System.<strong>Data</strong>.Objects.<strong>Data</strong>Classes;<br />

using System.<strong>Data</strong>.EntityClient;<br />

using System.ComponentModel;<br />

using System.Xml.Serialization;<br />

using System.Runtime.Serialization;<br />

Customizing Entities and Building Custom Entity Classes 7-17<br />

[assembly: EdmSchemaAttribute()]<br />

<br />

[assembly: EdmRelationshipAttribute("",<br />

"", "", , typeof(), "", , typeof())]<br />

<br />

...<br />

if (!String.IsNullOrEmpty(namespaceName))<br />

{<br />

The first section, which contains the using statements, is text that is directly written to the generated code<br />

file. The code that is surrounded by is executed to generate the entity code when you run the<br />

template.<br />

You can edit both the text and executable sections of the template to customize the entity code that is<br />

generated when you run the template.<br />

Question: You want to add a reference to a namespace into your template. Should you surround the<br />

using statement <strong>with</strong> ?


7-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Defining Custom Interfaces in a Template<br />

Key Points<br />

You can modify any part of the template by adding either text to be written directly to the entity code or<br />

code to be executed when the template runs.<br />

Example<br />

To add validation code to all of the entities in your model, you can modify the template to base the entity<br />

classes on a predefined interface and add the signature for the interface methods to the class.<br />

The statement defines the start of the entity class definition section<br />

of the template file. The code in this section runs for each entity in the model, resulting in one class for<br />

each entity in the generated code file. The following code example shows the rest of this line of code,<br />

which declares the classes.<br />

[<strong>Visual</strong> Basic]<br />

Partial Class<br />

<br />

...<br />

Inherits <br />

End Class<br />

[<strong>Visual</strong> C#]


Customizing Entities and Building Custom Entity Classes 7-19<br />

partial class <br />

: <br />

{<br />

...<br />

}<br />

This code retrieves the Abstract property setting to correctly annotate the class and then creates a partial<br />

class that uses the entity name. Any base types for that entity are then added to the declaration.<br />

To declare that all of your entities should derive from another interface, you add the interface name to<br />

the end of the base class list in the declaration, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Partial Class<br />

<br />

Inherits <br />

Implements IValidate<br />

. . .<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

partial class <br />

: , IValidate<br />

{<br />

. . .<br />

}<br />

Then, <strong>with</strong>in the body of the class, you can add the implementation of the IValidate interface, as the<br />

following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Partial Class<br />

<br />

Inherits <br />

Implements IValidate<br />

Partial Private Sub OnValidate()<br />

Sub Validate() Implements IValidate.Validate<br />

OnValidate()<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

partial class <br />

: , IValidate<br />

{<br />

partial void OnValidate();<br />

void IValidate.Validate()<br />

{<br />

OnValidate();<br />

}<br />

. . .


7-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

Now when the template runs to regenerate the classes, they will derive from the IValidate interface, as<br />

the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

_<br />

_<br />

_<br />

Public Partial Class Product<br />

Inherits EntityObject<br />

Implements IValidate<br />

Private Partial Sub OnValidate()<br />

End Sub<br />

Private Sub Validate() Implements IValidate.Validate<br />

OnValidate()<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

[EdmEntityTypeAttribute(NamespaceName="AdventureWorksModel", Name="Product")]<br />

[Serializable()]<br />

[<strong>Data</strong>ContractAttribute(IsReference=true)]<br />

public partial class Product : EntityObject, IValidate<br />

{<br />

partial void OnValidate();<br />

void IValidate.Validate()<br />

{<br />

OnValidate();<br />

}<br />

. . .<br />

}<br />

Question: Why should you declare the method as partial?


Implementing Template Interfaces in a Model<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-21<br />

Although the stubs for the template interface are created in the generated classes, if you write your code<br />

in them, it will be overwritten when the class is regenerated. Therefore, you should add a partial class for<br />

the entity class in a new file and implement your interfaces in that partial class. You must ensure that the<br />

namespace of both the generated entity class and your partial class are the same.<br />

Implement the template interface<br />

1. Add a new class file to the project.<br />

2. Verify that the namespace is the same value as the entity class and modify the class definition to be a<br />

partial class of the generated entity class.<br />

3. Add the method signature for the interface method, ensuring that you define it as partial.<br />

4. Add your business logic to the method.<br />

The following code example shows how to implement the sample IValidate interface in the Product<br />

class.<br />

[<strong>Visual</strong> Basic]<br />

namespace ProductApplication<br />

{<br />

partial class Product<br />

{<br />

partial void OnValidate()<br />

{<br />

if (ListPrice < 0)<br />

{<br />

throw new Exception("List Price must be greater than zero");<br />

}<br />

}<br />

}


7-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

[<strong>Visual</strong> C#]<br />

namespace ProductApplication<br />

{<br />

partial class Product<br />

{<br />

partial void OnValidate()<br />

{<br />

if (ListPrice < 0)<br />

{<br />

throw new Exception("List Price must be greater than zero");<br />

}<br />

}<br />

}<br />

}<br />

Question: You have implemented your interface in a new partial class, but the code shows compile errors.<br />

What could be the problem?


Using the Template Implementation<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-23<br />

You call the template implementation by calling the method name as you would any other code in your<br />

project.<br />

You use the template implementation from wherever you need to execute the business logic that it<br />

contains. This is dependent on the structure of your application and the content of the business logic<br />

code. For simplicity, in the following code example, you will see how to call the ListPrice validation code<br />

directly from the user interface in response to a user request to save changes. In the lab, you will see how<br />

to call the business logic from a data access layer.<br />

Example<br />

[<strong>Visual</strong> Basic]<br />

Private Sub saveListPrice_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)<br />

Try<br />

Dim changedProduct As Product<br />

products = productContext.Products.ToList()<br />

changedProduct = products(productList.SelectedIndex)<br />

DirectCast(changedProduct, IValidate).Validate()<br />

productContext.SaveChanges()<br />

Catch ex As Exception<br />

MessageBox.Show(ex.Message)<br />

productContext.Refresh(RefreshMode.StoreWins, products)<br />

End Try<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

private void saveListPrice_Click(object sender, RoutedEventArgs e)<br />

{<br />

try<br />

{


7-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

Product changedProduct;<br />

products = productContext.Products.ToList();<br />

changedProduct = products[productList.SelectedIndex];<br />

((IValidate)changedProduct).Validate();<br />

productContext.SaveChanges();<br />

}<br />

catch (Exception ex)<br />

{<br />

MessageBox.Show(ex.Message);<br />

productContext.Refresh(RefreshMode.StoreWins, products);<br />

}<br />

In this code example, the validation code is surrounded by a try statement to ensure that, if validation<br />

fails, an exception is thrown that is caught in the client and handled gracefully.<br />

Question: You are using your template implementation from your client code. When validation fails, the<br />

user is informed, but the data displayed in the application still shows the invalid values until the<br />

application is closed. What is likely to be missing from your code?


Demonstration: Using Templates to Customize Entities<br />

Key Points<br />

• Define an interface in a template.<br />

• Code the implementation in your application.<br />

• Use that implementation from a client application.<br />

Demonstration Steps<br />

Customizing Entities and Building Custom Entity Classes 7-25<br />

1. Start <strong>Visual</strong> <strong>Studio</strong>.<br />

2. Open the project named ProductApplication in the<br />

E:\Demofiles\Mod07\TemplateDemo\ProductApplication folder.<br />

3. In the ProductApplication project, open ProductModel.tt, and then review the IValidate code at lines<br />

316-323.<br />

4. In the ProductApplication project, open ProductExtension.cs, and then review the OnValidate<br />

method.<br />

5. In the ProductUI project, open MainWindow.xaml.cs, and then review the SaveListPrice_Click<br />

method.<br />

6. Run the application.<br />

7. Close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: Why is the IValidate definition in the template not enclosed in tags?


7-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 3<br />

Creating and Using Custom Entity Classes<br />

You will often have existing business logic that you want to reuse in your new Entity Framework<br />

application. You can modify these classes by using inheritance and attributes to become entity classes and<br />

provide you <strong>with</strong> full entity functionality from a business class.<br />

This lesson describes how to define the entity, how to use entity attributes, and how to then use the<br />

custom entity class from your client applications.<br />

Objectives<br />

After you have completed this lesson, you will be able to:<br />

• Define a custom entity.<br />

• Use entity attributes.<br />

• Develop entity properties.<br />

• Use a custom entity class.


Defining a Custom Entity Class<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-27<br />

You can create custom entity classes from existing business classes by inheriting from EntityObject and<br />

adding entity attributes to the class and its members. To implement this functionality, you must add<br />

references and using statements for the relevant namespaces to the class. You can then declare the class<br />

to inherit from EntityObject and add the EdmEntityType attribute to the class definition, identifying the<br />

entity namespace and name as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Imports System.<strong>Data</strong><br />

Imports System.<strong>Data</strong>.Objects.<strong>Data</strong>Classes<br />

Imports System.<strong>Data</strong>.Metadata.Edm<br />

_<br />

Public Class myBusinessClass<br />

Inherits EntityObject<br />

[<strong>Visual</strong> C#]<br />

using System.<strong>Data</strong>;<br />

using System.<strong>Data</strong>.Objects.<strong>Data</strong>Classes;<br />

using System.<strong>Data</strong>.Metadata.Edm;<br />

[EdmEntityType (NamespaceName = "AdventureWorksModel", Name = "Product")]<br />

public class myBusinessClass : EntityObject<br />

By creating custom entity classes in this way, they will contain all of the functionality required to integrate<br />

<strong>with</strong> Object Services.<br />

When you are developing custom entity classes, you can remove the corresponding classes from the<br />

generated code, but keep the remainder of the generated classes, which you do not need to customize.


7-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Alternatively, you can customize all of the model, in which case, you must supply all of the classes<br />

including the EntityContainer class that returns the EntitySet class.<br />

Question: Why might you want to create a custom entity class?


Using Entity Attributes<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-29<br />

The System.<strong>Data</strong>.Objects.<strong>Data</strong>Classes namespace contains a set of EDM attribute classes that you can<br />

use to indicate that classes and members represent entity members. These include:<br />

• EdmEntityType. This attribute identifies that the class represents an entity type. It takes two<br />

parameters: NamespaceName, which denotes the namespace for the entity, and Name, which defines<br />

the name of the entity.<br />

• EdmScalarProperty. This attribute points a property in your class to a scalar property of the entity in<br />

the model. It takes two parameters: EntityKey, which defines whether it is the key property for the<br />

entity, and IsNullable, which defines whether the property can be set to null.<br />

• EdmComplexProperty. This attribute points a property in your class to a complex property of the<br />

entity in the model.<br />

• EdmComplexType. This attribute denotes that the class represents a complex type. It takes two<br />

parameters: NamespaceName, which denotes the namespace for the type, and Name, which defines<br />

the name of the type.<br />

• EdmRelationship. This attribute defines the relationship between two entity types based on an<br />

association in the model. It takes nine parameters:<br />

• RelationshipNamespaceName defines the namespace.<br />

• RelationshipName defines the name of the relationship.<br />

• IsForeignKey indicates whether the relationship is based on the foreign-key value.<br />

• Role1Multiplicity and Role2Multiplicity define the multiplicity at each end of the relationship.<br />

• Role1Name and Role2Name define the names of the roles at the ends of the relationship.<br />

• Role1Type and Role2Type define the types at the ends of the relationship.


7-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

You only define this attribute in one of the two related classes, so you must track where they are<br />

defined to avoid declaring it in more than one class.<br />

• EdmRelationshipNavigationProperty. This attribute identifies a navigation property between<br />

entities. It takes three parameters:<br />

• RelationshipNamespaceName defines the namespace.<br />

• RelationshipName defines the name of the relationship in the model.<br />

• TargetRoleName defines the role name of the end of the relationship.<br />

Example<br />

The following code example shows how to use the EdmScalarProperty attribute to define a scalar<br />

property of an entity.<br />

[<strong>Visual</strong> Basic]<br />

[EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)]<br />

public Int32 ProductID<br />

{<br />

}<br />

get<br />

{<br />

}<br />

set<br />

{<br />

}<br />

[<strong>Visual</strong> C#]<br />

// Code to retrieve the property value<br />

// Code to set the property value<br />

[EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)]<br />

public Int32 ProductID<br />

{<br />

}<br />

get<br />

{<br />

}<br />

set<br />

{<br />

}<br />

// Code to retrieve the property value<br />

// Code to set the property value<br />

Question: How do you specify that a property is the key property of an attribute?


Customizing an EDM for Custom Classes<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-31<br />

You will often find that the entity types and properties in your model do not match the class and property<br />

names in your custom data classes. To resolve this issue, you need to update the XML mapping files.<br />

Update the conceptual schema definition language (CSDL)<br />

1. Rename the EntityType and EntitySet elements to match the class names in your custom data<br />

classes.<br />

2. Remove any EntityType and EntitySet elements that are not used in your custom data classes.<br />

3. Rename the Property elements in each type to match the property names in your custom data<br />

classes.<br />

4. Remove any Property elements that are not used in your custom data classes.<br />

Update the mapping specification language (MSL)<br />

1. Rename the EntitySetMapping element and the TypeName attribute to match the names of your<br />

custom data classes.<br />

2. Remove any EntitySetMapping elements that are not used in your custom data classes.<br />

3. Rename the ScalarProperty elements in each type to match the property names in your custom data<br />

classes.<br />

4. Remove any ScalarProperty elements that are not used in your custom data classes.<br />

5. Rename the EndProperty elements in the AssociationSetMapping element to match the names of<br />

your custom data classes.<br />

6. Rename the ScalarProperty elements in the AssociationSetMapping element to match the names<br />

of the properties in your custom data classes.<br />

7. Remove any AssociationSetMapping elements for associations that are not used in your custom<br />

data classes.


7-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Update the store schema definition language (SSDL)<br />

1. Remove any EntityType elements that are not used in your custom data classes.<br />

2. Remove any ScalarProperty elements that are not used in your custom data classes.<br />

Question: How can you view the XML behind a model?


<strong>Developing</strong> Entity Properties<br />

Key Points<br />

Customizing Entities and Building Custom Entity Classes 7-33<br />

The Entity Framework enables you to track changes to entities. Therefore, when your code modifies a<br />

property, you must also notify the change tracker that the property has changed. There are two methods<br />

of the EntityObject class that you can call to notify the change tracker:<br />

• EntityObject.ReportPropertyChanging. This method notifies the change tracker that a property<br />

change is pending. Call this method directly before setting the property of an entity.<br />

• EntityObject.ReportPropertyChanged. This method notifies the change tracker that a property has<br />

changed. Call this method directly after setting the property of an entity.<br />

To make a change to the property, you call the StructuralObject.SetValidValue method. This ensures<br />

that the change is notified to all relevant objects and correctly updated in the entity and the user<br />

interface. When you use this method to change string properties, you must pass it two parameters: the<br />

value and a Boolean value that indicates whether the property allows a null string.<br />

Examples<br />

The following code example shows how to retrieve and set an entity property.<br />

[<strong>Visual</strong> Basic]<br />

_<br />

Public Property ProductID() As Int32<br />

Get<br />

' Code to retrieve the property value<br />

Return _ProductID<br />

End Get<br />

Set(ByVal value As Int32)<br />

' Code to set the property value<br />

ReportPropertyChanging("ProductID")<br />

_ProductID = StructuralObject.SetValidValue(value)<br />

ReportPropertyChanged("ProductID")


7-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

End Set<br />

End Property<br />

[<strong>Visual</strong> C#]<br />

[EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)]<br />

public Int32 ProductID<br />

{<br />

get<br />

{<br />

// Code to retrieve the property value<br />

return _ProductID;<br />

}<br />

set<br />

{<br />

// Code to set the property value<br />

ReportPropertyChanging("ProductID");<br />

_ProductID = StructuralObject.SetValidValue(value);<br />

ReportPropertyChanged("ProductID");<br />

}<br />

}<br />

You will notice that, in this code example, there is no reference to the OnChanging method.<br />

Unless you want to intercept this method <strong>with</strong> a partial method, you do not have to call it.<br />

You can validate data in the set method. If you encounter invalid data, you can throw an exception, as<br />

shown in the following code example, which can be caught at the client and handled appropriately.<br />

[<strong>Visual</strong> Basic]<br />

_<br />

Public Property ListPrice As Decimal<br />

Get<br />

' Code to retrieve the property value<br />

Return _ListPrice<br />

End Get<br />

Set(ByVal value As Decimal)<br />

' Code to set the property value<br />

If (Value < 0) Then<br />

Throw New Exception("List Price must be greater than zero")<br />

End If<br />

ReportPropertyChanging("ListPrice")<br />

_ListPrice = StructuralObject.SetValidValue(Value)<br />

ReportPropertyChanged("ListPrice")<br />

End Set<br />

End Property<br />

[<strong>Visual</strong> C#]<br />

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)]<br />

public Decimal ListPrice<br />

{<br />

get<br />

{<br />

// Code to retrieve the property value<br />

return _ListPrice;<br />

}<br />

set<br />

{<br />

// Code to set the property value


}<br />

}<br />

Customizing Entities and Building Custom Entity Classes 7-35<br />

if (value < 0)<br />

{<br />

throw new Exception("List Price must be greater than zero");<br />

}<br />

ReportPropertyChanging("ListPrice");<br />

_ListPrice = StructuralObject.SetValidValue(value);<br />

ReportPropertyChanged("ListPrice");<br />

}<br />

Question: What does the call to the ReportPropertyChanged method do?


7-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Using a Custom Entity Class<br />

Key Points<br />

You can data bind a custom entity class to a Windows® Presentation Foundation (WPF) user interface in<br />

the same way that you bind a generated class. However, if your entity property throws an exception<br />

during its validation process, you must ensure that your user interface elements can catch the exception<br />

and handle it gracefully. The simplest way to do this is to add the tag to the<br />

list of binding rules in the element for the bound control as shown in the<br />

following code example.<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

This validation rule causes the control to be flagged <strong>with</strong> an error if the validation fails.<br />

Question: What would happen in the compiled application if you did not include the<br />

tag in the binding rules?


Customizing Entities and Building Custom Entity Classes 7-37<br />

Lab: Customizing Entities and Building Custom Entity<br />

Classes<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Use code templates to add custom functionality to entity classes.<br />

• Create custom entity classes.<br />

Introduction<br />

In this lab, you will use a T4 template to add custom functionality to the existing Contact class. You will<br />

then replace this class <strong>with</strong> an existing business class that already contains all of the business functionality<br />

that you require. You will modify this class to inherit from EntityObject and work <strong>with</strong> Object Services.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-07 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


7-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

You are concerned that some of the older existing applications that use your data access code might be<br />

saving invalid contact data. You decide to implement a defense-in-depth strategy and add some custom<br />

validation logic to the Contact entity class in your data access layer to support these older applications.<br />

You then discover that the business logic and validation rules for contacts is much more complicated than<br />

you originally anticipated. Instead of implementing the validation logic for this class yourself, you decide<br />

to expose the existing business class, which already validates data as an entity class.


Customizing Entities and Building Custom Entity Classes 7-39<br />

Exercise 1: Using a Template to Add Custom Functionality to Entity Classes<br />

Scenario<br />

In this exercise, you will define an interface called IValidate that exposes a single method called Validate.<br />

You will add a code generation item to the EDM and customize the T4 template to include the IValidate<br />

interface in all generated entities and invoke a partial method called OnValidate. You will then add a<br />

partial class file for the Contact entity in the EDM and implement the OnValidate method for the entity.<br />

The OnValidate method will validate the data in the entity and throw an exception if any of this data is<br />

invalid when adding or updating a contact.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the AdventureWorks database for the lab.<br />

2. Open the starter project for this exercise.<br />

3. Create the IValidate class.<br />

4. Create the template.<br />

5. Customize the template.<br />

6. View the generated code.<br />

7. Implement the OnValidate method.<br />

8. Modify the DAL code to validate the data.<br />

9. Add unit tests to verify your code.<br />

10. Build and test the application.<br />

Task 1: Prepare the AdventureWorks database for the lab<br />

1. Log on to the 10265A-GEN-DEV-07 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. Run AWReset.bat in the E:\Labfiles folder.<br />

Task 2: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab07\VB\Ex1\Starter or<br />

E:\Labfiles\Lab07\CS\Ex1\Starter folder.<br />

Task 3: Create the IValidate class<br />

1. Add a new interface named IValidate to the DAL project.<br />

2. Modify the interface definition to make it public, and add a void method named Validate that takes<br />

no arguments.<br />

Task 4: Create the template<br />

1. Add an ADO.NET EntityObject Generator item named AWModel.tt to the DAL project.<br />

2. Open AdventureWorksEDM.Designer.cs or AdventureWorksEDM.Designer.vb and review the<br />

comment that it contains.<br />

Task 5: Customize the template<br />

1. In AWModel.tt, locate the line of code that begins .<br />

2. Edit the line of code to make every entity object implement the IValidate interface.<br />

3. Within the body of this section, declare a partial void method named OnValidate that takes no<br />

arguments.<br />

4. Immediately after the statement that declares the OnValidate method, implement the<br />

IValidate.Validate method. Inside this method, call the OnValidate method that you have just<br />

declared.


7-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

5. If you are using Microsoft <strong>Visual</strong> Basic®, you must also manually adjust the namespace to match the<br />

rest of the project.<br />

Task 6: View the generated code<br />

• Build the solution, and then review the generated code in each class in AWModel.cs or AWModel.vb.<br />

Task 7: Implement the OnValidate method<br />

1. Add a new class named ContactExtension to the DAL project.<br />

2. Modify the class definition to define the class as a public partial class for the Contact class.<br />

3. Add a void method named OnValidate to the class. If you are using Microsoft <strong>Visual</strong> C#®, this<br />

method should be declared partial.<br />

4. Add code to the OnValidate method to throw a DALValidationException exception in each of the<br />

following scenarios:<br />

• If the CurrentPoints property is set to a negative value.<br />

• If the EmailAddress property does not contain an @ symbol.<br />

• If the EmailAddress property does not contain a period.<br />

Task 8: Modify the DAL code to validate the data<br />

1. In the <strong>Data</strong><strong>Access</strong>Layer class, modify the UpdateContact method to call the Validate method<br />

before saving changes to the object.<br />

2. In the <strong>Data</strong><strong>Access</strong>Layer class, modify the AddContact method to call the Validate method before<br />

saving changes to the object.<br />

3. In the CustomerRewards project, in the MainWindow.xaml.cs or MainWindow.xaml.vb class, in the<br />

contacts_MouseDoubleClick event, refresh the contacts list.<br />

Task 9: Add unit tests to verify your code<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>LayerTest code file by double-clicking the comment TODO: Ex1 - Add a test<br />

for AddContact when there is a CurrentPoints validation exception item in the task list. This task<br />

is located in the AddContactCurrentPointsValidationTest method.<br />

3. Add an ExpectedException attribute to the method for the DALValidationException type.<br />

4. Delete the comment in the AddContactCurrentPointsValidationTest method.<br />

5. Add a unit test to create a Contact object, set the CurrentPoints property of the Contact object to<br />

an invalid value, and then add the contact to the database. Be sure to release all resources at the end<br />

of the test.<br />

6. Locate the AddContactAtSymbolValidationTest method by double-clicking the comment TODO:<br />

Ex1 - Add a test for AddContact when there is a missing @ sign in the e-mail address<br />

validation exception item in the task list.<br />

7. Add an ExpectedException attribute to the method for the DALValidationException type.<br />

8. Delete the comment in the AddContactAtSymbolValidationTest method.<br />

9. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to<br />

an invalid value <strong>with</strong> a missing @ symbol, and then add the contact to the database. Be sure to<br />

release all resources at the end of the test.<br />

10. Locate the AddContactPeriodValidationTest method by double-clicking the comment TODO: Ex1<br />

- Add a test for AddContact when there is a missing period in the e-mail address validation<br />

exception item in the task list.<br />

11. Add an ExpectedException attribute to the method for the DALValidationException type.<br />

12. Delete the comment in the AddContactPeriodValidationTest method.


Customizing Entities and Building Custom Entity Classes 7-41<br />

13. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to<br />

an invalid value <strong>with</strong> a missing period, and then add the contact to the database. Be sure to release all<br />

resources at the end of the test.<br />

14. Locate the UpdateContactCurrentPointsValidationTest method by double-clicking the comment<br />

TODO: Ex1 - Add a test for UpdateContact when there is a CurrentPoints validation exception<br />

item in the task list.<br />

15. Add an ExpectedException attribute to the method for the DALValidationException type.<br />

16. Delete the comment in the UpdateContactCurrentPointsValidationTest method.<br />

17. Add a unit test to create a Contact object, retrieve that contact from the database, set the<br />

CurrentPoints property of that Contact object to an invalid value, and then update the contact in<br />

the database. Be sure to release all resources at the end of the test.<br />

18. Locate the UpdateContactAtSymbolValidationTest method by double-clicking the comment<br />

TODO: Ex1 - Add a test for UpdateContact when there is a missing @ sign in the e-mail<br />

address validation exception item in the task list.<br />

19. Add an ExpectedException attribute to the method for the DALValidationException type.<br />

20. Delete the comment in the UpdateContactAtSymbolValidationTest method.<br />

21. Add a unit test to create a Contact object, retrieve that contact from the database, set the<br />

EmailAddress property of the Contact object to an invalid value <strong>with</strong> a missing @ symbol, and then<br />

update the contact in the database. Be sure to release all resources at the end of the test.<br />

22. Locate the UpdateContactPeriodValidationTest method by double-clicking the comment TODO:<br />

Ex1 - Add a test for UpdateContact when there is a missing period in the e-mail address<br />

validation exception item in the task list.<br />

23. Add an ExpectedException attribute to the method for the DALValidationException type.<br />

24. Delete the comment in the UpdateContactPeriodValidationTest method.<br />

25. Add a unit test to create a Contact object, retrieve that contact from the database , set the<br />

EmailAddress property of the Contact object to an invalid value <strong>with</strong> a missing period, and then add<br />

the contact to the database. Be sure to release all resources at the end of the test.<br />

26. Save the <strong>Data</strong><strong>Access</strong>LayerTest code file.<br />

Task 10: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application in Debug mode.<br />

3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into<br />

the data grid. Verify that the application functions as expected.<br />

4. Close the application.<br />

5. Run all of the tests in the solution.<br />

6. Verify that all of the tests succeed.<br />

7. Close the solution.


7-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 2: Creating Custom Entity Classes<br />

Scenario<br />

In this exercise, you will modify an existing business class that models customers and add functionality<br />

that enables it to operate as an entity class. You will inherit from the EntityObject class, and will add<br />

scalar and navigational properties that are exposed to the EDM. You will replace the existing Contact<br />

entity class in the data access layer <strong>with</strong> this custom implementation.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Remove the existing Contact class from the DAL project.<br />

3. Add the existing business class to the DAL project.<br />

4. Modify the business class to operate as an entity class.<br />

5. Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class (for <strong>Visual</strong> Basic only).<br />

6. Modify the user interface to catch the validation exception.<br />

7. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

1. Open the DAL solution in the E:\Labfiles\Lab07\VB\Ex2\Starter or E:\Labfiles\Lab07\CS\Ex2\Starter<br />

folder.<br />

2. If you are using <strong>Visual</strong> C#, if a Problem Loading message is displayed, on the Build menu, click<br />

Rebuild Solution, and then in the designer pane, click Reload the designer.<br />

Task 2: Remove the existing Contact class from the DAL project<br />

• Open AdventureWorksEDM.Designer.cs or AdventureWorksEDM.Designer.vb, and then in the Entities<br />

region, comment out all of the Contact partial class.<br />

Task 3: Add the existing business class to the DAL project<br />

1. Add the businessLogicCustomer.cs or businessLogicCustomer.vb file in the<br />

E:\Labfiles\Lab07\CS\Ex2\Starter or E:\Labfiles\Lab07\VB\Ex2\Starter folder to the DAL project.<br />

2. If you are using <strong>Visual</strong> Basic, rename the businessLogicCustomer class file and class to Contact.vb and<br />

Contact.<br />

3. If you are using <strong>Visual</strong> C#, rename the businessLogicCustomer class file and class to Contact.cs and<br />

Contact.<br />

Task 4: Modify the business class to operate as an entity class<br />

1. In the Contact class, add using statements for the following namespaces:<br />

• System.<strong>Data</strong><br />

• System.<strong>Data</strong>.Objects.<strong>Data</strong>Classes<br />

• System.<strong>Data</strong>.Metadata.Edm<br />

2. In the Contact class, modify the class definition to inherit from EntityObject.<br />

3. In the Contact class, use the EdmEntityType attribute to link the class to the Contact entity in the<br />

AdventureWorksModel namespace.<br />

4. Use the EdmScalarProperty attribute to configure the entity properties in the class, as the following<br />

table shows.


Property name EntityKeyProperty IsNullable<br />

ContactID true false<br />

NameStyle false false<br />

Title false true<br />

FirstName false false<br />

MiddleName false true<br />

LastName false false<br />

Suffix false true<br />

EmailAddress false true<br />

EmailPromotion false false<br />

Phone false true<br />

PasswordHash false false<br />

PasswordSalt false false<br />

AdditionalContactInfo false true<br />

rowguid false false<br />

ModifiedDate false false<br />

CurrentPoints false false<br />

Customizing Entities and Building Custom Entity Classes 7-43<br />

5. Modify the Set statements for each property to notify the change tracker when a property change is<br />

pending and then completed, and to use the SetValidValue method of the StructuralObject object<br />

to change the property value.<br />

6. Add navigation properties to link the Contact entity to the SalesOrderHeader, StoreContact, and<br />

RewardsClaimed entities.<br />

7. If you are using <strong>Visual</strong> C#, build the solution and correct any errors.<br />

Task 5: Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class<br />

(for <strong>Visual</strong> Basic only)<br />

1. Open the AdventureWorksEDM.Designer.vb file<br />

2. At the top of the file, if it is not already present, add a statement to bring the DAL namespace into<br />

scope.<br />

3. Update code that references AdventureWorksModel.Contact to reference DAL.Contact.<br />

4. Build the solution and correct any errors.<br />

Task 6: Modify the user interface to catch the validation exception<br />

1. In CustomerWindow.xaml or CustomerAddWindow.xaml, add the ExceptionValidationRule rule to<br />

the binding validation rules for the CurrentPoints text box to catch the validation exception.<br />

2. In CustomerWindow.xaml or CustomerAddWindow.xaml, add the binding exception validation rule<br />

for the EmailAddress text box to catch the validation exception and change the Style attribute of the<br />

text box to display errors.


7-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 7: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application <strong>with</strong>out debugging.<br />

3. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into<br />

the data grid. Verify that the application functions as expected.<br />

4. Close the application.<br />

5. Run all of the tests in the solution.<br />

6. Verify that all of the tests succeed.<br />

7. Save and close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


Lab Review<br />

Customizing Entities and Building Custom Entity Classes 7-45<br />

Review Questions<br />

1. In a T4 template, what does the line of code that defines an entity begin <strong>with</strong>?<br />

2. When creating your own custom entity classes, what method should you call to notify the change<br />

tracker that a property is about to change?


7-46 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. When you use generated entity classes, how can you write additional code to validate data in the<br />

entities?<br />

2. Where can you write code to execute when a navigation property changes?<br />

3. How can you write code for existing types <strong>with</strong>out recompiling the existing type or deriving from it?<br />

4. What happens to the code behind the model designer when you add an ADO.NET EntityObject<br />

Generator item to a project?<br />

5. In a T4 template, what happens to code that is surrounded by tags?<br />

6. In a T4 template, what happens to code that is not surrounded by tags?<br />

7. What attribute do you add to a class definition to identify it as an entity class?<br />

Best Practices Related to Overriding Generated Classes<br />

Supplement or modify the following best practices for your own work situations:<br />

• Do not add custom logic to generated entities. Instead, use partial classes to ensure that the code is<br />

not overwritten when the classes are regenerated.<br />

• Use partial methods to populate calculated columns in the user interface. In this way, you can avoid<br />

performing a round trip to save the data and retrieve the calculated column.<br />

• Use extension methods to enable IntelliSense support for custom methods.<br />

Best Practices Related to Using Templates to Customize Entities<br />

Supplement or modify the following best practices for your own work situations:<br />

• Declare template methods as partial so that you can implement them outside the generated class.<br />

Best Practices Related to Creating and Using Custom Entity Classes<br />

Supplement or modify the following best practices for your own work situations:


Customizing Entities and Building Custom Entity Classes 7-47<br />

• Use the EntityObject.ReportPropertyChanging and EntityObject.ReportPropertyChanged<br />

methods in your property set methods to ensure that the change tracker is notified of the changes.<br />

• Use the StructuralObject.SetValidValue method to change properties to ensure that the change is<br />

notified to all relevant objects and updated in the entity and user interface.<br />

• Use the tag to flag validation errors in a WPF user interface.


7-48 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010


Module 8<br />

Using POCO Classes <strong>with</strong> the Entity Framework<br />

Contents:<br />

Lesson 1: Requirements for POCO Classes 8-3<br />

Lesson 2: POCO Classes and Lazy Loading 8-10<br />

Lesson 3: POCO Classes and Change Tracking 8-15<br />

Lesson 4: Extending Entity Types 8-20<br />

Lab: Using POCO Classes <strong>with</strong> the Entity Framework 8-25<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-1


8-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

This module introduces the ways in which you can define custom entity classes in your Entity Framework<br />

application. By default, <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® generates a set of entity classes for you from the Entity<br />

<strong>Data</strong> Model (EDM). Instead of these generated classes, you may want to use an existing set of plain-old<br />

CLR object (POCO) business classes in your application. You can also extend the generated entity classes<br />

to add custom business functionality to your entity objects.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• List the requirements that your POCO classes must meet.<br />

• Create POCO entities that support automatic lazy loading.<br />

• Create POCO entities that support automatic change tracking.<br />

• Describe the options for using interfaces and inheritance to create custom entity objects.


Lesson 1<br />

Requirements for POCO Classes<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-3<br />

There are specific requirements that POCO entity classes must meet for them to work <strong>with</strong> the Entity<br />

Framework. There are also further requirements if you want your POCO entity classes to support lazy<br />

loading or change tracking. This lesson describes these requirements and also describes how to define a<br />

custom ObjectContext class that will work <strong>with</strong> your POCO entity classes.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the characteristics of a POCO entity class.<br />

• Create a custom ObjectContext class.<br />

• Switch off automatic object layer generation.


8-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

What Are POCO Entity Classes?<br />

Key Points<br />

POCO entity classes are standard common language runtime (CLR) classes that the Entity Framework can<br />

use in place of the entity classes that are generated from the EDM. POCO classes are useful when you<br />

have a pre-existing set of business classes that you want to reuse in your Entity Framework application, or<br />

when you want to add custom business functionality to your entity classes. An important feature of these<br />

POCO entities is that they have no dependencies on the Entity Framework. For example, Adventure Works<br />

may have an existing set of business classes that represent sales entities such as SalesOrders,<br />

SalesTerritories, and SalesPersons that it wants to reuse in the Rewards application.<br />

To use custom POCO entity classes in place of classes that are generated from the EDM, you must switch<br />

off the automatic generation of classes from the EDM. This procedure is shown later in a demonstration.<br />

The ObjectContext object can manage POCO entity objects in the same way that it manages standard<br />

entity objects: by loading data from the database, modifying data in the cache, and persisting changes<br />

back to the database. If a POCO entity object supports lazy loading or automatic change tracking, the<br />

ObjectContext object creates a proxy object to manage the POCO entity object.<br />

The base requirements that all POCO entity class must meet are:<br />

• The entity type name in the EDM must be the same as the POCO entity class name.<br />

• Each property of the entity type must map to a public property in the POCO entity class. The names<br />

and types of these matching properties should be the same.<br />

To support either lazy loading or automatic change tracking, your POCO entity class must also meet the<br />

following requirements:<br />

• You must declare the class <strong>with</strong> public access.<br />

• You must not mark the class as sealed (Microsoft <strong>Visual</strong> C#®) or NotInheritable (Microsoft <strong>Visual</strong><br />

Basic®).<br />

• You must not mark the class as abstract (<strong>Visual</strong> C#) or MustInherit (<strong>Visual</strong> Basic).


• The class must have a public or protected no-argument constructor.<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-5<br />

• The class must not implement the IEntityWithChangeTracker or IEntityWithRelationships<br />

interfaces.<br />

• You must set the ProxyCreationEnabled property of the ObjectContext object to true. This is the<br />

default.<br />

To ensure that the ObjectContext object creates a proxy object when you create a new POCO entity<br />

object, you must instantiate the entity object by calling the CreateObject method of the ObjectContext<br />

object instead of by using the new operator.<br />

Question: Does the ObjectContext object create proxy objects for all POCO entity objects?


8-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Defining a Custom ObjectContext Class<br />

Key Points<br />

To use your custom POCO entity classes, you must define a custom ObjectContext class that can connect<br />

to your EDM. It should also expose properties that return ObjectSet objects that represent the entity sets<br />

in your EDM.<br />

The following code example shows a custom ObjectContext class that works <strong>with</strong> custom POCO entity<br />

classes. The custom POCO classes are mapped to the SalesOrderHeader, SalesTerritory, and<br />

SalesPerson entities in the EDM.<br />

[<strong>Visual</strong> Basic]<br />

Public Class AdventureWorksContext<br />

Inherits ObjectContext<br />

' ObjectSet variables to hold entity set data.<br />

Private _headers As ObjectSet(Of SalesOrderHeader)<br />

Private _territories As ObjectSet(Of SalesTerritory)<br />

Private _salesPersons As ObjectSet(Of SalesPerson)<br />

' Required public, no-argument constructor.<br />

' Passes connection information to its parent class.<br />

Public Sub New()<br />

MyBase.new("name=AdventureWorksEntities",<br />

"AdventureWorksEntities")<br />

End Sub<br />

' Explicitly enable lazy loading.<br />

Me.ContextOptions.LazyLoadingEnabled = True<br />

' <strong>Access</strong>or for the SalesOrderHeaders entity set.<br />

Public ReadOnly Property SalesOrderHeaders As _<br />

ObjectSet(Of SalesOrderHeader)<br />

Get


If _headers Is Nothing Then<br />

End If<br />

End Get<br />

End Property<br />

_headers = MyBase.CreateObjectSet(<br />

Of SalesOrderHeader)("SalesOrderHeaders")<br />

Return _headers<br />

' <strong>Access</strong>or for the SalesTerritories entity set.<br />

Public ReadOnly Property SalesTerritories As _<br />

ObjectSet(Of SalesTerritory)<br />

Get<br />

If _territories Is Nothing Then<br />

_territories = MyBase.CreateObjectSet(<br />

Of SalesTerritory)("SalesTerritories")<br />

End If<br />

End Get<br />

End Property<br />

Return _territories<br />

' <strong>Access</strong>or for the SalesPersons entity set.<br />

Public ReadOnly Property SalesPersons As _<br />

ObjectSet(Of SalesPerson)<br />

Get<br />

If _salesPersons Is Nothing Then<br />

End If<br />

End Get<br />

End Property<br />

End Class<br />

_territories = MyBase.CreateObjectSet(<br />

Of SalesPersons)("SalesPersons")<br />

Return _salesPersons<br />

[<strong>Visual</strong> C#]<br />

public class AdventureWorksContext : ObjectContext<br />

{<br />

// ObjectSet variables to hold entity set data.<br />

private ObjectSet _headers;<br />

private ObjectSet _territories;<br />

private ObjectSet _salesPersons;<br />

// Required public, no-argument constructor.<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-7


8-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

// Passes connection information to its parent class.<br />

public AdventureWorksContext()<br />

: base("name=AdventureWorksEntities",<br />

"AdventureWorksEntities")<br />

{<br />

// Explicitly enable lazy loading.<br />

this.ContextOptions.LazyLoadingEnabled = true;<br />

}<br />

// <strong>Access</strong>or for the SalesOrderHeaders entity set.<br />

public ObjectSet SalesOrderHeaders<br />

{<br />

get<br />

{<br />

if (_headers == null)<br />

{<br />

_headers = base.CreateObjectSet<br />

("SalesOrderHeaders");<br />

}<br />

return _headers;<br />

}<br />

}<br />

// <strong>Access</strong>or for the SalesTerritories entity set.<br />

public ObjectSet SalesTerritories<br />

{<br />

get<br />

{<br />

if (_territories == null)<br />

{<br />

_territories = base.CreateObjectSet<br />

("SalesTerritories");<br />

}<br />

return _territories;<br />

}<br />

}<br />

// <strong>Access</strong>or for the SalesPersons entity set.<br />

public ObjectSet SalesPersons<br />

{<br />

get<br />

{<br />

if (_salesPersons == null)<br />

{<br />

_territories = base.CreateObjectSet<br />

("SalesPersons");<br />

}<br />

return _salesPersons;<br />

}<br />

}<br />

Question: What other functionality might you add to your custom ObjectContext class?


Demonstration: Switching Off Object Layer Generation<br />

Key Points<br />

• Disable object layer generation from the EDM.<br />

• Rebuild a solution to remove the generated entity classes.<br />

Demonstration Steps<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-9<br />

1. Log on to the 10265A-GEN-DEV-08 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration.<br />

3. Start <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

4. Open the ObjectLayerGeneration solution.<br />

5. Open the AdventureWorksArchivedEDM.edmx file and set the Code Generation Strategy property<br />

to None.<br />

6. Save the AdventureWorksArchivedEDM.edmx file and build the solution.<br />

Question: Why do you need to disable object layer generation when you are using custom POCO entity<br />

classes?


8-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

POCO Classes and Lazy Loading<br />

If you are using custom POCO entity classes, you must design the classes to support lazy loading, or<br />

explicitly load entity objects in your data access when they are needed. In this lesson, you will learn how<br />

to support lazy loading when you are using custom POCO entity classes.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the requirements for lazy loading.<br />

• Explicitly load entity objects.


Requirements for Lazy Loading<br />

Key Points<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-11<br />

Lazy loading is a feature of the Entity Framework that enables the ObjectContext object to load related<br />

entity objects automatically when you access them. For example, one of the properties of a<br />

SalesOrderHeader entity object is a SalesPerson object. If you run a query that loads a<br />

SalesOrderHeader entity object, the query does not load the related SalesPerson object. When you first<br />

access the SalesPerson object through the SalesPerson property of the SalesOrderHeader object, this<br />

triggers the ObjectContext object to load the SalesPerson object.<br />

For lazy loading to work <strong>with</strong> your custom POCO entity classes, two things must be true:<br />

1. The LazyLoadingEnabled property of your ObjectContext object must be set to true.<br />

2. In addition to the requirements for POCO proxy generation that the previous lesson listed, your<br />

custom POCO entity class must meet the following requirements:<br />

• For every navigation property of the entity in the EDM, there must be a navigation property <strong>with</strong><br />

the same name in the POCO entity class.<br />

• The get accessor of every navigation property must be declared as public and virtual (<strong>Visual</strong><br />

C#), or Overridable (<strong>Visual</strong> Basic).<br />

The following code example shows how the SalesPerson navigation property is defined in the<br />

SalesOrderHeader POCO entity class.<br />

[<strong>Visual</strong> Basic]<br />

Public Overridable Property SalesPerson As SalesPerson<br />

[<strong>Visual</strong> C#]<br />

public virtual SalesPerson SalesPerson { get; set; }


8-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Question: Which class implements lazy loading for your POCO object?


Explicitly Loading POCO Entities<br />

Key Points<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-13<br />

If your custom POCO entity class does not support lazy loading, you will need to explicitly load any<br />

related POCO entity objects before you use them.<br />

Currently, the data access layer in the AdventureWorks application contains code that relies on lazy<br />

loading, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Dim orders As ObjectQuery(Of SalesOrderHeader) =<br />

entities.SalesOrderHeaders<br />

Dim query = From o In orders<br />

Where o.OrderID = orderID<br />

Select o<br />

SalesOrderHeader(order = query.First())<br />

' The next line relies on lazy loading.<br />

Dim salesPerson As SalesPerson = order.SalesPerson<br />

[<strong>Visual</strong> C#]<br />

ObjectQuery orders = entities.SalesOrderHeaders;<br />

var query = from o in orders<br />

where o.OrderID == orderID<br />

select o;<br />

SalesOrderHeader order = query.First();<br />

// The next line relies on lazy loading.<br />

SalesPerson salesPerson = order.SalesPerson;


8-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

If the SalesOrderHeader POCO entity class does not support lazy loading, you must modify the code as<br />

the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Dim orders As ObjectQuery(Of SalesOrderHeader) =<br />

entities.SalesOrderHeaders<br />

Dim query = From o In orders<br />

Where o.OrderID = orderID<br />

Select o<br />

SalesOrderHeader(order = query.First())<br />

' Explicitly load the SalesPerson entity object.<br />

entities.LoadProperty(order, Function(o) o.SalesPerson)<br />

Dim salesPerson As SalesPerson = order.SalesPerson<br />

[<strong>Visual</strong> C#]<br />

ObjectQuery orders = entities.SalesOrderHeaders;<br />

var query = from o in orders<br />

where o.OrderID == orderID<br />

select o;<br />

SalesOrderHeader order = query.First();<br />

// Explicitly load the SalesPerson entity object.<br />

entities.LoadProperty(order, o => o.SalesPerson);<br />

SalesPerson salesPerson = order.SalesPerson;<br />

Note: As an alternative to lazy loading, you can use the Include method on an ObjectQuery<br />

object to load objects as a part of a query.<br />

Question: There is an overloaded version of the LoadProperty method that takes a third parameter of<br />

type MergeOption. What behavior does this third parameter control?<br />

Additional Reading<br />

For more information about loading related objects, see the Loading Related Objects (Entity Framework)<br />

page at http://go.microsoft.com/fwlink/?LinkID=194058.


Lesson 3<br />

POCO Classes and Change Tracking<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-15<br />

When you are using POCO entity classes, you must either design your entity classes to support change<br />

tracking, or explicitly check for changes in your entity objects. In this lesson, you will learn how to manage<br />

change tracking when you are using custom POCO entity classes.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• List the requirements that custom POCO entity classes must meet to support automatic change<br />

tracking.<br />

• Manually detect changes in your POCO entity objects.


8-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Requirements for Change Tracking<br />

Key Points<br />

The Entity Framework tracks changes to entity objects by using the ObjectStateManager class. The Entity<br />

Framework uses the ObjectStateManager class to track the changes that it must persist to the database<br />

when your application calls the SaveChanges method. The change tracking information also enables the<br />

Entity Framework to detect concurrency issues when it is saving changes.<br />

When your application uses POCO entity classes, there are two ways that the Entity Framework can track<br />

changes to entity objects:<br />

• If your entity object has a proxy object, the proxy object can perform the change tracking function.<br />

• If there is no proxy object, you must perform the change tracking manually by using snapshots.<br />

The Entity Framework can only create the proxy objects that perform the change tracking function if your<br />

POCO entity classes meet the following requirements in addition to the base requirements for POCO<br />

proxy object creation:<br />

• Each property of the entity class that is mapped to a property of the entity type in the EDM must be<br />

declared by using public and virtual (<strong>Visual</strong> C#) or Overridable (<strong>Visual</strong> Basic) get and set accessors.<br />

• Every navigation property of the entity class that represents the "many" end of a relationship must<br />

return a type that implements the ICollection interface, where T is the type of the object at the<br />

other end of the relationship.<br />

• If you want the Entity Framework to create a proxy object along <strong>with</strong> your entity object, use the<br />

CreateObject method on the ObjectContext object when you create a new entity object, instead of<br />

the new operator.<br />

If your POCO entity classes meet these requirements, you do not need to make any changes to the code<br />

in your data access layer to support their use.


Using POCO Classes <strong>with</strong> the Entity Framework 8-17<br />

Question: What are the requirements that relate to the constructor of a POCO entity class if the class<br />

must support proxy creation?<br />

Additional Reading<br />

For more information about identifying proxy objects, see the How to: Identify that a POCO Entity is a<br />

Proxy (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194059.


8-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Tracking Changes in POCO Entity Objects by Using Snapshots<br />

Key Points<br />

If the Entity Framework cannot create a proxy object for your POCO entity object, the Entity Framework<br />

cannot perform automatic change tracking. Otherwise, you must detect any changes in your entity object<br />

by calling the DetectChanges method of the ObjectContext object.<br />

If there is no proxy object, when an entity object is attached to the ObjectContext object, the Entity<br />

Framework takes a snapshot of all of the property values of the entity object. When you call the<br />

DetectChanges method, the Entity Framework updates the information in the ObjectStateManager<br />

object by comparing the current values of the properties of the entity object <strong>with</strong> the values in the<br />

snapshot.<br />

The no-argument version of the SaveChanges method calls the DetectChanges method before it<br />

processes any data modifications, so in many cases you do not need to include a call to DetectChanges<br />

in your code. However, if you call any of the following methods in your code and you are not using proxy<br />

objects, you should call the DetectChanges method first to ensure that the information that the<br />

ObjectStateManager object maintains is up to date:<br />

• Any query or object load operation that you execute <strong>with</strong> a MergeOption value of<br />

PreserveChanges.<br />

• The AddObject, Attach, AttachTo, DeleteObject, Detach, GetObjectByKey, TryGetObjectByKey,<br />

ApplyCurrentValues, ApplyOriginalValues, Refresh, or ChangeObjectState methods of the<br />

ObjectContext class.<br />

• The GetObjectStateEntry, TryGetObjectStateEntry, GetObjectStateEntries, or<br />

ChangeRelationshipState of the ObjectStateManager class.<br />

• All of the methods of the ObjectStateEntry class.


Using POCO Classes <strong>with</strong> the Entity Framework 8-19<br />

Question: If your POCO entity object has no proxy object, the Entity Framework updates the<br />

ObjectStateManager object when you call the DetectChanges method. If your POCO entity object has a<br />

proxy object, when does the Entity Framework update the ObjectStateManager object?


8-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 4<br />

Extending Entity Types<br />

An alternative to creating POCO entity classes is to extend the classes that the Entity Framework generates<br />

to incorporate additional functionality or business logic. This is an appropriate option if you do not need<br />

to use existing domain classes <strong>with</strong> the Entity Framework. You can also implement the Entity Framework<br />

interfaces directly in your custom classes, but you are strongly recommended to use POCO entity classes<br />

instead.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Extend the entity classes that the Entity Framework generates.<br />

• Understand the roles of the Entity Framework interfaces.


Extending Generated Types<br />

Key Points<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-21<br />

The Entity Framework generates entity classes automatically from the EDM. These entity classes provide<br />

full support for querying and modifying entity data. However, these classes do not include any custom<br />

functionality or business logic.<br />

The Entity Framework generates entity classes as partial classes. This means that you can extend these<br />

classes by adding code to a separate source file that will not be modified when the Entity Framework<br />

refreshes the generated source files.<br />

The following code example shows how to add a ValidatePassword method to the Contact class by<br />

using a partial class.<br />

[<strong>Visual</strong> Basic]<br />

Public Partial Class Contact<br />

Public Function ValidatePassword(ByVal password As String) As Boolean<br />

Dim passwordHash As String = Hashing.CreatePasswordHash(password,<br />

Me._PasswordSalt)<br />

If passwordHash = Me._PasswordHash Then<br />

Return True<br />

Else<br />

Return False<br />

End If<br />

End Function<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public partial class Contact<br />

{


8-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

public bool ValidatePassword(string password)<br />

{<br />

string passwordHash =<br />

Hashing.CreatePasswordHash(password, this._PasswordSalt);<br />

if (passwordHash == this._PasswordHash)<br />

return true;<br />

else<br />

return false;<br />

}<br />

Question: When would you choose to extend a generated entity type instead of defining a POCO entity<br />

class?


Interfaces and Inheritance for Custom Entity Types<br />

Key Points<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-23<br />

You can also create custom entity objects by inheriting from the EntityObject class or by implementing<br />

the IEntityWithChangeTracker and IEntityWithRelationships interfaces. The Entity Framework supports<br />

these approaches for reasons of backward compatibility, and you are recommended to use POCO entity<br />

classes for any new development.<br />

Inheriting from the EntityObject Class<br />

If your custom entity class inherits from the EntityObject class, you must include a call to the<br />

ReportPropertyChanging method before you set a property value, and include a call to<br />

ReportPropertyChanged after you set a property value.<br />

Implementing the Custom <strong>Data</strong> Class Interfaces<br />

If you cannot inherit from the EntityObject class, you can create a custom entity class by implementing<br />

the following three interfaces:<br />

• IEntityWithChangeTracker. This interface is required for change tracking and it enables the Entity<br />

Framework to track changes to the entity object. The IEntityWithChangeTracker interface defines<br />

the SetChangeTracker method, which specifies the IEntityChangeTracker object that is used to<br />

report changes to the Entity Framework.<br />

• IEntityWithKey. This optional interface exposes the entity key to the Entity Framework for improved<br />

performance. The IEntityWithKey interface defines the EntityKey property and the Entity<br />

Framework uses the EntityKey property to manage objects in the ObjectContext object.<br />

• IEntityWithRelationships. This interface is required for entities <strong>with</strong> associations because it enables<br />

the Entity Framework to manage relationships between entity objects. The IEntityWithRelationships<br />

interface defines the RelationshipManager property.


8-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Using the EDM Attributes<br />

In addition to inheriting from the EntityObject class or implementing the custom data class interfaces,<br />

you must use EDM attributes in your custom data class to map class properties to entity properties in the<br />

EDM.<br />

The following table describes these attributes.<br />

Attribute name Description<br />

EdmSchemaAttribute Apply this attribute to an assembly that contains entity<br />

types. It is only required to be applied once, but it may be<br />

applied multiple times.<br />

EdmRelationshipAttribute Apply this attribute at the assembly level <strong>with</strong> one instance<br />

for each association. There can be multiple attributes of this<br />

type in an assembly.<br />

The details for the role of an entity type in a particular<br />

association must match the association that is defined in the<br />

conceptual schema.<br />

EdmEntityTypeAttribute This attribute links a class to an entity type in the EDM.<br />

Apply this attribute to classes that represent entity types.<br />

EdmScalarPropertyAttribute This attribute links a scalar property on a data class to a<br />

property of an entity type or complex type that is defined in<br />

the conceptual model.<br />

Apply this attribute to properties that return scalar types.<br />

Question: Why is it necessary to call the ReportPropertyChanging and ReportPropertyChanged<br />

methods?<br />

Additional Reading<br />

For more information about mapping custom objects to entities, see the How to: Map Custom Objects to<br />

Entities (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194060.<br />

For more information about inheriting from the EntityObject class, see the How to: Inherit from the<br />

EntityObject and ComplexObject Base Classes (Entity Framework) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194061.<br />

For more information about implementing the custom data class interfaces, see the How to: Implement<br />

Custom <strong>Data</strong> Class Interfaces (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194062.


Using POCO Classes <strong>with</strong> the Entity Framework 8-25<br />

Lab: Using POCO Classes <strong>with</strong> the Entity Framework<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Create POCO entity classes that support lazy loading and automatic change tracking.<br />

• Create and use POCO entity classes that do not support lazy loading and automatic change tracking.<br />

Introduction<br />

In this lab, you will disable object layer generation in the EDM. You will then complete the<br />

implementation of the custom POCO entity classes for Adventure Works. You will modify the data access<br />

layer where necessary to work <strong>with</strong> the POCO entity classes.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-08 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


8-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

Adventure Works implements an EDM to support its customer reward program. You have been asked to<br />

modify the data access layer to use custom POCO entity classes that implement some of the business<br />

logic that Adventure Works requires.<br />

You have also been asked to update the data access layer in two stages. In the first stage, you will replace<br />

the generated entity classes <strong>with</strong> simple POCO entity classes that support lazy loading and automatic<br />

change tracking. You will then enhance the POCO entity classes to include additional business logic and<br />

adapt the data access layer to work <strong>with</strong> these enhanced entity classes.


Exercise 1: Using POCO Classes<br />

Scenario<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-27<br />

You have been asked to replace the existing generated entity classes <strong>with</strong> custom POCO entity classes. At<br />

this stage, the POCO classes will support lazy loading and automatic change tracking, but you must create<br />

a new ObjectContext class to load the new custom entity classes. You must also ensure that change<br />

tracking is enabled by instantiating entity objects by using the CreateObject method instead of the new<br />

operator.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the AdventureWorks database for the lab.<br />

2. Open the starter project for this exercise.<br />

3. Disable object layer generation for your EDM.<br />

4. Implement a custom ObjectContext class.<br />

5. Complete the RewardsClaimed class in the AdventureWorks project.<br />

6. Modify the data access layer to work <strong>with</strong> the new POCO classes.<br />

7. Build and test the application.<br />

Task 1: Prepare the AdventureWorks database for the lab<br />

1. Log on to the 10265A-GEN-DEV-08 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run AWReset.bat.<br />

Task 2: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex1\Starter or<br />

E:\Labfiles\Lab08\VB\Ex1\Starter folder.<br />

Task 3: Disable object layer generation for your EDM<br />

1. Open the AdventureWorks EDM model in the ADO.NET Entity <strong>Data</strong> Model Designer (Entity Designer).<br />

2. In the AdventureWorks EDM model, set the Code Generation Strategy property to None.<br />

3. Save the AdventureWorks EDM model.<br />

Task 4: Implement a custom ObjectContext class<br />

1. Review the task list.<br />

2. Open the AdventureWorksContext file by double-clicking the TODO: Ex1 - Add a constructor task<br />

in the task list. This task is located in the AdventureWorksContext class.<br />

3. Immediately after the comment, add a no-argument constructor that enables automatic lazy loading.<br />

The constructor should invoke the base class constructor passing the strings<br />

"name=AdventureWorksEntities" and "AdventureWorksEntities" as parameters.<br />

4. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -<br />

Define the Contacts entity set task in the task list. This task is located in the<br />

AdventureWorksContext class.<br />

5. Immediately after the comment, add a read-only property called Contacts based on the ObjectSet<br />

generic type. Specify Contact as the type parameter for the ObjectSet type. Create the ObjectSet<br />

object if it does not exist by calling the CreateObjectSet method in the base class, and then save the<br />

ObjectSet object in a private field.


8-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

6. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -<br />

Define the RewardsClaimed entity set task in the task list. This task is located in the<br />

AdventureWorksContext class.<br />

7. Immediately after the comment, add a read-only property called RewardsClaimed based on the<br />

ObjectSet generic type. Specify RewardsClaimed as the type parameter for the ObjectSet type.<br />

Create the ObjectSet object if it does not exist by calling the CreateObjectSet method in the base<br />

class, and then save the ObjectSet object in a private field.<br />

8. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -<br />

Define the Rewards entity set task in the task list. This task is located in the<br />

AdventureWorksContext class.<br />

9. Immediately after the comment, add a read-only property called Rewards based on the ObjectSet<br />

generic type. Specify Rewards as the type parameter for the ObjectSet type. Create the ObjectSet<br />

object if it does not exist by calling the CreateObjectSet method in the base class, and then save the<br />

ObjectSet object in a private field.<br />

10. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -<br />

Define the AddToContacts method task in the task list. This task is located in the<br />

AdventureWorksContext class.<br />

11. Immediately after the comment, add a void method called AddToContacts that takes a contact<br />

entity as a parameter. The method should call the AddObject method in the base class to add the<br />

contact entity to the contacts entity set.<br />

12. Locate the next comment in the AdventureWorksContext file by double-clicking the TODO: Ex1 -<br />

Define the AddToRewards method task in the task list. This task is located in the<br />

AdventureWorksContext class.<br />

13. Immediately after the comment, add a void method called AddToRewards that takes a reward entity<br />

as a parameter. The method should call the AddObject method in the base class to add the reward<br />

entity to the rewards entity set.<br />

14. Save the AdventureWorksContext file.<br />

Task 5: Complete the RewardsClaimed class in the AdventureWorks project<br />

1. Review the task list.<br />

2. Open the RewardsClaimed file by double-clicking the TODO: Ex1 - Add virtual public accessors for<br />

every RewardsClaimed entity property task in the task list. This task is located in the<br />

RewardsClaimed class.<br />

3. Immediately after the comment, add a virtual public property for every entity property of the<br />

RewardsClaimed entity object in the EDM.<br />

4. Locate the next comment in the RewardsClaimed file by double-clicking the TODO: Ex1 - Add<br />

virtual public accessors for every RewardsClaimed navigation property task in the task list. This<br />

task is located in the RewardsClaimed class.<br />

5. Immediately after the comment, add a virtual public property for every navigation property of the<br />

RewardsClaimed entity object in the EDM.<br />

6. Save the RewardsClaimed file.<br />

Task 6: Modify the data access layer to work <strong>with</strong> the new POCO classes<br />

1. Review the task list.


Using POCO Classes <strong>with</strong> the Entity Framework 8-29<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex1 - Add a using clause for the<br />

AdventureWorks namespace task in the task list. This task is located near the top of the<br />

<strong>Data</strong><strong>Access</strong>Layer file.<br />

3. Immediately after the comment, add a using statement for the AdventureWorks namespace.<br />

4. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex1 - Use the<br />

custom ObjectContext class task in the task list. This task is located in the SetContext method.<br />

5. Immediately after the comment, modify the next line of code to use the AdventureWorksContext<br />

class instead of the AdventureWorksEntities class.<br />

6. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex1 - Create a<br />

new contact by using the CreateObject method task in the task list. This task is located in the<br />

AddContact method.<br />

7. Immediately after the comment, add code that creates a new contact entity by calling the<br />

CreateObject method. Then, use the Copy method of the contact object to copy the values from the<br />

parameter passed to the AddContact method.<br />

8. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex1 - Create a<br />

new reward by using the CreateObject method task in the task list. This task is located in the<br />

AddReward method.<br />

9. Immediately after the comment, add code that creates a new reward entity by calling the<br />

CreateObject method. Then, use the Copy method of the reward object to copy the values from the<br />

parameter passed to the AddReward method. You must check the type of reward passed as a<br />

parameter to the AddReward method (AdventureWorksReward, SupermarketReward, or<br />

AirMilesReward), and then create the correct reward type.<br />

10. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex1 - Create a<br />

new claim by using the CreateObject method task in the task list. This task is located in the<br />

CreateRewardsClaim method.<br />

11. Immediately after the comment, add code that creates a new RewardsClaimed entity by calling the<br />

CreateObject method. Then, use the Copy method of the RewardsClaimed object to copy the<br />

values from the parameter passed to the CreateRewardsClaim method.<br />

12. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 7: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed.<br />

4. Start the application in Debug mode.<br />

5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into<br />

the data grid. Verify that you can add, search for, and delete customer data and that you can add and<br />

delete claim data.<br />

6. Close the application.<br />

7. Close the solution.<br />

8. Reset the AdventureWorks <strong>Data</strong>base. In the E:\Labfiles folder, run AWReset.bat.


8-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 2: Extending Your POCO Classes<br />

Scenario<br />

You have been asked to enhance the custom POCO entity classes to include some custom business logic.<br />

These changes mean that your custom entity classes no longer support lazy loading or automatic change<br />

tracking, so you must update your data access layer.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Add business operations to the Contact class.<br />

3. Add a business operation to the RewardsClaimed class.<br />

4. Modify the data access layer to work <strong>with</strong> your new POCO entities.<br />

5. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

• Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex2\Starter or<br />

E:\Labfiles\Lab08\VB\Ex2\Starter folder.<br />

Task 2: Add business operations to the Contact class<br />

1. Review the task list.<br />

2. Open the Contact file by double-clicking the TODO: Ex2 - Create the Salt and Hash task in the task<br />

list. This task is located in the Password property.<br />

3. Immediately after the comment, generate a value for the PasswordSalt property by calling the static<br />

CreateSalt method of the Hashing class <strong>with</strong> a parameter value of 5. Then, generate a value for the<br />

PasswordHash property by calling the CreatePasswordHash method of the Hashing class, passing<br />

the password and PasswordSalt value as parameters.<br />

4. Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the<br />

AddRewardClaim method task in the task list. This task is located in the AddRewardClaim method.<br />

5. Immediately after the comment, add code to decrement the CurrentPoints property by the value of<br />

the PointsUsed property of the claim object, set the ModifiedDate property to the current date and<br />

time, and then add the claim object to the _rewardsClaimed list.<br />

6. Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the<br />

RemoveRewardClaim method task in the task list. This task is located in the RemoveRewardClaim<br />

method.<br />

7. Immediately after the comment, add code to increment the CurrentPoints property by the value of<br />

the PointsUsed property of the claim object, set the ModifiedDate property to the current date and<br />

time, and then add the claim object to the _rewardsClaimed list.<br />

8. Save the Contact file.<br />

Task 3: Add a business operation to the RewardsClaimed class<br />

1. Review the task list.<br />

2. Open the RewardsClaimed file by double-clicking the TODO: Ex2 - Implement the ModifyClaim<br />

method task in the task list. This task is located in the ModifyClaim method.<br />

3. Immediately after the comment, add code to perform the following tasks:<br />

a. Increment the CurrentPoints property of the Contact property by the value of the PointsUsed<br />

property of the current claim object.


Using POCO Classes <strong>with</strong> the Entity Framework 8-31<br />

b. Decrement the CurrentPoints property of the Contact property by the value of the pointsUsed<br />

parameter.<br />

c. Assign the rewardID parameter to the RewardID property.<br />

d. Assign the pointsUsed parameter to the PointsUsed property.<br />

4. Save the RewardsClaimed file.<br />

Task 4: Modify the data access layer to work <strong>with</strong> your new POCO entities<br />

1. Review the task list.<br />

2. Open the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Delete the call to the<br />

EncryptPassword method task in the task list. This task is located in the AddContact method.<br />

3. The Contact class now handles password encryption. Delete the line of code after the comment that<br />

calls the EncryptPassword method.<br />

4. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Remove<br />

the EncryptPassword method task in the task list. This task is located in the <strong>Data</strong><strong>Access</strong>Layer class.<br />

5. The password encryption functionality is now in the AdventureWorks project. Delete the whole of the<br />

EncryptPassword method from the <strong>Data</strong><strong>Access</strong>Layer class.<br />

6. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Make sure<br />

that all the claims are loaded task in the task list. This task is located in the DeleteContact method.<br />

7. The new POCO classes do not support automatic lazy loading. Immediately after the comment, add<br />

code to load all of the claims that are related to the contact by using the LoadProperty method.<br />

8. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Load the<br />

contact and then call the AddRewardClaim method task in the task list. This task is located in the<br />

CreateRewardsClaim method.<br />

9. Immediately after the comment, add code to perform the following tasks:<br />

a. Create an EntityKey object for the contact associated <strong>with</strong> the claim.<br />

b. Use the TryGetObjectByKey method to load the contact entity.<br />

c. Use the AddRewardClaim method to add the claim to the contact.<br />

10. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Remove<br />

the claim before you refresh the contact task in the task list. This task is located in the<br />

CreateRewardsClaim method.<br />

11. Immediately after the comment, add code to remove the claim from the contact by calling the<br />

RemoveRewardClaim method.<br />

12. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Use the<br />

AddRewardClaim method task in the task list. This task is located in the CreateRewardsClaim<br />

method.<br />

13. Immediately after the comment, add code to add the claim to the contact by calling the<br />

AddRewardClaim method on the Contact property of the claim variable.<br />

14. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Use the<br />

ModifyClaim business method task in the task list. This task is located in the UpdateRewardsClaim<br />

method.<br />

15. Immediately after the comment, add code to call the ModifyClaim method.


8-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

16. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Give the<br />

original points back to the Contact task in the task list. This task is located in the<br />

UpdateRewardsClaim method.<br />

17. Immediately after the comment, add code to call the ModifyClaim method, passing the<br />

originalPoints variable as the second parameter.<br />

18. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Use the<br />

ModifyClaim method to give the points to the contact task in the task list. This task is located in<br />

the UpdateRewardsClaim method.<br />

19. Immediately after the comment, add code to call the ModifyClaim method, passing the RewardID<br />

property of the rewardClaim object as the first parameter and the PointsUsed property of the<br />

rewardClaim object as the second parameter.<br />

20. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Use the<br />

RemoveRewardClaim method task in the task list. This task is located in the DeleteRewardsClaim<br />

method.<br />

21. Immediately after the comment, add code to call the RemoveRewardClaim method of the<br />

relatedContact object, passing the rewardClaimToDelete object as a parameter.<br />

22. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Add the<br />

claim back while you refresh the contact task in the task list. This task is located in the<br />

DeleteRewardsClaim method.<br />

23. Immediately after the comment, add code to call the AddRewardClaim method of the<br />

relatedContact object, passing the rewardClaimToDelete object as a parameter.<br />

24. Locate the next comment in the <strong>Data</strong><strong>Access</strong>Layer file by double-clicking the TODO: Ex2 - Use the<br />

RemoveRewardClaim method again task in the task list. This task is located in the<br />

DeleteRewardsClaim method.<br />

25. Immediately after the comment, add code to call the RemoveRewardClaim method of the<br />

relatedContact object, passing the rewardClaimToDelete object as a parameter.<br />

26. Save the <strong>Data</strong><strong>Access</strong>Layer file.<br />

Task 5: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed.<br />

4. Start the application in Debug mode.<br />

5. In the AdventureWorks Rewards window, click All Customers to load data from the entity model into<br />

the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add,<br />

modify, and delete Adventure Works reward data.<br />

6. Close the application.<br />

7. Close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


Lab Review<br />

Using POCO Classes <strong>with</strong> the Entity Framework 8-33<br />

Review Questions<br />

1. The POCO entity classes are defined in the AdventureWorks project. Does this project need to<br />

reference any of the Entity Framework assemblies?<br />

2. In Exercise 1, does the Entity Framework create proxy objects for the POCO entity objects?<br />

3. In Exercise 1, why is it necessary to modify the code in the data access layer to call the CreateObject<br />

method?<br />

4. In Exercise 2, why is it not necessary to call the CreateObject method?<br />

5. In Exercise 2, why is it not necessary to call the DetectChanges method in all of the data modification<br />

methods?


8-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. What must the Entity Framework create to enable lazy loading and automatic change tracking for<br />

POCO entity objects?<br />

2. Must your POCO entity classes implement any specific interfaces?<br />

3. How can you load related entity objects if your POCO entity classes do not support lazy loading?<br />

4. Can you have a mixture of generated entity classes and POCO entity classes in your application?<br />

Best Practices Related to Using Custom Entity Classes in Your Entity Framework<br />

Application<br />

Supplement or modify the following best practices for your own work situations:<br />

• Use custom POCO entity classes to enable the reuse of existing business classes.<br />

• Use custom POCO entity classes instead of implementing the custom data class interfaces or<br />

extending the EntityObject class.<br />

• Add additional logic to the generated entity classes by placing code in partial classes.


Module 9<br />

Building an N-Tier Solution by Using the Entity Framework 9-1<br />

Building an N-Tier Solution by Using the Entity Framework<br />

Contents:<br />

Lesson 1: Designing an N-Tier Solution 9-3<br />

Lesson 2: Defining Operations and Implementing <strong>Data</strong> Transport Structures 9-10<br />

Lesson 3: Protecting <strong>Data</strong> and Operations 9-24<br />

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


9-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

This module introduces the architectural issues that you may encounter when you build an n-tier solution<br />

and explains how you can solve these problems by using the Entity Framework. N-tier architectures are<br />

commonly used to build enterprise-class business applications because they enable you to build<br />

applications that are scalable, secure, maintainable, and interoperable.<br />

An n-tier solution can use other technologies, such as ASP.NET and Windows® Communication<br />

Foundation (WCF), in addition to the Entity Framework. This module does not attempt to cover these<br />

additional technologies in detail; instead; it focuses on the role of the Entity Framework and how the<br />

Entity Framework interacts <strong>with</strong> these technologies. This module will discuss how you can build an n-tier<br />

application that supports query operations. A later module will explain how you can support data<br />

modifications in an n-tier application.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Discuss the issues and strategies that are relevant to building n-tier applications.<br />

• Understand the key components that you must create to implement an n-tier application.<br />

• Protect operations and data in an n-tier application.


Lesson 1<br />

Designing an N-Tier Solution<br />

Building an N-Tier Solution by Using the Entity Framework 9-3<br />

You must consider several issues when you design an n-tier application. This lesson introduces some of<br />

these issues and discusses some of the alternative strategies that you can choose between when you<br />

decide how to transport data between tiers.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• List the key issues that you must be aware of when you design n-tier applications.<br />

• Describe the common patterns for transporting data between tiers.<br />

• List the options for hosting your tiers.


9-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Architectural Issues<br />

Key Points<br />

This module focuses on the issues concerned <strong>with</strong> building the data access layer tier in an n-tier<br />

application; however, many of these issues will be relevant to the design of other tiers. There is no one<br />

right way to design a data access layer; the requirements will be specific to the application, but you<br />

should treat the guidance that this module provides as an indication of best practice. Designers frequently<br />

adopt n-tier architectures because they help to deliver requirements such as scalability, maintainability,<br />

security, and interoperability to the solution.<br />

The following questions are examples of the type of questions that you must answer when you design an<br />

n-tier application.<br />

What Operations Will the Client Applications Require?<br />

The data access layer tier exists to provide client applications <strong>with</strong> access to the data that they require. The<br />

client application may display the data in the user interface (UI), for example, to allow a customer to<br />

browse the products that Adventure Works sells. Alternatively, the client application may control a part of<br />

a business process, such as allocating a reward to a customer. The design goals are to identify the<br />

operations that the various client applications must be able to invoke and to ensure that you can extend<br />

the set of available operations in the future. To help to ensure that the data access layer exposes the<br />

correct set of operations, you should identify operations based on their business role. For example, for<br />

Adventure Works, a set of business-centric operations that enable users to place, retrieve, or cancel orders<br />

is better than a set of data-centric operations that enable users to retrieve or move data.<br />

How Should You Partition the Operations in the <strong>Data</strong> <strong>Access</strong> Layer?<br />

Not all clients will require access to all of the operations exposed by the data access layer. In some<br />

scenarios, it may not be desirable for certain clients to have access to certain operations; for example,<br />

security considerations may prohibit access. In this case, you must identify the operations that particular<br />

interfaces in the data access layer expose.


Building an N-Tier Solution by Using the Entity Framework 9-5<br />

What <strong>Data</strong> Will the Client Applications Require?<br />

Client applications will not need to access all of the data in the database. You should identify the data that<br />

client applications will require and ensure that the data access layer only exposes this data. You should<br />

also consider whether all client applications require access to all data; if not, examine ways to partition the<br />

data. It is a waste of effort, and a potential security risk, to expose more data than is necessary.<br />

How Can You Minimize Network Overhead?<br />

N-tier applications, by definition, must move data over a network. There is a trade-off between making a<br />

large number of requests that transfer small amounts of data and making just a few requests that transfer<br />

large amounts of data. An application that uses a 'chatty' interface will suffer from performance problems<br />

that result from the overhead of a large number of small requests and the network latency on all of those<br />

requests. Fetching large quantities of data can swamp the network and often moves data around<br />

unnecessarily. Again, you should create business-centric operations to help you to ensure that you move<br />

the right amount of data across the network.<br />

How Should You Transport <strong>Data</strong> over the Network?<br />

The format of the data that you move over the network is important. The objects that you use to move<br />

the data must be serializable, but you must also consider how much functionality you should build into<br />

these objects and what dependencies these objects may introduce into the client application. The<br />

functionality of these objects will be discussed later in this module, but an important goal should be to<br />

avoid any tight coupling of the client application to the underlying data store.<br />

How Should You Host the <strong>Data</strong> <strong>Access</strong> Layer?<br />

This question will be discussed in more detail later in this module.<br />

How Can You Maximize the Scalability of the <strong>Data</strong> <strong>Access</strong> Layer?<br />

This is a key goal for n-tier architectures. The most significant design decision that you can make in the<br />

data access layer is to make this tier stateless. Each operation that you define in the data access layer<br />

should be self-contained <strong>with</strong>out any dependencies on any other public operations. This approach<br />

maximizes the opportunities to recycle and reuse objects in the data access layer tier. If any of your<br />

operations require state, you should consider very carefully where that state data should be maintained.<br />

Question: One of the goals of n-tier architectures is to promote interoperability. Which of the issues that<br />

are discussed in this topic will have the most impact on interoperability <strong>with</strong> the data access layer?<br />

Additional Reading<br />

For more information about application architecture, see the Microsoft Application Architecture Guide,<br />

2nd Edition page at http://go.microsoft.com/fwlink/?LinkID=194063.


9-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Strategies for N-Tier Applications<br />

Key Points<br />

In an n-tier application, the data access layer must deliver data to the client application over the network,<br />

and if the client modifies the data, the client must send the data back to the data access layer. To move<br />

the data over the network, the format that you use to represent the data must be serializable; however,<br />

you must make additional choices about the format, and these choices will have a significant impact on<br />

the design of the client and the data access layer.<br />

This topic outlines and evaluates four alternative strategies. You can adopt any of these strategies for use<br />

in an application that incorporates the Entity Framework.<br />

Change Set Objects<br />

A change set object can hold any data from the database, so you can populate it <strong>with</strong> the data that you<br />

require for the particular business-centric operation that you implement. A change set will often<br />

implement its own change-tracking mechanism, so the client can make changes to the contents of the<br />

change set before it returns the change set to the data access layer. The <strong>Data</strong>Set class in ADO.NET is a<br />

good example of a type that you can use as a change set. You can fill a <strong>Data</strong>Set object <strong>with</strong> <strong>Data</strong>Table<br />

objects that contain the data that an operation requires, and the <strong>Data</strong>Set and <strong>Data</strong>Table classes have a<br />

comprehensive application programming interface (API) that enables you to work <strong>with</strong> the data held in<br />

the <strong>Data</strong>Table objects.<br />

Although the <strong>Data</strong>Set class is very easy to work <strong>with</strong>, <strong>Data</strong>Set objects are large, they are not<br />

interoperable, and they often lead to tight coupling between the client and the data access layer. You<br />

must also be careful to validate the contents of a <strong>Data</strong>Set object in the data access layer when it is<br />

received from a client.<br />

<strong>Data</strong> Transfer Objects<br />

Change sets are large and complex objects, but data transfer objects (DTOs) are simple and compact<br />

objects. They contain just the data that you need to transfer between the tiers, but both the client and the


Building an N-Tier Solution by Using the Entity Framework 9-7<br />

data access layer have their own object representations. This pattern results in a very loose coupling<br />

between the client and the data access layer, but it requires the most development effort.<br />

Simple Entities<br />

Simple entities (SEs) enable you to use the same object representation for both the data access layer and<br />

the client. SEs should be as simple as possible (unlike change sets) and represent simple entity data. A<br />

client application can make changes to entity properties before it sends those entities back to the data<br />

access layer. The major advantage of this strategy is simplicity. This does make it more difficult to<br />

implement scenarios that are more complex especially if multiple entities are involved. For example, in the<br />

AdventureWorks EDM, if you modify a reward entity, this may affect rewards claimed entities and contact<br />

entities. Using SEs in this type of scenario may lead the application to become too 'chatty.'<br />

Self-Tracking Entities<br />

Self-tracking entities (STEs) are SEs <strong>with</strong> tracking capabilities. They solve some of the problems of SEs by<br />

building more intelligence into the entity objects. STEs keep track of their own changes and changes to<br />

related objects. This helps to reduce the number of interactions between the client and the data access<br />

layer, because the client can send a single message that includes multiple related changes to the data<br />

access layer. Ideally, you implement STEs as plain-old CLR objects (POCOs), so that you minimize the<br />

dependencies in the client.<br />

STEs are specific to an entity type; therefore, they are more compact than change sets and can include<br />

custom validation rules.<br />

Question: What type of projects would benefit most from the use of DTOs?


9-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Hosting Your Service<br />

Key Points<br />

A data access tier in an n-tier solution typically exposes its functionality to clients over a network. You<br />

have a number of choices about how you can host your data access layer.<br />

Windows Communication Foundation<br />

This section discusses the four choices that WCF offers for hosting your data access layer. An advantage of<br />

WCF is that the code that you write to implement a service will not have to change significantly if you<br />

decide to change your hosting option later.<br />

Internet Information Services<br />

Internet Information Services (IIS) is a highly scalable and highly available platform. Hosting a WCF service<br />

in IIS means that you can immediately use all of the functionality that IIS offers, including security,<br />

management tools, and monitoring. You do not need to write any hosting code, but you must use the<br />

HTTP transport for your application.<br />

Windows Process Activation Service<br />

Windows Process Activation Service (WAS) is a process-activation mechanism that was introduced <strong>with</strong><br />

Windows Server® 2008. It offers the same features as IIS for your WCF service, but removes the<br />

dependency on the HTTP transport. You can use TCP, Message Queuing, or named pipes as your<br />

transport mechanism.<br />

Managed Windows Services<br />

You can host your application as a Windows service; the operating system controls the lifetime of this<br />

service. You must write some hosting and installation code.<br />

Self-Hosting<br />

You can write your own managed hosting application to host your WCF service. This is the most flexible<br />

option, but can require that you write an extensive volume of code to provide robust hosting facilities.


Windows Azure<br />

Building an N-Tier Solution by Using the Entity Framework 9-9<br />

Windows Azure is a cloud-based operating system that you can use to host services. You can use a<br />

cloud-based version of IIS to host your services. Alternatively, you can use the cloud-based AppFabric<br />

Service Bus to provide a hosted, secure, and widely accessible platform for service publishing.<br />

Microsoft .NET Remoting<br />

<strong>Microsoft®</strong> .NET Remoting was the precursor to WCF. It is now regarded as a legacy technology that<br />

should not be used for new developments. You can use a wide variety of deployment models and choose<br />

from a wide variety of message formats.<br />

Question: What are the advantages of hosting your service as a WCF service in IIS?<br />

Additional Reading<br />

For more information about Windows Azure, see the About Windows Azure page at<br />

http://go.microsoft.com/fwlink/?LinkID=194064.<br />

For more information about the AppFabric Service Bus, see the AppFabric Service Bus page at<br />

http://go.microsoft.com/fwlink/?LinkID=194065.<br />

For more information about Microsoft SQL Azure, see the SQL Azure page at<br />

http://go.microsoft.com/fwlink/?LinkID=194066.<br />

For more information about .NET Remoting, see the .NET Remoting page at<br />

http://go.microsoft.com/fwlink/?LinkID=194067.


9-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

Defining Operations and Implementing <strong>Data</strong><br />

Transport Structures<br />

You can use the ADO.NET Self-Tracking Entity Generator Text Template Transformation Toolkit (T4)<br />

template to easily generate classes implementing the STE pattern. The generated classes are simple POCO<br />

classes that can track changes to their data. The template is optimized for use <strong>with</strong> WCF, but there are<br />

choices that you must make about the configuration of the service, such as where to place your STE<br />

classes. After you have created and configured your service, there are choices to make about how to<br />

optimize the service and how to manage relationships.<br />

This lesson explains how to use the ADO.NET Self-Tracking Entity Generator T4 template. It also explains<br />

how to optimize your service when you use WCF hosting and how to manage more complex query<br />

operations.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Generate an STE by using the ADO.NET Self-Tracking Entity Generator T4 template.<br />

• Identify the differences among implementations of DTOs, SEs, and STEs.<br />

• Choose the appropriate configuration options for your WCF service.<br />

• Manage parent/child relationships between entity objects.<br />

• Describe the structure of an n-tier solution.


Building an N-Tier Solution by Using the Entity Framework 9-11<br />

Demonstration: Using the Self-Tracking Entity T4 Template<br />

Key Points<br />

• Use the ADO.NET Self-Tracking Entity Generator T4 template to generate the code for an STE.<br />

• Examine the code that the T4 template generates for the ObjectContext class and the entity classes.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-09 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration.<br />

3. Start Microsoft <strong>Visual</strong> <strong>Studio</strong>® 2010.<br />

4. Open the STEDemo solution.<br />

5. Add an ADO.NET Self-Tracking Entity Generator.<br />

6. Review the code generated for the ObjectContext class.<br />

7. Review a generated entity class.<br />

8. Save the project and close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: What functionality is implemented in the AdventureWorksModel.Context.Extensions.cs file?


9-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

<strong>Data</strong> Transport Structures<br />

Key Points<br />

In the previous demonstration, you saw the code that was generated by using a T4 template to create an<br />

STE. A WCF service can serialize this STE to a client, and the client can read the properties of the STE<br />

object. In a later module, you will see how the client can modify the data in the STE and then send the<br />

modified STE back to the data access layer, where you can update the database.<br />

If the client does not modify the data, you can use a simpler data structure to return data to the client.<br />

Simple Entity Objects<br />

In an earlier module, you saw how to create POCOs that you can use as entity objects. If they are<br />

serializable, you can use these POCOs to transport data to the client application. The following code<br />

example shows a class that you can use to represent contact entities.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Class Contact<br />

Public Property ContactID As String<br />

Public Property NameStyle As Boolean<br />

Public Property Title As String<br />

Public Property FirstName As String<br />

Public Property MiddleName As String<br />

Public Property LastName As String<br />

Public Property Suffix As String<br />

Public Property EmailAddress As String<br />

Public Property EmailPromotion As Integer<br />

Public Property Phone As String<br />

Public Property PasswordHash As String<br />

Public Property PasswordSalt As String<br />

Public Property AdditionalContactInfo As String<br />

Public Property rowguid As Guid<br />

Public Property ModifiedDate As DateTime<br />

Public Property CurrentPoints As Integer


Private _rewardsClaimed As New List(Of RewardsClaimed)<br />

Public Property RewardsClaimed As List(Of RewardsClaimed)<br />

Get<br />

Return _rewardsClaimed<br />

End Get<br />

Set(ByVal value As List(Of RewardsClaimed))<br />

_rewardsClaimed = value<br />

End Set<br />

End Property<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

[Serializable]<br />

public class Contact<br />

{<br />

public int ContactID { get; set; }<br />

public bool NameStyle { get; set; }<br />

public string Title { get; set; }<br />

public string FirstName { get; set; }<br />

public string MiddleName { get; set; }<br />

public string LastName { get; set; }<br />

public string Suffix { get; set; }<br />

public string EmailAddress { get; set; }<br />

public int EmailPromotion { get; set; }<br />

public string Phone { get; set; }<br />

public string PasswordHash { get; set; }<br />

public string PasswordSalt { get; set; }<br />

public string AdditionalContactInfo { get; set; }<br />

public Guid rowguid { get; set; }<br />

public DateTime ModifiedDate { get; set; }<br />

public int CurrentPoints { get; set; }<br />

}<br />

private List _rewardsClaimed =<br />

new List();<br />

public List RewardsClaimed<br />

{<br />

get { return _rewardsClaimed; }<br />

set { _rewardsClaimed = value; }<br />

}<br />

Building an N-Tier Solution by Using the Entity Framework 9-13<br />

Note: The additional considerations that you must take into account if your client modifies an SE<br />

are discussed in a later module.<br />

<strong>Data</strong> Transfer Objects<br />

An SE object contains all of the entity properties in its definition. This means that you transport all of an<br />

entity's data over the network, and both the data access layer and the client share the same object<br />

representation. You design your DTOs to transport just the data that each operation requires; this design<br />

decouples the data access layer and the client. There is some additional complexity, because you must<br />

create code that translates between the DTO representation and each tier's entity representation.<br />

The following code example shows a DTO that you can use when the client displays e-mail contact<br />

information.


9-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Class ContactEmail<strong>Data</strong><br />

Public Property ContactID As Integer<br />

Public Property EmailAddress As String<br />

Public Property EmailPromotion As Integer<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

[Serializable]<br />

public class ContactEmail<strong>Data</strong><br />

{<br />

public int ContactID { get; set; }<br />

public string EmailAddress { get; set; }<br />

public int EmailPromotion { get; set; }<br />

}<br />

Question: What advantages does a DTO have over an STE or SE in an n-tier application?


Creating a WCF Service<br />

Key Points<br />

Building an N-Tier Solution by Using the Entity Framework 9-15<br />

WCF offers the most flexible set of options to expose the operations in your data access layer to the client<br />

tier. In fact, the code generated by the ADO.NET Self-Tracking Entity Generator T4 template is optimized<br />

for use <strong>with</strong> WCF. This topic describes the key elements that you must create if you use WCF to host a<br />

data access layer built by using STEs.<br />

A WCF service implements one or more operations. To define a WCF operation, you start <strong>with</strong> a contract.<br />

This contract is an interface that defines the operations that your service exposes, including any fault<br />

information that you may need to return to the client.<br />

The following code example shows an interface that defines two operations that you want to expose from<br />

the Adventure Works data access layer. It also shows how to specify the format of the fault data that you<br />

can use to notify the client of an error.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Class ServiceFault<br />

<br />

Public ExceptionType As String<br />

<br />

Public ExceptionMessage As String<br />

End Class<br />

<br />

Public Interface IRewardsService<br />

<br />


9-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Function GetRewardDetails(ByVal rewardID As Integer) As Reward<br />

<br />

<br />

Function GetRewardsClaimedDetails(ByVal claimID As Integer) _<br />

As RewardsClaimed<br />

End Interface<br />

[<strong>Visual</strong> C#]<br />

[<strong>Data</strong>Contract]<br />

public class ServiceFault<br />

{<br />

[<strong>Data</strong>Member]<br />

public string ExceptionType;<br />

}<br />

[<strong>Data</strong>Member]<br />

public string ExceptionMessage;<br />

[ServiceContract (Name="RewardsWebService",<br />

Namespace="http://microsoft.com")]<br />

public interface IRewardsService<br />

{<br />

[FaultContract(typeof(ServiceFault))]<br />

[OperationContract]<br />

Reward GetRewardDetails(int rewardID);<br />

[FaultContract(typeof(ServiceFault))]<br />

[OperationContract]<br />

RewardsClaimed GetRewardsClaimedDetails(int claimID);<br />

}<br />

Next, you must implement the interface. The following code example shows how you can implement the<br />

operations that are defined in the IRewardsService interface. The Reward and RewardsClaimed types<br />

that the operations return are the entity types that the T4 templates generate. The<br />

InstanceContextMode property of the ServiceBehavior attribute is set to the PerCall value. This value<br />

causes every call from the client to create an instance of the service, which results in a stateless service.<br />

The ConcurrencyMode property is set to the value Multiple to enable multiple threads to access an<br />

instance. The example also shows how to return a fault to the client; an alternative strategy is to log full<br />

details of any errors on the server and minimize the amount of information about the service that you<br />

return to the client.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Class RewardsServiceImpl<br />

Implements IRewardsService<br />

Public Function GetRewardDetails(ByVal rewardID As Integer) As reward Implements<br />

IRewardsService.GetRewardDetails


Dim reward As reward = Nothing<br />

Try<br />

Using context As New AdventureWorksEntities()<br />

Dim matchingRewards As IEnumerable(Of reward) =<br />

From r In context.Rewards<br />

Where r.RewardID = rewardID<br />

Select r<br />

If matchingRewards.Count() > 0 Then<br />

reward = matchingRewards.First()<br />

End If<br />

Return reward<br />

End Using<br />

Catch ex As Exception<br />

Throw New FaultException(Of ServiceFault)(<br />

New ServiceFault() With<br />

{<br />

.ExceptionType = "GetRewardsClaimedDetails",<br />

.ExceptionMessage = "Failed to retrieve reward"<br />

})<br />

End Try<br />

End Function<br />

Building an N-Tier Solution by Using the Entity Framework 9-17<br />

Public Function GetRewardsClaimedDetails(ByVal claimID As Integer) As RewardsClaimed<br />

Implements IRewardsService.GetRewardsClaimedDetails<br />

Dim claim As RewardsClaimed = Nothing<br />

Try<br />

Using context = New AdventureWorksEntities()<br />

{<br />

Dim matchingClaims As IEnumerable(Of RewardsClaimed) =<br />

From c In context.RewardsClaimed<br />

Where c.ClaimID = claimID<br />

Select c<br />

If matchingClaims.Count() > 0 Then<br />

claim = matchingClaims.First()<br />

End If<br />

Return claim<br />

End Using<br />

Catch ex As Exception<br />

End Try<br />

End Function<br />

End Class<br />

Throw New FaultException(Of ServiceFault)(<br />

New ServiceFault() With<br />

{<br />

.ExceptionType = "GetRewardsClaimedDetails",<br />

.ExceptionMessage = "Failed to retrieve claim"<br />

})


9-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> C#]<br />

[ServiceBehavior(Name = "RewardsWebService",<br />

Namespace = "http://microsoft.com",<br />

InstanceContextMode = InstanceContextMode.PerCall,<br />

ConcurrencyMode = ConcurrencyMode.Multiple)]<br />

public class RewardsServiceImpl : IRewardsService<br />

{<br />

public Reward GetRewardDetails(int rewardID)<br />

{<br />

Reward reward = null;<br />

}<br />

try<br />

{<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

IEnumerable matchingRewards =<br />

from r in context.Rewards<br />

where r.RewardID == rewardID<br />

select r;<br />

if (matchingRewards.Count() > 0)<br />

{<br />

reward = matchingRewards.First();<br />

}<br />

return reward;<br />

}<br />

}<br />

catch (Exception ex)<br />

{<br />

throw new FaultException(<br />

new ServiceFault()<br />

{ExceptionType="GetRewardsClaimedDetails",<br />

ExceptionMessage="Failed to retrieve reward"});<br />

}<br />

public RewardsClaimed GetRewardsClaimedDetails(int claimID)<br />

{<br />

RewardsClaimed claim = null;<br />

try<br />

{<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

IEnumerable matchingClaims =<br />

from c in context.RewardsClaimed<br />

where c.ClaimID == claimID<br />

select c;<br />

if (matchingClaims.Count() > 0)<br />

{<br />

claim = matchingClaims.First();<br />

}<br />

return claim;<br />

}<br />

}<br />

catch (Exception ex)<br />

{<br />

throw new FaultException(


}<br />

}<br />

}<br />

new ServiceFault()<br />

{ExceptionType="GetRewardsClaimedDetails",<br />

ExceptionMessage="Failed to retrieve claim"});<br />

Building an N-Tier Solution by Using the Entity Framework 9-19<br />

The service interface and implementation do not include any details about the service infrastructure. You<br />

specify this information in the configuration file for the service. The following code example shows a part<br />

of the web.config file for the service. The configuration data specifies the transport to use, the security<br />

options to enforce, and the end point for the service.<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

One of the architectural goals of building n-tier applications is to decouple the client from the data access<br />

layer. To achieve this goal, you should place your STE classes in a separate assembly that will have no<br />

dependencies on the Entity Framework. You can reference this assembly from the client project to avoid<br />

introducing any dependencies on the Entity Framework. To achieve this, you must select the Reuse types<br />

in specified referenced assemblies option when you add the service reference to the client project. This<br />

prevents the proxy from generating its own types that are based on the service metadata.<br />

Question: What are the consequences of setting the ConcurrencyMode property of the service to the<br />

Single value?<br />

Additional Reading<br />

For more information about serializing entity objects, see the Serializing Objects (Entity Framework) page<br />

at http://go.microsoft.com/fwlink/?LinkID=194068.


9-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

For more information about WCF, see the Fundamental Windows Communication Foundation Concepts<br />

page at http://go.microsoft.com/fwlink/?LinkID=194069.<br />

For more information about WCF sessions and concurrency, see the Sessions, Instancing, and Concurrency<br />

page at http://go.microsoft.com/fwlink/?LinkID=194070.


Working <strong>with</strong> Parent/Child Relationships<br />

Key Points<br />

Building an N-Tier Solution by Using the Entity Framework 9-21<br />

When WCF moves objects over the network, it does so by serializing these objects. Entity objects in the<br />

Entity Framework usually have navigation properties that enable you to navigate to related objects; for<br />

example, a rewards claim entity can have a navigation property that enables you to navigate to the<br />

related contact entity. You have seen in previous modules that the Entity Framework can use lazy loading<br />

to materialize related entities automatically when you reference them. You must be careful that the WCF<br />

serializer does not return more data than expected to the client.<br />

If you use binary serialization and WCF data contract serialization, WCF serializes and transports related<br />

objects to the client, so in this scenario, you should disable automatic lazy loading.<br />

If you use XML serialization, WCF does not serialize related objects.<br />

The ObjectContext class that the T4 templates generate does not enable lazy loading. The following code<br />

example shows the code in the Initialize method that all of the constructors in the ObjectContext class<br />

call.<br />

[<strong>Visual</strong> Basic]<br />

Private Sub Initialize()<br />

' Creating proxies requires the use of the Proxy<strong>Data</strong>ContractResolver and<br />

' may allow lazy loading which can expand the loaded graph during serialization.<br />

ContextOptions.ProxyCreationEnabled = False<br />

AddHandler ObjectMaterialized, AddressOf HandleObjectMaterialized<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

private void Initialize()<br />

{


9-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

// Creating proxies requires the use of the<br />

// Proxy<strong>Data</strong>ContractResolver and<br />

// may allow lazy loading, which can expand the loaded graph<br />

// during serialization.<br />

ContextOptions.ProxyCreationEnabled = false;<br />

ObjectMaterialized +=<br />

new ObjectMaterializedEventHandler(HandleObjectMaterialized);<br />

If you must return related entities to the client, you can explicitly load them by using the Include<br />

operator. The following code example shows how to load the related Reward entity when you query for a<br />

RewardsClaimed entity.<br />

[<strong>Visual</strong> Basic]<br />

Dim matchingClaims As IEnumerable(Of RewardsClaimed) =<br />

From c in context.RewardsClaimed.Include("Reward")<br />

Where c.ClaimID = claimID<br />

Select c<br />

[<strong>Visual</strong> C#]<br />

IEnumerable matchingClaims =<br />

from c in context.RewardsClaimed.Include("Reward")<br />

where c.ClaimID == claimID<br />

select c;<br />

Question: Does the Entity Framework enable lazy loading by default?<br />

Additional Reading<br />

For more information about how the Entity Framework serializes objects, see the Serializing Objects<br />

(Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194071.<br />

For more information about WCF serialization, see the <strong>Data</strong> Transfer and Serialization page at<br />

http://go.microsoft.com/fwlink/?LinkID=194072.<br />

For more information about returning related objects in a query, see the How to: Use Query Paths to<br />

Shape Results (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194073.


Demonstration: Examining an End-to-End Example<br />

Key Points<br />

• Examine a complete end-to-end example of an n-tier solution.<br />

• Organize your application into separate assemblies.<br />

Demonstration Steps<br />

Building an N-Tier Solution by Using the Entity Framework 9-23<br />

1. In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration.<br />

2. Create the virtual directory in IIS for the service.<br />

3. Start <strong>Visual</strong> <strong>Studio</strong> 2010 as an administrator.<br />

4. Open the RewardsDAL solution.<br />

5. Review that the entity classes are now in the RewardsClientLibrary project, and review that they have<br />

been extended by using partial classes.<br />

6. In the RewardsService project, review the contract and implementation of the WCF service.<br />

7. In the RewardsWebService project, review the service configuration.<br />

8. Add a console application project to the solution called RewardsClientApp.<br />

9. Add a reference to the RewardsClientLibrary project.<br />

10. Add a service reference to the Rewards Web service.<br />

11. Add code to invoke the service in Program.cs.<br />

12. Test the client application.<br />

13. Close the application, and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: What assemblies are required by the RewardsClientLibrary project?


9-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 3<br />

Protecting <strong>Data</strong> and Operations<br />

After you have built an n-tier solution, you will need to decide how to protect the data from unauthorized<br />

access and how to protect the application from attack.<br />

This lesson describes how to add security to your n-tier solution. It explains how to use the security<br />

features in WCF to protect operations and data, and how to design your application to <strong>with</strong>stand<br />

common attacks.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Configure authentication and authorization policies for the operations in your data access layer.<br />

• Encrypt the data that your application transfers between the data access layer and the client.<br />

• Protect your application from common attacks.


Authentication and Authorization<br />

Key Points<br />

Building an N-Tier Solution by Using the Entity Framework 9-25<br />

When you define a service by using WCF, you can use the PrincipalPermission attribute to specify the<br />

authorization rules for your service operation. The following code example demonstrates an authorization<br />

rule that restricts access to the GetRewardDetails method to members of the RewardAdmin role.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Function GetRewardDetails(ByVal rewardID As Integer) As Reward<br />

...<br />

End Function<br />

[<strong>Visual</strong> C#]<br />

[PrincipalPermission(SecurityAction.Demand, Role="RewardAdmin")]<br />

public Reward GetRewardDetails(int rewardID)<br />

{<br />

...<br />

}<br />

In an n-tier application, you can collect the authentication data in the client. The following code example<br />

demonstrates how to add the credential data to the service client object.<br />

[<strong>Visual</strong> Basic]<br />

svc = New RewardsWebServiceClient(bindingConfiguration)<br />

svc.ClientCredentials.Windows.ClientCredential.UserName = userName<br />

svc.ClientCredentials.Windows.ClientCredential.Password = password


9-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> C#]<br />

svc = new RewardsWebServiceClient(bindingConfiguration);<br />

svc.ClientCredentials.Windows.ClientCredential.UserName = userName;<br />

svc.ClientCredentials.Windows.ClientCredential.Password = password;<br />

You configure the mechanism used to validate these credentials in the web.config file for the service. You<br />

can use the Windows operating system, a third-party security token service (STS), or a custom<br />

authentication mechanism to perform the authentication.<br />

WCF offers two modes of security that affect how you move credentials over the network. Transport level<br />

security relies on a secure transport such as HTTPS to handle authentication. Message level security uses<br />

the WS-Security standards to encode the credentials in the message itself. The following code example<br />

from the web.config file shows how to specify both transport-based and message-based security for a<br />

service. In this scenario, the transport security provides the integrity and confidentiality, and the message<br />

security handles the credentials transfer.<br />

<br />

<br />

<br />

<br />

<br />

Question: What are the advantages of using message-level security instead of transport-level security to<br />

pass credentials to the data access layer?<br />

Additional Reading<br />

For more information about WCF authentication, see the Authentication page at<br />

http://go.microsoft.com/fwlink/?LinkID=194074.<br />

For more information about WCF authorization, see the Authorization page at<br />

http://go.microsoft.com/fwlink/?LinkID=194075.


Encrypting <strong>Data</strong><br />

Key Points<br />

Building an N-Tier Solution by Using the Entity Framework 9-27<br />

When you move data over a network, you must take steps to ensure that you protect any confidential<br />

data. This confidential data can include data that your application retrieves from the database, data that<br />

the client sends to the data access layer, and security credentials.<br />

If you use an HTTP transport, you should configure your service to use the HTTPS protocol that encrypts<br />

the HTTP messages by using Secure Sockets Layer (SSL) security. HTTPS encrypts your data to provide<br />

confidentiality. It also guarantees the integrity of your data, authenticates the server, and can optionally<br />

authenticate the client.<br />

To configure HTTPS in IIS, you will need a suitable certificate. For testing purposes, you can use the IIS<br />

management tool to generate a self-signed certificate. In the IIS management tool, you specify the<br />

bindings for the HTTPS protocol at the site level. The bindings specify the port number and the certificate<br />

that you want to use for the HTTPS protocol. You can specify whether SSL is required or optional for a<br />

virtual directory or for an individual address.<br />

An additional consideration for a WCF service is whether the service metadata is accessible by using<br />

HTTPS. The following code example from the web.config file shows how make sure that the metadata for<br />

a service is only available by using HTTPS.<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

Question: Why should you not use a self-signed certificate in a production environment?


9-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Additional Reading<br />

For more information about IIS security, see the Configure Web Server Security (IIS 7) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194076.


Protecting Against Common Attacks<br />

Key Points<br />

Building an N-Tier Solution by Using the Entity Framework 9-29<br />

By using the HTTPS protocol, you can mitigate the risk of some common attacks. For example, SSL<br />

encrypts all of the data that the service transfers between the client and the server, which helps to<br />

minimize the risk of unnecessary information disclosure. This also applies to the metadata endpoints that<br />

a WCF service exposes. SSL also helps to minimize the risk of anyone tampering <strong>with</strong> the data while the<br />

service moves it over the network, and it can help to prevent replay attacks.<br />

Another common attack is a denial-of-service (DoS) attack. A DoS attack is a malicious attempt to swamp<br />

a server <strong>with</strong> valid requests so that legitimate users cannot use the service. A partial mitigation for this<br />

type of attack is to limit the amount of data that an individual request can return so that it becomes more<br />

difficult for an attacker to overwhelm the server <strong>with</strong> expensive requests. If you use Language-Integrated<br />

Query (LINQ) to Entities, you can use the Take operator to limit the number of entities that a query can<br />

return. If you use Entity Structured Query Language (Entity SQL), you can use the Limit operator. The<br />

following code example shows how to use the Take operator to impose a limit of 50 on the number of<br />

claims that an operation returns.<br />

[<strong>Visual</strong> Basic]<br />

Return claims.Take(50).ToList(Of RewardsClaimed)()<br />

[<strong>Visual</strong> C#]<br />

return claims.Take(50).ToList();<br />

If you use Entity SQL, you should take steps to avoid the risk of any SQL injection attacks by ensuring that<br />

you never combine user input from the client <strong>with</strong> the Entity SQL command text.<br />

Question: Why is LINQ to Entities not susceptible to traditional SQL injection attacks?


9-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Additional Reading<br />

For more information about Entity Framework security considerations, see the Security Considerations<br />

(Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194077.<br />

For more information about WCF security considerations, see the Security Considerations page at<br />

http://go.microsoft.com/fwlink/?LinkID=194078.


Building an N-Tier Solution by Using the Entity Framework 9-31<br />

Lab: Building an N-Tier Solution by Using the Entity<br />

Framework<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Create a data access layer for an n-tier application.<br />

• Protect the data access tier.<br />

Introduction<br />

In this lab, you will develop a data access tier to fetch and manage contact and order data. You will<br />

configure the data access tier to defend against common attacks and then implement authentication and<br />

authorization to ensure that clients can only access the data they are allowed to use.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-09 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


9-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

You have been asked to modify an existing client/server Orders application to use a separate data access<br />

tier. As a proof of concept, you have been asked to build a data access tier that client applications can use<br />

to query data. In this architecture, you will need to redesign the data access layer to transport objects<br />

between the data access tier and the business tier. You will also ensure that the data access tier is<br />

protected against common threats, and that it restricts each group of employees so that they can only<br />

access data appropriate to their role.


Building an N-Tier Solution by Using the Entity Framework 9-33<br />

Exercise 1: Creating the Contacts and Orders <strong>Data</strong> <strong>Access</strong> Tier<br />

Scenario<br />

In this exercise, you will create the orders data access tier holding the Contact, Order, and OrderDetail<br />

entity objects. The data access tier will be implemented by using a WCF service that will expose business<br />

methods that return data as self-tracking entities.<br />

Each operation will include logic to prevent a request from returning too much data that could potentially<br />

swamp the network and hog resources on the server.<br />

When a client application requests the details of an order, the corresponding entity objects are created,<br />

populated, and transported to the business layer in the client application.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the environment for the lab.<br />

2. Prepare the AdventureWorks database for the lab.<br />

3. Open the starter project.<br />

4. Create the OrdersDAL class library.<br />

5. Create the OrdersClientLibrary class library.<br />

6. Create the IOrdersService interface.<br />

7. Implement exception handling and logging.<br />

8. Implement the IOrdersService interface.<br />

9. Create the OrdersWebService Web service.<br />

10. Configure the OrderManagement application.<br />

11. Test the OrderManagement application.<br />

Task 1: Prepare the environment for the lab<br />

1. Log on to the 10265A-GEN-DEV-09 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures IIS and creates the<br />

required users and groups.<br />

Task 2: Prepare the AdventureWorks database for the lab<br />

• In the E:\Labfiles folder, run AWReset.bat.<br />

Task 3: Open the starter project<br />

1. In the E:\Labfiles\Lab09\VB\Ex1\Starter folder (if you are using Microsoft <strong>Visual</strong> Basic®), or<br />

E:\Labfiles\Lab09\CS\Ex1\Starter folder (if you are using Microsoft <strong>Visual</strong> C#®), run ExSetup.bat as an<br />

administrator. This script adds the required virtual directories to IIS.<br />

2. Open <strong>Visual</strong> <strong>Studio</strong> 2010 as an administrator.<br />

3. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex1\Starter\OrdersDAL or<br />

E:\Labfiles\Lab09\CS\Ex1\Starter\OrdersDAL folder.<br />

Task 4: Create the OrdersDAL class library<br />

1. Add a new Class Library project named OrdersDAL to the solution.<br />

2. Delete the Class1 file.<br />

3. Add a new ADO.NET Entity <strong>Data</strong> Model (EDM) named AdventureWorksModel to the OrdersDAL<br />

project. Generate the EDM from the AdventureWorks Microsoft SQL Server® database and create<br />

entities for the Contact, SalesOrderHeader, and SalesOrderDetail tables.


9-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

4. Modify the EDM to generate self-tracking entities by adding the ADO.NET Self-Tracking Entity<br />

Generator code generation item to the EDM. Name the code generation item<br />

AdventureWorksModel.tt. Allow <strong>Visual</strong> <strong>Studio</strong> to overwrite the existing AdventureWorks.Context.tt<br />

file when prompted.<br />

5. Build the OrdersDAL project and correct any errors.<br />

Important: Only build the OrdersDAL project. The OrderManagement project will not build successfully<br />

because it is not yet complete.<br />

Task 5: Create the OrdersClientLibrary class library<br />

1. Add a new Class Library project named OrdersClientLibrary to the solution.<br />

2. Delete the Class1 file.<br />

3. Add a reference to the System.Runtime.Serialization assembly.<br />

4. If you are using <strong>Visual</strong> Basic, change the Root namespace property of the OrdersClientLibrary project<br />

to OrdersDAL.<br />

5. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.<br />

6. Add a reference to the OrdersClientLibrary assembly to the OrdersDAL project.<br />

7. Add a class named AdditionalMethods to the OrdersClientLibrary project.<br />

8. In the AdditionalMethods class, perform the following tasks:<br />

a. Remove the AdditionalMethods class declaration.<br />

b. If you are using <strong>Visual</strong> C#, change the namespace to OrdersDAL.<br />

c. Create a public partial class named SalesOrderHeader.<br />

d. Create a public partial class named SalesOrderDetail.<br />

9. In the partial SalesOrderHeader class, add code to overwrite the ToString method by returning a<br />

string that contains the SalesOrderID, ContactID, AccountNumber, OrderDate,<br />

PurchaseOrderNumber, and TotalDue properties from the current object.<br />

10. In the partial SalesOrderDetail class, add code to overwrite the ToString method by returning the<br />

ProductID, OrderQty, UnitPrice, UnitPriceDiscount, and LineTotal properties of the current object.<br />

11. Build the OrdersClientLibrary project and correct any errors.<br />

Important: Only build the OrdersClientLibrary project. The OrderManagement project will not build<br />

successfully because it is not yet complete.<br />

Task 6: Create the IOrdersService interface<br />

1. Add a new Class Library project named OrdersService to the solution.<br />

2. Delete the Class1 file.<br />

3. Create an interface named IOrdersService in the OrdersService project.<br />

4. Add a reference to the OrdersClientLibrary assembly.<br />

5. Add a reference to the OrdersDAL assembly.<br />

6. Add a reference to the System.ServiceModel assembly.


7. Add a reference to the System.Runtime.Serialization assembly.<br />

8. Add a reference to the System.<strong>Data</strong>.Entity assembly.<br />

Building an N-Tier Solution by Using the Entity Framework 9-35<br />

9. In the IOrdersService code file, add code to bring the System.Runtime.Serialization,<br />

System.ServiceModel, and OrdersDAL namespaces into scope.<br />

10. In the IOrdersService file, before the interface declaration, write code to perform the following tasks:<br />

a. Add a new class called ServiceFault. Prefix the class <strong>with</strong> the <strong>Data</strong>Contract attribute.<br />

b. Add a public string field to the ServiceFault class called ExceptionType. Prefix the field <strong>with</strong> the<br />

<strong>Data</strong>Member attribute.<br />

c. Add a public string field to the ServiceFault class called ExceptionMessage. Prefix the field <strong>with</strong><br />

the <strong>Data</strong>Member attribute.<br />

11. In the IOrdersService file, perform the following tasks:<br />

a. Make the IOrdersService interface public (if it is not already public).<br />

b. Specify that the IOrdersService interface defines a service contract called OrdersWebService. Use<br />

http://microsoft.com as the Web service namespace.<br />

12. In the IOrdersService interface, perform the following tasks:<br />

a. Define a method called GetContactDetails that returns an object of type Contact and accepts<br />

an integer named contactID as a parameter.<br />

b. Specify that the GetContactDetails method is Web service operation that is part of the<br />

OrdersWebService service contract.<br />

c. Specify that the GetContactDetails method returns a message of type ServiceFault if an<br />

exception occurs.<br />

13. In the IOrdersService interface, perform the following tasks:<br />

a. Define a method called GetAllContactsInRange that returns a generic IEnumerable object of<br />

<strong>with</strong> a type parameter of Contact, and accepts an integer named lowerBound and an integer<br />

named upperBound as parameters.<br />

b. Specify that the GetAllContactsInRange method is Web service operation that is part of the<br />

OrdersWebService service contract.<br />

c. Specify that the GetAllContactsInRange method returns a message of type ServiceFault if an<br />

exception occurs.<br />

14. In the IOrdersService interface, perform the following tasks:<br />

a. Define a method called GetOrderDetails that returns an object of type SalesOrderHeader and<br />

accepts an integer named orderID as a parameter.<br />

b. Specify that the GetOrderDetails method is a Web service operation that is part of the<br />

OrdersWebService service contract.<br />

c. Specify that the GetOrderDetails method returns a message of type ServiceFault if an exception<br />

occurs.<br />

15. In the IOrdersService interface, perform the following tasks:<br />

a. Define a method called GetOrdersForContact that returns a generic IEnumerable object of <strong>with</strong><br />

a type parameter of SalesOrderHeader and accepts an integer named contactID as a<br />

parameter.<br />

b. Specify that the GetOrdersForContact method is a Web service operation that is part of the<br />

OrdersWebService service contract.<br />

c. Specify that the GetOrdersForContact method returns a message of type ServiceFault if an<br />

exception occurs.


9-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

16. In the IOrdersService interface, perform the following tasks:<br />

a. Define a method called GetOrdersForProduct that returns a generic IEnumerable object of<br />

<strong>with</strong> a type parameter of SalesOrderHeader and accepts an integer named productID as a<br />

parameter.<br />

b. Specify that the GetOrdersForProduct method is a Web service operation that is part of the<br />

OrdersWebService service contract.<br />

c. Specify that the GetOrdersForProduct method returns a message of type ServiceFault if an<br />

exception occurs.<br />

17. In the IOrdersService interface, perform the following tasks:<br />

a. Define a method called GetAllOrdersInRange that returns a generic IEnumerable object <strong>with</strong> a<br />

type parameter of SalesOrderHeader and accepts an integer named lowerBound and an<br />

integer named upperBound as parameters.<br />

b. Specify that the GetAllOrdersInRange method is a Web service operation that is part of the<br />

OrdersWebService service contract.<br />

c. Specify that the GetAllOrdersInRange method returns a message of type ServiceFault if an<br />

exception occurs.<br />

18. Build the project and correct any errors.<br />

Important: Only build the OrdersService project. The OrderManagement project will not build<br />

successfully because it is not yet complete.<br />

Task 7: Implement exception handling and logging<br />

1. Create a class named OrdersServiceImpl in the OrdersService project.<br />

2. In the OrdersServiceImpl code file, write code to bring the following namespaces into scope:<br />

• System.Text<br />

Note: If you are using <strong>Visual</strong> C#, the System.Text namespace is already in scope and there is no need to<br />

add it again.<br />

• System.ServiceModel<br />

• System.Diagnostics<br />

• System.Threading<br />

• OrdersDAL<br />

• System.Security.Permissions<br />

3. In the OrdersServiceImpl code file, perform the following tasks:<br />

a. Prefix the OrdersWebService class <strong>with</strong> the ServiceBehavior attribute, specify the name of the<br />

service as OrdersWebService, and set the namespace to http://microsoft.com.<br />

b. Set the InstanceContextMode property of the ServiceBehavior attribute to PerCall.<br />

c. Set the ConcurrencyMode property of the ServiceBehavior attribute to Multiple.<br />

d. Declare the OrdersServiceImpl class as a public class (if it is not already public) that implements<br />

the IOrdersService interface.<br />

4. In the OrdersServiceImpl class, write code to perform the following tasks:


Building an N-Tier Solution by Using the Entity Framework 9-37<br />

a. Declare a constant integer named maxContactsCount and assign it a value of 500.<br />

b. Declare a constant integer named maxOrdersCount and assign it a value of 400.<br />

c. Declare a constant string named eventSource and assign it the value "Orders Service".<br />

d. Declare a constant string named eventLog and assign it the value "Application".<br />

5. In the OrdersServiceImpl class, write code to perform the following tasks:<br />

a. Create a new private method named logException that accepts an Exception object named ex<br />

and a string object named eventName as parameters. This method should not return a value.<br />

b. In the logException method, verify that the event source identified by the eventSource constant<br />

exists by calling the SourceExists method of the EventLog class in the System.Diagnostics<br />

namespace. If the event source does not exist, create it by calling the CreateEventSource<br />

method of the EventLog class, specifying the eventSource and eventLog constants as<br />

parameters.<br />

c. Declare a string named eventMessage and assign it a value by combining the eventName<br />

variable, the Message property of the ex variable, and the value of the<br />

Thread.CurrentPrincipal.Identity.Name property.<br />

d. Call the WriteEntry method of the EventLog class, passing the eventSource and eventMessage<br />

variables as parameters and specifying Error as the EventLogEntryType parameter.<br />

6. In the OrdersServiceImpl class, create a new private method named handleException that accepts<br />

the following parameters:<br />

• An Exception object named ex.<br />

• A string parameter named operationName.<br />

• An optional integer parameter named operation<strong>Data</strong> <strong>with</strong> a default value of 0.<br />

7. In the handleException method, write code to perform the following tasks:<br />

a. Create a new StringBuilder object named eventMessageBuilder.<br />

b. Append the message "Failure in {0}" to the eventMessageBuilder object, and specify the value<br />

of the operationName parameter as the {0} placeholder.<br />

c. If the value of the operation<strong>Data</strong> variable is not equal to 0, append the value of the<br />

operation<strong>Data</strong> parameter to the eventMessageBuilder object.<br />

d. Call the logException method, passing the ex object and the string value of the<br />

eventMessageBuilder object as parameters.<br />

8. In the handleException method, add code to perform the following tasks:<br />

a. If the ex object is of type ApplicationException, create a new ServiceFault object named sf. Set<br />

the ExceptionType property to the type of the ex object, and set the ExceptionMessage<br />

property by using Message property of the ex object.<br />

b. Throw a new FaultException exception, specifying the sf object and the string<br />

value of the eventMessageBuilder object as parameters.<br />

c. If the ex object is some other type of exception, create a new ServiceFault object named sf, and<br />

set the ExceptionType property to the type of the ex object and the value of the<br />

ExceptionMessage property to the string "Exception occurred fetching data".<br />

d. Throw a new FaultException exception. Pass the value of the sf object and the<br />

message "Failure in {0}" where the {0} placeholder is the value of the operationName variable as<br />

parameters to the constructor.<br />

9. Save the OrdersServiceImpl file.


9-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 8: Implement the IOrdersService interface<br />

1. In the OrdersServiceImpl class, generate stub methods for each of the items in the IOrdersService<br />

interface.<br />

2. Locate the GetContactDetails method. This method takes an integer value as a parameter and<br />

returns a Contact object.<br />

3. If you are using <strong>Visual</strong> C#, delete the default method body that throws a<br />

NotImplementedException exception.<br />

4. In the body of the method, add code to perform the following tasks:<br />

a. Create a Contact object named contact and assign it the value null (Nothing in <strong>Visual</strong> Basic).<br />

b. Create a new AdventureWorksEntities object.<br />

c. Define a LINQ query named matchingContacts that retrieves all of the Contact entities by using<br />

the AdventureWorksEntities object, <strong>with</strong> a ContactID property that is equal to the value of the<br />

contactID variable.<br />

d. If there is at least one Contact object in the matchingContacts collection, return it; otherwise,<br />

return a null (Nothing in <strong>Visual</strong> Basic) value. If there is more than one matching contact, return<br />

the first one found.<br />

Note: The ContactID should be unique, so there should only be at most one matching contact. However,<br />

it is good practice to write defensive code just in case a database administrator amends the structure of<br />

the Contact table in the database and creates a different key column.<br />

e. Handle any exceptions by calling the handleException method; pass the Exception object, the<br />

method name, and the contactID variable as parameters before returning a null (Nothing in<br />

<strong>Visual</strong> Basic) value.<br />

5. Locate the GetAllContactsInRange method. This method takes two integer values, lowerBound and<br />

upperBound, as parameters and returns an IEnumerable list of Contact objects.<br />

6. If you are using <strong>Visual</strong> C#, delete the default method body that throws a<br />

NotImplementedException exception.<br />

7. In the body of the method, add code to perform the following tasks:<br />

a. Create an IEnumerable object named contacts by using the Contact type as the type<br />

parameter, and assign it the value null (Nothing in <strong>Visual</strong> Basic).<br />

b. Create a new AdventureWorksEntities object.<br />

c. Define a LINQ query that retrieves all of the Contact entities where the ContactID property is<br />

between the values of the lowerBound and upperBound variables. The result of this query should<br />

be assigned to the contacts object.<br />

d. If the number of objects in the contacts collection is greater than or equal to the value of the<br />

maxContactsCount constant, throw a new ApplicationException exception <strong>with</strong> the message<br />

"Too many contacts".<br />

e. Return the contacts collection as a generic List object. If you are using <strong>Visual</strong> C#, specify the<br />

Contact type as the type parameter for the List object.<br />

f. Handle any exceptions by calling the handleException method; pass the exception object and<br />

the method name as parameters before returning null (Nothing in <strong>Visual</strong> Basic).<br />

8. Locate the GetOrderDetails method. This method takes an integer value as a parameter and returns<br />

a SalesOrderHeader object.


Building an N-Tier Solution by Using the Entity Framework 9-39<br />

9. If you are using <strong>Visual</strong> C#, delete the default method that throws a NotImplementedException<br />

exception.<br />

10. In the body of the method, add code to perform the following tasks:<br />

a. Create a SalesOrderHeader object named order and assign it the value null (Nothing in <strong>Visual</strong><br />

Basic).<br />

b. Create a new AdventureWorksEntities object.<br />

c. Define a LINQ query named matchingOrders that retrieves all of the SalesOrderHeader entities<br />

and the related SalesOrderDetail entities where the SalesOrderID property matches the value in<br />

the orderID variable.<br />

d. If the number of objects in the matchingOrders collection is greater than zero, return the first<br />

SalesOrderHeader object in the collection; otherwise, return null (Nothing in <strong>Visual</strong> Basic).<br />

e. Handle any Exception exceptions by calling the handleException method; pass the exception<br />

object, the method name, and the orderID variable as parameters before returning null (Nothing<br />

in <strong>Visual</strong> Basic).<br />

11. Locate the GetOrdersForContact method. This method takes an integer value as a parameter and<br />

returns an IEnumerable list of SalesOrderHeader objects.<br />

12. If you are using <strong>Visual</strong> C#, delete the default method body that throws a<br />

NotImplementedException exception.<br />

13. In the body of the method, add code to perform the following tasks:<br />

a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type<br />

parameter, and assign it the value null (Nothing in <strong>Visual</strong> Basic).<br />

b. Create a new AdventureWorksEntities object.<br />

c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related<br />

SalesOrderDetail entities where the ContactID property matches the value in the contactID<br />

variable. Assign the result of this query to the orders object.<br />

d. If the number of objects in the orders collection is greater than or equal to the value of the<br />

maxOrdersCount constant, throw a new ApplicationException exception <strong>with</strong> the message "Too<br />

many orders".<br />

e. Return the orders collection as a generic List object. If you are using <strong>Visual</strong> C#, specify the<br />

SalesOrderHeader type as the type parameter for the List object.<br />

f. Handle any exceptions by calling the handleException method; pass the exception object, the<br />

method name, and the contactID variable as parameters before returning null (Nothing in <strong>Visual</strong><br />

Basic).<br />

14. Locate the GetOrdersForProduct method. This method takes an integer value as a parameter and<br />

returns an IEnumerable list of SalesOrderHeader objects.<br />

15. If you are using <strong>Visual</strong> C#, delete the default method body that throws a<br />

NotImplementedException exception.<br />

16. In the body of the method, add code to perform the following tasks:<br />

a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type<br />

parameter, and assign it the value null (Nothing in <strong>Visual</strong> Basic).<br />

b. Create a new AdventureWorksEntities object.<br />

c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related<br />

SalesOrderDetail entities where the ProductID property of at least one of the SalesOrderDetail<br />

entities for the SalesOrderHeader entity matches the productID variable. Assign the result of this<br />

query to the orders object.


9-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Note: The SalesOrderDetail records for an order specify the products being ordered. An order can have<br />

one or more SalesOrderDetail records. To find all orders for a specific product, you must find all<br />

SalesOrderDetail records that match the product and return the SalesOrderHeader objects that<br />

reference these SalesOrderDetail records.<br />

d. If the number of objects in the orders collection is greater than or equal to the value of the<br />

maxOrdersCount constant, throw a new ApplicationException exception <strong>with</strong> the message "Too<br />

many orders".<br />

e. Return the orders collection as a generic List object. If you are using <strong>Visual</strong> C#, specify the<br />

SalesOrderHeader type as the type parameter for the List object.<br />

f. Handle any exceptions by calling the handleException method; pass the exception object, and<br />

the method name as parameters before returning null (Nothing in <strong>Visual</strong> Basic).<br />

17. Locate the GetAllOrdersInRange method. This method takes two integer values, lowerBound and<br />

upperBound, as parameters and returns an IEnumerable list of Contact objects.<br />

18. If you are using <strong>Visual</strong> C#, delete the default method body that throws a<br />

NotImplementedException exception.<br />

19. In the body of the method, add code to perform the following tasks:<br />

a. Create an IEnumerable object named orders by using the SalesOrderHeader type as the type<br />

parameter, and assign it the value null (Nothing in <strong>Visual</strong> Basic).<br />

b. Create a new AdventureWorksEntities object.<br />

c. Define a LINQ query that retrieves all of the SalesOrderHeader entities and related<br />

SalesOrderDetails entities where the value in the SalesOrderID property is between the<br />

lowerBound and upperBound variables. The result of this query should be assigned to the orders<br />

object.<br />

d. If the number of objects in the orders collection is greater than or equal to the value of the<br />

maxOrdersCount constant, throw a new ApplicationException exception <strong>with</strong> the message "Too<br />

many orders".<br />

e. Return the orders collection as a generic List object. If you are using <strong>Visual</strong> C#, specify the<br />

SalesOrderHeader type as the type parameter for the List object.<br />

f. Handle any exceptions by calling the handleException method; pass the exception object, and<br />

the method name as parameters before returning null (Nothing in <strong>Visual</strong> Basic).<br />

20. Build the OrdersService project and correct any errors.<br />

Important: Only build the OrdersService project. The OrderManagement project will not build<br />

successfully because it is not yet complete.<br />

Task 9: Create the OrdersWebService Web service<br />

1. Add a new empty ASP.NET Web Application project named OrdersWebService to the solution.<br />

2. Configure the project to use the local IIS Web server <strong>with</strong> the URL<br />

http://localhost/OrdersWebService.<br />

3. Add a new WCF service called OrdersWebService.svc to the OrdersWebService project.<br />

4. Delete the IOrdersWebService code file from the OrdersWebService project.<br />

5. Add a reference to the OrdersClientLibrary assembly.<br />

6. Add a reference to the OrdersDAL assembly.


7. Add a reference to the OrdersService assembly.<br />

8. Delete the OrdersWebService code-behind file.<br />

9. In the OrdersWebService.svc file, delete the existing markup code.<br />

Building an N-Tier Solution by Using the Entity Framework 9-41<br />

10. In the OrdersWebService.svc file, add markup statements that perform the following tasks:<br />

a. Create a ServiceHost instance and set the Service property to point to the OrdersServiceImpl<br />

service that is defined in the OrdersService project.<br />

b. Specify that this service is located in the OrdersService assembly.<br />

Note: <strong>Visual</strong> <strong>Studio</strong> reports that it cannot find the OrdersService assembly. This warning will disappear<br />

when you build the project and you can safely ignore it.<br />

11. Delete the Web.config file from the OrdersWebService project.<br />

12. Add the existing Web.config file from the E:\Labfiles\Lab09\CS\Ex1\Starter or<br />

E:\Labfiles\Lab09\VB\Ex1\Starter folder to the OrdersWebService project.<br />

13. Build the OrdersWebService project and correct any errors.<br />

Important: Only build the OrdersWebService project. The OrderManagement project will not build<br />

successfully because it is not yet complete.<br />

Task 10: Configure the OrderManagement application<br />

1. In the OrderManagement project, add a reference to the OrdersClientLibrary assembly.<br />

2. Add a service reference to the OrdersWebService service to the OrderManagement application.<br />

Generate the service reference in the OWService namespace. The URL of the OrdersWebService is<br />

http://localhost/OrdersWebService/OrdersWebService.svc. Use System.Collections.Generic.List as<br />

the collection type and ensure that you reuse types in referenced assemblies.<br />

3. In the app.config file, modify the definition of the wsHttpBinding binding by increasing the<br />

maxBufferPoolSize property to 5242880 and the maxReceivedMessageSize property to 5242880.<br />

4. Review the task list.<br />

5. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the<br />

TODO: Lab 9, Ex1 - Fetch a single contact task in the task list. This task is located in the<br />

RetrieveContacts method.<br />

6. Immediately after the TODO: Lab9, Ex1 - Fetch a single contact comment, write code to perform<br />

the following tasks:<br />

a. Create a new Contact object named contact by calling the GetContactDetails method of the<br />

service object. Pass the rangeFrom variable as a parameter.<br />

Note: The service variable is an OrdersWebServiceClient object. The OdersWebServiceClient type was<br />

generated when you added the service reference to the OrdersWebService service. This type provides the<br />

Web service proxy for connecting to the OrdersWebService service, and it exposes methods that you can<br />

call to invoke the operations in the OrdersWebService service.<br />

b. If the contact object is not null (Nothing in <strong>Visual</strong> Basic), instantiate the contacts generic List<br />

collection and specify Contact as the type parameter for this list.


9-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

c. Add the contact object to the contacts list.<br />

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab 9, Ex1 - Fetch all contacts in range task in the task list.<br />

8. Immediately after the TODO: Lab 9, Ex1 - Fetch all contacts in range comment, write code to call<br />

the GetAllContactsInRange method of the service object. Specify the rangeFrom and rangeTo<br />

variables as parameters. Assign the returned value to the contacts object.<br />

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact task in the task<br />

list. This task is located in the getOrdersForContact_Click method.<br />

10. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact comment,<br />

write code to call the GetOrdersForContact method of the service object. Specify the contactID<br />

variable as the parameter. Assign the returned value to the orders object.<br />

11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified product task in the task<br />

list. This task is located in the getOrdersForProduct_Click method.<br />

12. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified product comment,<br />

write code to call the GetOrdersForProduct method of the service object. Specify the productID<br />

variable as the parameter. Assign the returned value to the orders object.<br />

13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab 9, Ex1 - Fetch a single order task in the task list. This task is located<br />

in the RetrieveOrders method.<br />

14. Immediately after the comment, write code to create a new SalesOrderHeader object named order.<br />

Assign this the value that is returned by calling the GetOrderDetails method of the service object.<br />

Specify the rangeFrom variable as a parameter. If an order is returned by the GetOrderDetails<br />

method, instantiate the orders generic List object and add the order to this list. Specify<br />

SalesOrderHeader as the type parameter for the orders list.<br />

15. Locate the next comment in the code behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab 9, Ex1 - Fetch all orders in range task in the task list.<br />

16. Immediately after the TODO: Lab 9, Ex1 - Fetch all orders in range comment, write code to call the<br />

GetAllOrdersInRange method of the service object. Specify the rangeFrom and rangeTo variables as<br />

parameters. Assign the returned value to the orders object.<br />

17. Build the solution and correct any errors.<br />

Task 11: Test the OrderManagement application<br />

1. Check that the OrderManagement application is set as the startup project.<br />

2. Start the application <strong>with</strong>out debugging.<br />

3. In the Order Management window, on the Contacts tab, click Get. One contact is displayed in the<br />

contacts grid.<br />

4. Adjust the To slider to a value that is less than 500, and then click Get. Check that the correct number<br />

of rows is returned.<br />

5. Adjust the To slider to a value that is greater than 500, and then click Get. An exception will be<br />

thrown containing the message "Too many contacts".<br />

6. In the Service Fault occurred dialog box, click OK.<br />

7. In the Order Management window, click the General Orders tab.


Building an N-Tier Solution by Using the Entity Framework 9-43<br />

8. On the General Orders tab, adjust the From slider to a value that is greater than 50000, and then<br />

click Get. A single order is displayed in the orders grid. Expand the order to view the order details.<br />

9. Adjust the To slider to retrieve a range of between 20 and 50 orders, and then click Get. Verify that<br />

the correct number of orders is displayed.<br />

You can use the arrow keys to adjust the slider in small increments.<br />

10. Adjust the To slider to retrieve a range of over 500 orders. Verify that a message box appears <strong>with</strong> the<br />

message “Too many orders.”<br />

11. In the Service Fault occurred dialog box, click OK.<br />

12. On the Orders By Contact tab, in the Contact ID box, type 10 and then click Get. The four orders<br />

placed by customer 10 should appear. Expand each order to view the details.<br />

13. On the Orders By Product tab, in the Product ID box, type 710 and then click Get. The 44 orders for<br />

this product should appear. Expand each order to view the details.<br />

14. Close the Order Management window and return to <strong>Visual</strong> <strong>Studio</strong>.<br />

15. Open the Orders Service event log to view the details of exceptions generated by the Web service.<br />

16. Close the solution.


9-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 2: Protecting <strong>Data</strong> <strong>Access</strong> Operations<br />

Scenario<br />

In this exercise, you will configure the data access tier to defend against common attacks, and implement<br />

authentication and authorization to ensure that client applications can only access the data that they are<br />

allowed to use.<br />

You will define two roles—OrderAdmin and ContactAdmin—and then configure the DAL so that only<br />

users in the ContactAdmin role can use the GetContactDetails and GetAllContactsInRange operations, and<br />

only users in the OrderAdmin role can invoke the GetOrderDetails, GetOrdersForContact,<br />

GetOrdersForProduct, and GetAllOrdersInRange operations.<br />

You will then configure the service to encrypt data as it traverses the network, and will protect against<br />

replay and DoS attacks.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Host the OrdersWebService Web service by using SSL.<br />

3. Apply security demands.<br />

4. Modify the OrdersWebService Web service to use transport security.<br />

5. Modify the OrderManagement application.<br />

6. Create unit tests.<br />

7. Build and test the application.<br />

Task 1: Open the starter project<br />

1. In the E:\Labfiles\Lab09\VB\Ex2\Starter folder (if you are using <strong>Visual</strong> Basic), or<br />

E:\Labfiles\Lab09\CS\Ex2\Starter folder (if you are using <strong>Visual</strong> C#), run ExSetup.bat as an<br />

administrator.<br />

2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex2\Starter\OrdersDAL or<br />

E:\Labfiles\Lab09\CS\Ex2\Starter\OrdersDAL folder.<br />

Task 2: Host the OrdersWebService Web service by using SSL<br />

1. Open IIS Manager as an administrator.<br />

2. Add a new self-signed certificate named OrdersWebService.<br />

3. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.<br />

4. Set the OrdersWebService Web service to require SSL security.<br />

5. Close IIS Manager.<br />

6. Set the Project Url property for the OrdersWebService project to use HTTPS.<br />

Task 3: Apply security demands<br />

1. Review the task list.<br />

2. Open the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2 - Allow<br />

ContactAdmins to call GetAllContactsInRange task in the task list.<br />

3. Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetAllContactsInRange<br />

comment, add the PrincipalPermission attribute to specify that users must be members of the<br />

ContactAdmin role.<br />

4. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2<br />

- Allow ContactAdmins to call GetContactDetails task in the task list.


Building an N-Tier Solution by Using the Entity Framework 9-45<br />

5. Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetContactDetails<br />

comment, add the PrincipalPermission attribute to specify that users must be members of the<br />

ContactAdmin role.<br />

6. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2<br />

- Allow OrderAdmins to call GetAllOrdersInRange task in the task list.<br />

7. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetAllOrdersInRange<br />

comment, add the PrincipalPermission attribute to specify that users must be members of the<br />

OrderAdmin role.<br />

8. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2<br />

- Allow OrderAdmins to call GetOrderDetails task in the task list.<br />

9. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrderDetails comment,<br />

add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin<br />

role.<br />

10. Locate the next comment in the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2<br />

- Allow OrderAdmins to call GetOrdersForContact task in the task list.<br />

11. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForContact<br />

comment, add the PrincipalPermission attribute to specify that users must be members of the<br />

OrderAdmin role.<br />

12. Locate the next comment in the OrderServiceImpl code file by double-clicking the TODO: Lab9, Ex2<br />

- Allow OrderAdmins to call GetOrdersForProduct task in the task list.<br />

13. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForProduct<br />

comment, add the PrincipalPermission attribute to specify that users must be members of the<br />

OrderAdmin role.<br />

14. Build the solution and correct any errors.<br />

Task 4: Modify the OrdersWebService Web service to use transport security <strong>with</strong><br />

message-level credentials<br />

1. In the OrdersWebService project, open the web.config file.<br />

2. In the web.config file, locate the TODO: Lab 9, Ex2 - Use TransportWithMessageCredential<br />

security mode comment. This comment is located in the bindings section.<br />

3. Change the security mode from None to TransportWithMessageCredential.<br />

4. In the web.config file, locate the TODO: Lab 9, Ex2 - Enable https, disable http comment. This<br />

comment is located in the serviceBehaviors section.<br />

5. Change the serviceMetadata property to enable HTTPS and disable HTTP.<br />

6. Build the solution and correct any errors.<br />

Task 5: Modify the OrderManagement application<br />

1. Update the OrdersWebService service reference to use the new binding and security.<br />

2. Review the task list.<br />

3. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the<br />

TODO: Lab9, Ex2 - Add credentials in getContacts_Click task in the task list.<br />

4. Immediately after the TODO: Lab9, Ex2 - Add credentials in getContacts_Click comment, add<br />

code to perform the following tasks:


9-46 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

a. Assign the value of the userName.Text property to the<br />

service.ClientCredentials.Windows.ClientCredential.UserName property.<br />

b. Assign the value of the password.Password property to the<br />

service.ClientCredentials.Windows.ClientCredential.Password property.<br />

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrders_Click task in the task list.<br />

6. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrders_Click comment, add code<br />

to perform the following tasks:<br />

a. Assign the value of the userName.Text property to the<br />

service.ClientCredentials.Windows.ClientCredential.UserName property.<br />

b. Assign the value of the password.Password property to the<br />

service.ClientCredentials.Windows.ClientCredential.Password property.<br />

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click task in the<br />

task list.<br />

8. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click comment,<br />

add code to perform the following tasks:<br />

a. Assign the value of the userName.Text property to the<br />

service.ClientCredentials.Windows.ClientCredential.UserName property.<br />

b. Assign the value of the password.Password property to the<br />

service.ClientCredentials.Windows.ClientCredential.Password property.<br />

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click task in the<br />

task list.<br />

10. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click<br />

comment, add code to perform the following tasks:<br />

a. Assign the value of the userName.Text property to the<br />

service.ClientCredentials.Windows.ClientCredential.UserName property.<br />

b. Assign the value of the password.Password property to the<br />

service.ClientCredentials.Windows.ClientCredential.Password property.<br />

11. Build the solution and correct any errors.<br />

Task 6: Create unit tests<br />

1. Review the task list.<br />

2. Open the OrderServiceImplTest code file by double-clicking the TODO: Lab9, Ex2 - Create a unit<br />

test for the GetAllContactsInRange method task in the task list.<br />

3. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllContactsInRange<br />

method comment, add the following unit test code. This code calls the GetAllContactsInRange<br />

method to fetch all contacts in the range 100 to 500, and verifies that the method returns the correct<br />

number of rows. This method specifies the user name Fred and the password Pa$$w0rd. This user is<br />

a member of the ContactAdmin role.<br />

Your code should resemble the following code example.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetAllContactsInRangeTest()


End Sub<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Fred"<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd"<br />

Dim lowerBound As Integer = 100<br />

Dim upperBound As Integer = 500<br />

Dim expectedCount As Integer = 401<br />

Dim expectedFirstName As String = "Jackie"<br />

Dim actual As IEnumerable(Of Contact) =<br />

service.GetAllContactsInRange(lowerBound, upperBound)<br />

Assert.AreEqual(expectedCount, actual.Count())<br />

Assert.AreEqual(expectedFirstName, actual.First().FirstName)<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetAllContactsInRangeTest()<br />

{<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Fred";<br />

}<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd";<br />

int lowerBound = 100;<br />

int upperBound = 500;<br />

int expectedCount = 401;<br />

string expectedFirstName = "Jackie";<br />

IEnumerable actual =<br />

service.GetAllContactsInRange(lowerBound, upperBound);<br />

Assert.AreEqual(expectedCount, actual.Count());<br />

Assert.AreEqual(expectedFirstName, actual.First().FirstName);<br />

Building an N-Tier Solution by Using the Entity Framework 9-47<br />

4. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,<br />

Ex2 - Create a unit test for the GetAllOrdersInRange method task in the task list.<br />

5. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllOrdersInRange method<br />

comment, add the following unit test code. This code calls the GetAllOrdersInRange method to<br />

retrieve all orders in the range 43650 to 43700, and verifies that the method returns the correct<br />

number of rows. This method specifies the user name Fred and the password Pa$$w0rd.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetAllOrdersInRangeTest()<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert"<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd"<br />

Dim lowerBound As Integer = 43650<br />

Dim upperBound As Integer = 43700<br />

Dim expectedCount As Integer = 42<br />

Dim expectedSalesOrderDetailCount As Integer = 12


9-48 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Dim actual As IEnumerable(Of SalesOrderHeader) =<br />

service.GetAllOrdersInRange(lowerBound, upperBound)<br />

Assert.AreEqual(expectedCount, actual.Count())<br />

Assert.AreEqual(expectedSalesOrderDetailCount,<br />

actual.First().SalesOrderDetails.Count())<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetAllOrdersInRangeTest()<br />

{<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert";<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd";<br />

}<br />

int lowerBound = 43650;<br />

int upperBound = 43700;<br />

int expectedCount = 42;<br />

int expectedSalesOrderDetailCount = 12;<br />

IEnumerable actual =<br />

service.GetAllOrdersInRange(lowerBound, upperBound);<br />

Assert.AreEqual(expectedCount, actual.Count());<br />

Assert.AreEqual(expectedSalesOrderDetailCount,<br />

actual.First().SalesOrderDetails.Count());<br />

6. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,<br />

Ex2 - Create a unit test for the GetContactDetails method task in the task list.<br />

7. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetContactDetails method<br />

comment, add the following unit test code. This code calls the GetContactDetails method to retrieve<br />

the details of contact 13, and verifies that the method returns the correct data. This method specifies<br />

the user name Fred and the password Pa$$w0rd.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetContactDetailsTest()<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Fred"<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd"<br />

Dim contactID As Integer = 13<br />

Dim expectedFirstName As String = "Robert"<br />

Dim expectedLastName As String = "Ahlering"<br />

Dim actual As Contact = service.GetContactDetails(contactID)<br />

Assert.AreEqual(expectedFirstName, actual.FirstName)<br />

Assert.AreEqual(expectedLastName, actual.LastName)<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetContactDetailsTest()<br />

{<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Fred";<br />

service.ClientCredentials.Windows.ClientCredential.Password =


}<br />

"Pa$$w0rd";<br />

int contactID = 13;<br />

string expectedFirstName = "Robert";<br />

string expectedLastName = "Ahlering";<br />

Contact actual = service.GetContactDetails(contactID);<br />

Assert.AreEqual(expectedFirstName, actual.FirstName);<br />

Assert.AreEqual(expectedLastName, actual.LastName);<br />

Building an N-Tier Solution by Using the Entity Framework 9-49<br />

8. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,<br />

Ex2 - Create a unit test for the GetOrderDetails method task in the task list.<br />

9. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrderDetails method<br />

comment, add the following unit test code. This code calls the GetOrderDetails method to retrieve<br />

the details of order 71780, and verifies that the method returns the correct data. This method<br />

specifies the user name Bert and the password Pa$$w0rd. This user is a member of the OrderAdmin<br />

role.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetOrderDetailsTest()<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert"<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd"<br />

Dim orderID As Integer = 71780<br />

Dim expectedSalesOrderID As Integer = 71780<br />

Dim expectedAccountNumber As String = "10-4020-000340"<br />

Dim expectedSalesOrderDetailsCount As Integer = 29<br />

Dim actual As SalesOrderHeader = service.GetOrderDetails(orderID)<br />

Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID)<br />

Assert.AreEqual(expectedAccountNumber, actual.AccountNumber)<br />

Assert.AreEqual(expectedSalesOrderDetailsCount,<br />

actual.SalesOrderDetails.Count)<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetOrderDetailsTest()<br />

{<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert";<br />

}<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd";<br />

int orderID = 71780;<br />

int expectedSalesOrderID = 71780;<br />

string expectedAccountNumber = "10-4020-000340";<br />

int expectedSalesOrderDetailsCount = 29;<br />

SalesOrderHeader actual = service.GetOrderDetails(orderID);<br />

Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID);<br />

Assert.AreEqual(expectedAccountNumber, actual.AccountNumber);<br />

Assert.AreEqual(expectedSalesOrderDetailsCount,<br />

actual.SalesOrderDetails.Count);


9-50 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

10. Locate the next comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,<br />

Ex2 - Create a unit test for the GetOrdersForContact method task in the task list.<br />

11. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForContact method<br />

comment, add the following unit test code. This code calls the GetOrdersForContact method to<br />

retrieve the orders for contact 100, and verifies that the method returns the correct data. This method<br />

specifies the user name Bert and the password Pa$$w0rd.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetOrdersForContactTest()<br />

End Sub<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert"<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd"<br />

Dim contactID As Integer = 100<br />

Dim expectedOrdersCount As Integer = 4<br />

Dim expectedSalesOrderDetailCount As Integer = 3<br />

Dim actual As IEnumerable(Of SalesOrderHeader) =<br />

service.GetOrdersForContact(contactID)<br />

Assert.AreEqual(expectedOrdersCount, actual.Count())<br />

Assert.AreEqual(expectedSalesOrderDetailCount,<br />

actual.First().SalesOrderDetails.Count())<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetOrdersForContactTest()<br />

{<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert";<br />

}<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd";<br />

int contactID = 100;<br />

int expectedOrdersCount = 4;<br />

int expectedSalesOrderDetailCount = 3;<br />

IEnumerable actual =<br />

service.GetOrdersForContact(contactID);<br />

Assert.AreEqual(expectedOrdersCount, actual.Count());<br />

Assert.AreEqual(expectedSalesOrderDetailCount,<br />

actual.First().SalesOrderDetails.Count());<br />

12. Locate the final comment in the OrderServiceImplTest code file by double-clicking the TODO: Lab9,<br />

Ex2 - Create a unit test for the GetOrdersForProduct method task in the task list.<br />

13. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForProduct method<br />

comment, add the following unit test code. This code calls the GetOrdersForProduct method to<br />

retrieve the orders that contain product 709, and verifies that the method returns the correct data.<br />

This method specifies the user name Bert and the password Pa$$w0rd.


[<strong>Visual</strong> Basic]<br />

<br />

Public Sub GetOrdersForProductTest()<br />

End Sub<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert"<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd"<br />

Dim productID As Integer = 709<br />

Dim expectedOrderCount As Integer = 188<br />

Dim expectedOrderDetailsCount As Integer = 12<br />

Dim actual As IEnumerable(Of SalesOrderHeader) =<br />

service.GetOrdersForProduct(productID)<br />

Assert.AreEqual(expectedOrderCount, actual.Count())<br />

Assert.AreEqual(expectedOrderDetailsCount,<br />

actual.First().SalesOrderDetails.Count)<br />

[<strong>Visual</strong> C#]<br />

[TestMethod()]<br />

public void GetOrdersForProductTest()<br />

{<br />

service.ClientCredentials.Windows.ClientCredential.UserName =<br />

"Bert";<br />

}<br />

service.ClientCredentials.Windows.ClientCredential.Password =<br />

"Pa$$w0rd";<br />

int productID = 709;<br />

int expectedOrderCount = 188;<br />

int expectedOrderDetailsCount = 12;<br />

IEnumerable actual =<br />

service.GetOrdersForProduct(productID);<br />

Assert.AreEqual(expectedOrderCount, actual.Count());<br />

Assert.AreEqual(expectedOrderDetailsCount,<br />

actual.First().SalesOrderDetails.Count);<br />

14. Build the solution and correct any errors.<br />

Task 7: Build and test the application<br />

1. Run all of the tests in the solution and verify that they are successful.<br />

2. Start the OrderManagement application <strong>with</strong>out debugging.<br />

Building an N-Tier Solution by Using the Entity Framework 9-51<br />

3. In the Order Management window, on the Contacts tab, click Get to retrieve the details of contact 1<br />

from the Web service. No credentials have been supplied; therefore, an exception is thrown. In the<br />

message box displaying the message "<strong>Access</strong> is denied", click OK.<br />

4. Test the application by using the two sets of credentials in the following table. You should only be<br />

able to use the Contacts tab when you are authenticated as Fred, and you should only be able to use<br />

the General Orders, Orders By Contact, and Orders By Product tabs when you are authenticated<br />

as Bert. You should not be able to access any data if you omit or specify incorrect credentials.<br />

Username Password Role


9-52 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Username Password Role<br />

Fred Pa$$w0rd ContactAdmin<br />

Bert Pa$$w0rd OrderAdmin<br />

5. Close <strong>Visual</strong> <strong>Studio</strong>.


Lab Review<br />

Building an N-Tier Solution by Using the Entity Framework 9-53<br />

Review Questions<br />

1. How did you implement the code that generates self-tracking entities for an EDM?<br />

2. How did you restrict the volume of data returned by the OrdersWebService Web service?<br />

3. How did you restrict the users that can invoke the operations in the OrdersWebService Web service?<br />

4. How did you specify the credentials for a user invoking operations in the OrdersWebService Web<br />

service?


9-54 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. In an n-tier application, should you try to make the size of the individual messages that the client and<br />

the data access layer exchange as small as possible?<br />

2. What are the two main components that the T4 ADO.NET Self-Tracking Entity Generator templates<br />

generate?<br />

3. What is the potential problem that occurs if lazy loading is enabled in an n-tier data access layer?<br />

4. How does the HTTPS protocol help to protect your application from replay attacks?<br />

Best Practices Related to Building an N-Tier Solution by Using the Entity Framework<br />

Supplement or modify the following best practices for your own work situations:<br />

• Use DTOs if you only need to pass a subset of data in each entity to client applications.<br />

• Use STEs if you need to pass complete entities to client applications.<br />

• Use WCF to host your data access layer.<br />

• Use the T4 templates to generate your STEs.<br />

• Make sure that you secure your WCF service.<br />

• Make sure that just the necessary information is moved over the network.


Module 10<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-1<br />

Handling Updates in an N-Tier Solution by Using the Entity<br />

Framework<br />

Contents:<br />

Lesson 1: Tracking Entities and Persisting Changes 10-3<br />

Lesson 2: Managing Exceptions in an N-Tier Solution 10-30<br />

Lab: Handling Updates in an N-Tier Solution by Using the Entity<br />

Framework 10-40


10-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

When you make data modifications to entities in an n-tier solution, you need to ensure that the changes<br />

are tracked and then transferred to the database. When you save changes to the database, exceptions can<br />

occur if data integrity is violated or concurrency issues arise.<br />

This module describes how you can handle data modifications in an n-tier solution and how to manage<br />

the exceptions that can occur during the data modification process.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Use different strategies to track changes in the client application and persist these changes to the<br />

database.<br />

• Trap and handle update and concurrency exceptions in an n-tier solution.


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-3<br />

Lesson 1<br />

Tracking Entities and Persisting Changes<br />

The way that you handle data modifications in an n-tier solution varies depending on the type of object<br />

that you choose to serialize your data between tiers. Therefore, it is important that you use the correct<br />

method for your choice of object. It is also important to ensure that you configure your connection strings<br />

to promote scalability and that you encrypt them.<br />

This lesson describes the alternative strategies for tracking changes that users make to entity data in a<br />

client application and how to persist these changes to the database in the data access layer.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the issues that are associated <strong>with</strong> change tracking in an n-tier solution.<br />

• Handle data modifications when the application uses simple entities (SEs).<br />

• Handle data modifications when the application uses self-tracking entities (STEs).<br />

• Handle data modifications when the application uses data transfer objects (DTOs).<br />

• Configure your database connection for scalability and security.


10-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Tracking Changes in an N-Tier Solution<br />

Key Points<br />

In an n-tier solution, you move data over the network between the data access layer and the client. To<br />

move entity data that the data access layer has retrieved from the database, you must serialize the objects<br />

that contain the data. You must also serialize the objects that contain data that the client application has<br />

modified when you return these objects to the data access layer. Windows® Communication Foundation<br />

(WCF) handles most of the serialization process for you. However, there is one aspect of the process that<br />

you must handle directly: you must ensure that the types that you use are serializable. Change tracking is<br />

more complicated in an n-tier solution because when you serialize an entity object in the data access<br />

layer, the entity object is detached from the ObjectContext object. Therefore, the ObjectContext object<br />

can no longer automatically track changes in that entity object. When you deserialize an object from the<br />

client application, you must reattach the entity object to the ObjectContext object, work out what has<br />

changed, and then save these changes to the database. This contrasts <strong>with</strong> the situation in a two-tier<br />

application, where entity objects can remain permanently attached to the ObjectContext object, which<br />

enables the ObjectContext object to track changes transparently.<br />

There are several methods in the Entity Framework application programming interface (API) that you will<br />

find useful when you build an n-tier solution. The following table summarizes these methods.<br />

Method name Description<br />

AddObject This method adds a detached entity object to the ObjectContext object.<br />

You can use this method when you have a new entity object. This<br />

method sets the state of the object to Added. If the entity object has<br />

related objects, the method also adds these objects to the<br />

ObjectContext object and sets their state to Added. You may need to<br />

change the state of the related objects to reflect the modifications that<br />

have occurred in the client application. When you add an entity object,<br />

you can assign a temporary key that will be replaced when the entity<br />

object is saved to the database.


Method name Description<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-5<br />

Attach and AttachTo These methods attach a detached object to the ObjectContext object<br />

and set its state to Unchanged. The key of the entity object that you are<br />

attaching must be unique in the ObjectContext object.<br />

ApplyCurrentValues This method takes a detached entity object as a parameter. It copies the<br />

properties of this object to the current values of an attached object <strong>with</strong><br />

the same key value. The state of each property value that is different is<br />

set to Modified.<br />

ApplyOriginalValues This method takes a detached entity object as a parameter. It copies the<br />

properties of this object to the original values of an attached object <strong>with</strong><br />

the same key value. The state of each property value that is different is<br />

set to Modified.<br />

GetUpdatableOriginalValues This method gives you update access to the original values of an<br />

attached entity object.<br />

CurrentValues This method gives you update access to the current values of an attached<br />

entity object.<br />

ChangeObjectState This method enables you to change the state of an attached entity<br />

object. If you change the state of an entity object to Modified, all of the<br />

properties of the entity object will be marked as modified.<br />

ChangeRelationshipState This method enables you to change the state of an attached relationship.<br />

ChangeState This method enables you to change the state of an attached entity object<br />

or relationship.<br />

SetModifiedProperty This method enables you to set the state of an individual property of an<br />

attached entity object.<br />

The following topics in this lesson illustrate how different scenarios use these methods.<br />

Question: In an n-tier solution, you are recommended to make your data access layer stateless. What<br />

impact does this have when you design a change-tracking mechanism?<br />

Additional Reading<br />

For more information about serializing entity objects, see the Serializing Objects (Entity Framework) page<br />

at http://go.microsoft.com/fwlink/?LinkID=194068.<br />

For more information about building n-tier solutions, see the Building N-Tier Applications (Entity<br />

Framework) page at http://go.microsoft.com/fwlink/?LinkID=194080.


10-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Change Tracking <strong>with</strong> Self-Tracking Entities in an N-Tier Solution<br />

Key Points<br />

In a previous module, you saw how to use the Text Template Transformation Toolkit (T4) templates to<br />

generate STEs from your Entity <strong>Data</strong> Model (EDM). In addition to generating the entity classes, these<br />

templates also generate a custom ObjectContext class to work <strong>with</strong> the entities.<br />

These templates generate entity classes that perform their own change tracking, so when the client<br />

modifies an entity object, the entity object stores details of the changes internally. When the STE is<br />

attached to the ObjectContext object back in the data access layer, the custom ObjectContext object<br />

can read the details of the changes back and work out what changes must be applied to the database.<br />

The following code example shows the implementation of the service method that receives a contact STE<br />

and the related reward claim STEs from the client and handles the updates to the database.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub UpdateContactWithRelatedRewardsClaimed(ByVal contact as Contact)<br />

End Sub<br />

ValidateContactUpdate(contact)<br />

DeleteRewardsClaimed(contact)<br />

ApplyContactValues(contact)<br />

' Save all of the changes.<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

context.Contacts.ApplyChanges(contact)<br />

context.SaveChanges()<br />

End Using<br />

[<strong>Visual</strong> C#]


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-7<br />

public void UpdateContactWithRelatedRewardsClaimed(Contact contact)<br />

{<br />

}<br />

ValidateContactUpdate(contact);<br />

DeleteRewardsClaimed(contact);<br />

ApplyContactValues(contact);<br />

// Save all of the changes.<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

context.Contacts.ApplyChanges(contact);<br />

context.SaveChanges();<br />

}<br />

The UpdateContactWithRelatedRewardsClaimed method first validates and makes any necessary<br />

changes to the detached contact STE and the related reward claim STEs that it receives from the client. It<br />

then uses the ApplyChanges extension method to copy all of the changes from the STEs into the<br />

ObjectContext object before it calls the SaveChanges method to persist the changes to the database.<br />

The following code example illustrates this.<br />

[<strong>Visual</strong> Basic]<br />

Private Sub ApplyContactValues(ByVal contact As Contact)<br />

End Sub<br />

For Each claim In contact.RewardsClaimed<br />

Select claim.ChangeTracker.State<br />

Case ObjectState.Unchanged<br />

Case ObjectState.Added<br />

claim.TimeStamp = DateTime.Now<br />

claim.ClaimID = GetNextClaimID()<br />

contact.CurrentPoints -= claim.PointsUsed<br />

contact.ModifiedDate = DateTime.Now<br />

Case ObjectState.Modified<br />

claim.TimeStamp = DateTime.Now<br />

' ConcurrencyMode.Fixed is<br />

' set on the PointsUsed field in the EDM.<br />

contact.CurrentPoints +=<br />

claim.ChangeTracker.OriginalValues("PointsUsed")<br />

contact.CurrentPoints -= claim.PointsUsed<br />

contact.ModifiedDate = DateTime.Now<br />

Case Else<br />

End Select<br />

Next claim<br />

[<strong>Visual</strong> C#]<br />

private void ApplyContactValues(Contact contact)<br />

{<br />

foreach (var claim in contact.RewardsClaimed)


10-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

{<br />

}<br />

switch (claim.ChangeTracker.State)<br />

{<br />

}<br />

case ObjectState.Unchanged:<br />

break;<br />

case ObjectState.Added:<br />

claim.TimeStamp = DateTime.Now;<br />

claim.ClaimID = GetNextClaimID();<br />

contact.CurrentPoints -= claim.PointsUsed;<br />

contact.ModifiedDate = DateTime.Now;<br />

break;<br />

case ObjectState.Modified:<br />

claim.TimeStamp = DateTime.Now;<br />

// ConcurrencyMode.Fixed is<br />

// set on the PointsUsed field in the EDM.<br />

contact.CurrentPoints +=<br />

(int)claim.ChangeTracker<br />

.OriginalValues["PointsUsed"];<br />

contact.CurrentPoints -= claim.PointsUsed;<br />

contact.ModifiedDate = DateTime.Now;<br />

break;<br />

default:<br />

break;<br />

The ApplyContactValues method iterates over the related reward claims to apply the business rules that<br />

calculate the CurrentPoints property value of the contact. This method also sets the timestamp values<br />

that will be used for concurrency checks. Notice how the method makes use of the ChangeTracker<br />

property of the entity object to discover the changes that the client application has made to the entities.<br />

The original value of the PointsUsed property is available because it is has a concurrency mode of Fixed<br />

in the EDM. The following code example illustrates this.<br />

[<strong>Visual</strong> Basic]<br />

Private Sub DeleteRewardsClaimed(ByVal contact As Contact)<br />

If contact.ChangeTracker.ObjectsRemovedFromCollectionProperties.Count > 0 Then<br />

For Each r In contact.ChangeTracker<br />

.ObjectsRemovedFromCollectionProperties("RewardsClaimeds")<br />

End Sub<br />

Dim c As RewardsClaimed = r<br />

contact.CurrentPoints += c.PointsUsed<br />

c.MarkAsDeleted()<br />

Next r<br />

End If<br />

[<strong>Visual</strong> C#]<br />

private void DeleteRewardsClaimed(Contact contact)<br />

{<br />

if (contact.ChangeTracker<br />

.ObjectsRemovedFromCollectionProperties.Count > 0)


}<br />

{<br />

}<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-9<br />

foreach (var r in contact.ChangeTracker<br />

.ObjectsRemovedFromCollectionProperties["RewardsClaimeds"])<br />

{<br />

RewardsClaimed c = (RewardsClaimed)r;<br />

contact.CurrentPoints += c.PointsUsed;<br />

c.MarkAsDeleted();<br />

}<br />

The DeleteRewardsClaimed method uses the ObjectsRemovedFromCollectionProperties collection to<br />

identify the rewards claimed that the client application has removed from the contact. The<br />

DeleteRewardsClaimed method then uses the MarkAsDeleted extension method to set the state of the<br />

entity objects in the ObjectContext object.<br />

The client application uses the entity types that the T4 templates generate. You can place these classes in<br />

a separate assembly so that the client application has no dependencies on the Entity Framework.<br />

The following code example shows how to add a new reward claim to a contact. The entity objects handle<br />

all of the change tracking for you. The UpdateContactWithRelatedRewardsClaimed method passes a<br />

contact entity and its related rewards claimed entities back to the WCF service.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As RewardsDAL.Contact =<br />

service.GetContactAndRelatedRewardsClaimed(1)<br />

Dim newClaim As RewardsDAL.RewardsClaimed =<br />

New RewardsDAL.RewardsClaimed()<br />

newClaim.PointsUsed = 100<br />

newClaim.RewardID = 1<br />

contact.RewardsClaimeds.Add(newClaim)<br />

service.UpdateContactWithRelatedRewardsClaimed(contact)<br />

[<strong>Visual</strong> C#]<br />

RewardsDAL.Contact contact =<br />

service.GetContactAndRelatedRewardsClaimed(1);<br />

RewardsDAL.RewardsClaimed newClaim = new RewardsDAL.RewardsClaimed();<br />

newClaim.PointsUsed = 100;<br />

newClaim.RewardID = 1;<br />

contact.RewardsClaimeds.Add(newClaim);<br />

service.UpdateContactWithRelatedRewardsClaimed(contact);<br />

The following code example shows how to change a rewards claimed entity. This will also update the<br />

points that the contact holds.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As RewardsDAL.Contact =<br />

service.GetContactAndRelatedRewardsClaimed(1)<br />

contact.RewardsClaimeds(1).PointsUsed = 50<br />

service.UpdateContactWithRelatedRewardsClaimed(contact)


10-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> C#]<br />

RewardsDAL.Contact contact =<br />

service.GetContactAndRelatedRewardsClaimed(1);<br />

contact.RewardsClaimeds[1].PointsUsed = 50;<br />

service.UpdateContactWithRelatedRewardsClaimed(contact);<br />

The following code example shows how to remove a rewards claimed entity. This will also update the<br />

points that the contact holds.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As RewardsDAL.Contact =<br />

service.GetContactAndRelatedRewardsClaimed(1)<br />

contact.RewardsClaimeds.Remove(contact.RewardsClaimeds(1))<br />

service.UpdateContactWithRelatedRewardsClaimed(contact)<br />

[<strong>Visual</strong> C#]<br />

RewardsDAL.Contact contact =<br />

service.GetContactAndRelatedRewardsClaimed(1);<br />

contact.RewardsClaimeds.Remove(contact.RewardsClaimeds[1]);<br />

service.UpdateContactWithRelatedRewardsClaimed(contact);


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-11<br />

Demonstration: Change Tracking <strong>with</strong> Self-Tracking Entities in an N-Tier<br />

Solution<br />

Key Points<br />

• An end-to-end walkthrough of a solution that uses STEs.<br />

Demonstration Steps<br />

1. Log on to the 10265A-GEN-DEV-10 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration, and then run<br />

EnvSetup.bat to configure Internet Information Services (IIS).<br />

3. In the E:\Demofiles\Mod10\Demo1\Solution folder, run ExSetup.bat as an administrator to configure<br />

the virtual directory in IIS for the application.<br />

4. Start <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010 as administrator.<br />

5. Open the RewardsSTE solution.<br />

6. Review the code in the solution.<br />

7. Run the application.<br />

8. Close the application and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: In this example, the WCF service code implements the business logic that updates the number<br />

of points that a contact holds. Where else could you place this logic?


10-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Change Tracking <strong>with</strong> Simple Entities in an N-Tier Solution<br />

Key Points<br />

This topic demonstrates how to use SEs instead of STEs to implement the same functionality in the<br />

Adventure Works application that you saw in the previous topic. This means that you must implement the<br />

change-tracking functionality manually.<br />

This example uses a set of custom entity classes that you write by hand instead of generating them by<br />

using the T4 templates. These entity classes are simple plain-old CLR object (POCO) classes that you place<br />

in a separate assembly so that you can eliminate any dependencies on the Entity Framework in the client<br />

application. The following code example shows the RewardsClaimed entity class.<br />

[<strong>Visual</strong> Basic]<br />

<br />

<br />

<br />

Public Class RewardsClaimed<br />

Public Overrides Function ToString() As String<br />

Return String.Format("ClaimID: {0}" + vbTab +<br />

"ContactID: {1}" + vbTab +<br />

"RewardID: {2}" + vbTab +<br />

"PointsUsed: {3}",<br />

Me.ClaimID, Me.ContactID, Me.RewardID, Me.PointsUsed)<br />

End Function<br />

<br />

Public Property ClaimID As Integer<br />


Public Property PointsUsed As Integer<br />

<br />

Public Property RewardID As Integer<br />

<br />

Public Property ContactID As Integer<br />

<br />

Public Property TimeStamp As System.DateTime<br />

<br />

Public Property Contact As Contact<br />

<br />

Public Property Reward As Reward<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

[<strong>Data</strong>Contract(IsReference = true)]<br />

[KnownType(typeof(Contact))]<br />

[KnownType(typeof(Reward))]<br />

public class RewardsClaimed<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-13<br />

{<br />

public override string ToString()<br />

{<br />

return string.Format("ClaimID: {0}\tContactID: {1}\tRewardID: {2}\tPointsUsed:<br />

{3}",<br />

this.ClaimID, this.ContactID, this.RewardID, this.PointsUsed);<br />

}<br />

}<br />

[<strong>Data</strong>Member]<br />

public int ClaimID { get; set; }<br />

[<strong>Data</strong>Member]<br />

public int PointsUsed { get; set; }<br />

[<strong>Data</strong>Member]<br />

public int RewardID { get; set; }<br />

[<strong>Data</strong>Member]<br />

public int ContactID { get; set; }<br />

[<strong>Data</strong>Member]<br />

public System.DateTime TimeStamp { get; set; }<br />

[<strong>Data</strong>Member]<br />

public Contact Contact { get; set; }<br />

[<strong>Data</strong>Member]<br />

public Reward Reward { get; set; }


10-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The following code example shows the WCF service method that adds a new rewards claim to a contact. It<br />

sets the timestamp values that will be used for concurrency checks. It uses the Attach method to attach<br />

the rewards claim and its related contact to the ObjectContext object before it sets the state of the<br />

objects and calls the SaveChanges method.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub AddRewardsClaim(ByVal claim As RewardsClaimed)<br />

claim.Contact.CurrentPoints -= claim.PointsUsed<br />

' Set all of the necessary object properties.<br />

claim.ContactID = claim.Contact.ContactID<br />

claim.Contact.ModifiedDate = DateTime.Now<br />

claim.TimeStamp = DateTime.Now<br />

claim.ClaimID = GetNextClaimID()<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

' Attach the claim (and the related contact)<br />

' - everything has a state of Unchanged.<br />

context.RewardsClaimeds.Attach(claim)<br />

' Set the correct entity state values before you call<br />

' the SaveChanges method.<br />

context.ObjectStateManager.ChangeObjectState(claim,<br />

System.<strong>Data</strong>.EntityState.Added)<br />

context.ObjectStateManager.ChangeObjectState(claim.Contact,<br />

System.<strong>Data</strong>.EntityState.Modified)<br />

context.SaveChanges()<br />

End Using<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

public void AddRewardsClaim(RewardsClaimed claim)<br />

{<br />

claim.Contact.CurrentPoints -= claim.PointsUsed;<br />

// Set all of the necessary object properties.<br />

claim.ContactID = claim.Contact.ContactID;<br />

claim.Contact.ModifiedDate = DateTime.Now;<br />

claim.TimeStamp = DateTime.Now;<br />

claim.ClaimID = GetNextClaimID();<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

// Attach the claim (and the related contact)<br />

// - everything has a state of Unchanged.<br />

context.RewardsClaimeds.Attach(claim);<br />

// Set the correct entity state values before you call<br />

// the SaveChanges method.<br />

context.ObjectStateManager.ChangeObjectState(claim,<br />

System.<strong>Data</strong>.EntityState.Added);<br />

context.ObjectStateManager.ChangeObjectState(claim.Contact,<br />

System.<strong>Data</strong>.EntityState.Modified);<br />

}<br />

context.SaveChanges();


}<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-15<br />

The following code example shows how to handle a change to a rewards claimed entity. Notice how it is<br />

necessary to retrieve the existing claim from the database to get the original value of the PointsUsed<br />

property.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub UpdateRewardsClaim(ByVal claim As RewardsClaimed)<br />

' Set all of the necessary object properties.<br />

claim.Contact.ModifiedDate = DateTime.Now<br />

claim.TimeStamp = DateTime.Now<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

' Find the original points to give back to the contact.<br />

' You must run a query here.<br />

RewardsClaimed(oldClaim =<br />

GetRewardsClaimedDetails(claim.ClaimID))<br />

claim.Contact.CurrentPoints += oldClaim.PointsUsed<br />

' Add the new points.<br />

claim.Contact.CurrentPoints -= claim.PointsUsed<br />

' Attach the claim (and the related contact)<br />

' - everything has a state of Unchanged.<br />

context.RewardsClaimeds.Attach(claim)<br />

' Set the correct entity state values before you call<br />

' the SaveChanges method.<br />

context.ObjectStateManager.ChangeObjectState(claim,<br />

System.<strong>Data</strong>.EntityState.Modified)<br />

context.ObjectStateManager.ChangeObjectState(claim.Contact,<br />

System.<strong>Data</strong>.EntityState.Modified)<br />

context.SaveChanges()<br />

End Using<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

public void UpdateRewardsClaim(RewardsClaimed claim)<br />

{<br />

// Set all of the necessary object properties.<br />

claim.Contact.ModifiedDate = DateTime.Now;<br />

claim.TimeStamp = DateTime.Now;<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

// Find the original points to give back to the contact.<br />

// You must run a query here.<br />

RewardsClaimed oldClaim =<br />

GetRewardsClaimedDetails(claim.ClaimID);<br />

claim.Contact.CurrentPoints += oldClaim.PointsUsed;<br />

// Add the new points.<br />

claim.Contact.CurrentPoints -= claim.PointsUsed;


10-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

}<br />

// Attach the claim (and the related contact)<br />

// - everything has a state of Unchanged.<br />

context.RewardsClaimeds.Attach(claim);<br />

// Set the correct entity state values before you call<br />

// the SaveChanges method.<br />

context.ObjectStateManager.ChangeObjectState(claim,<br />

System.<strong>Data</strong>.EntityState.Modified);<br />

context.ObjectStateManager.ChangeObjectState(claim.Contact,<br />

System.<strong>Data</strong>.EntityState.Modified);<br />

context.SaveChanges();<br />

The following code example shows how to remove a rewards claimed entity from a contact entity.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub DeleteRewardsClaim(ByVal claim As RewardsClaimed)<br />

End Sub<br />

claim.Contact.CurrentPoints += claim.PointsUsed<br />

' Set all of the necessary object properties.<br />

claim.Contact.ModifiedDate = DateTime.Now<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

' Attach the claim (and the related contact)<br />

' - everything has a state of Unchanged.<br />

context.RewardsClaimeds.Attach(claim)<br />

' Set the correct entity state values before you call<br />

' the SaveChanges method.<br />

context.ObjectStateManager.ChangeObjectState(claim.Contact,<br />

System.<strong>Data</strong>.EntityState.Modified)<br />

context.ObjectStateManager.ChangeObjectState(claim,<br />

System.<strong>Data</strong>.EntityState.Deleted)<br />

context.SaveChanges()<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

public void DeleteRewardsClaim(RewardsClaimed claim)<br />

{<br />

claim.Contact.CurrentPoints += claim.PointsUsed;<br />

// Set all of the necessary object properties.<br />

claim.Contact.ModifiedDate = DateTime.Now;<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

// Attach the claim (and the related contact)


}<br />

}<br />

// - everything has a state of Unchanged.<br />

context.RewardsClaimeds.Attach(claim);<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-17<br />

// Set the correct entity state values before you call<br />

// the SaveChanges method.<br />

context.ObjectStateManager.ChangeObjectState(claim.Contact,<br />

System.<strong>Data</strong>.EntityState.Modified);<br />

context.ObjectStateManager.ChangeObjectState(claim,<br />

System.<strong>Data</strong>.EntityState.Deleted);<br />

context.SaveChanges();<br />

The following three code examples show how the client application can add a new rewards claimed entity,<br />

update an existing rewards claimed entity, and delete a rewards claimed entity.<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As Contact = service.GetContactAndRelatedRewardsClaimed(1)<br />

Dim newClaim As New RewardsClaimed()<br />

newClaim.PointsUsed = 100<br />

newClaim.RewardID = 1<br />

newClaim.Contact = contact<br />

service.AddRewardsClaim(newClaim)<br />

[<strong>Visual</strong> C#]<br />

Contact contact = service.GetContactAndRelatedRewardsClaimed(1);<br />

RewardsClaimed newClaim = new RewardsClaimed();<br />

newClaim.PointsUsed = 100;<br />

newClaim.RewardID = 1;<br />

newClaim.Contact = contact;<br />

service.AddRewardsClaim(newClaim);<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As Contact = service.GetContactAndRelatedRewardsClaimed(1)<br />

contact.RewardsClaimed(1).PointsUsed = 50<br />

service.UpdateRewardsClaim(contact.RewardsClaimeds(1))<br />

[<strong>Visual</strong> C#]<br />

Contact contact = service.GetContactAndRelatedRewardsClaimed(1);<br />

contact.RewardsClaimeds[1].PointsUsed = 50;<br />

service.UpdateRewardsClaim(contact.RewardsClaimed[1]);<br />

[<strong>Visual</strong> Basic]<br />

Dim contact As Contact = service.GetContactAndRelatedRewardsClaimed(1)


10-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

service.DeleteRewardsClaim(contact.RewardsClaimeds(1))<br />

[<strong>Visual</strong> C#]<br />

Contact contact = service.GetContactAndRelatedRewardsClaimed(1);<br />

service.DeleteRewardsClaim(contact.RewardsClaimeds[1]);


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-19<br />

Demonstration: Change Tracking <strong>with</strong> Simple Entities in an N-Tier Solution<br />

Key Points<br />

• An end-to-end walkthrough of a solution that uses simple entities.<br />

Demonstration Steps<br />

1. If you did not perform these steps for the previous walkthrough, in the E:\Demofiles folder, run<br />

Demo.bat to set up the database for this demonstration, and then run EnvSetup.bat to configure IIS.<br />

2. In the E:\Demofiles\Mod10\Demo2\Solution folder, run ExSetup.bat as an administrator to configure<br />

the virtual directory in IIS for the application.<br />

3. Start <strong>Visual</strong> <strong>Studio</strong> 2010 as administrator.<br />

4. Open the RewardsSE solution.<br />

5. Review the code in the solution.<br />

6. Run the application.<br />

7. Close the application and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: In this example, the update logic requires you to retrieve the original version of the rewards<br />

claimed entity from the database to get the original value of the PointsUsed property. How can you<br />

avoid having to query for this information during the update?


10-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Change Tracking <strong>with</strong> <strong>Data</strong> Transfer Objects in an N-Tier Solution<br />

Key Points<br />

This topic demonstrates how to use DTOs to implement the same functionality in the Adventure Works<br />

application that you saw in the previous two topics.<br />

This example uses a set of DTO classes that minimize the amount of data that you move over the network<br />

and simplify the data modification logic. These DTO classes are placed in a separate assembly to eliminate<br />

any dependencies on the Entity Framework in the client application. They do require code to translate<br />

between the DTO and the entity objects in the data access layer; they also require code to translate<br />

between the DTO and the data representation that the client uses. In this example, the data access layer<br />

uses the standard entity objects that the Entity Framework generates directly from the EDM.<br />

The following code example shows the DTO class that the application uses to transfer information about a<br />

rewards claimed entity. Notice that the class has two PointsUsed fields: one to hold the original value<br />

that you retrieve from the database and one to hold the new value that the client sets.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Class RewardsClaimedDTO<br />

<br />

Public Property ClaimID As Integer<br />

<br />

Public Property OriginalPointsUsed As Integer<br />

<br />

Public Property NewPointsUsed As Integer<br />

<br />

Public Property RewardID As Integer<br />

<br />

Public Property ContactID As Integer


Public Property TimeStamp As System.DateTime<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

[<strong>Data</strong>Contract(IsReference = true)]<br />

public class RewardsClaimedDTO<br />

{<br />

[<strong>Data</strong>Member]<br />

public int ClaimID { get; set; }<br />

}<br />

[<strong>Data</strong>Member]<br />

public int OriginalPointsUsed { get; set; }<br />

[<strong>Data</strong>Member]<br />

public int NewPointsUsed { get; set; }<br />

[<strong>Data</strong>Member]<br />

public int RewardID { get; set; }<br />

[<strong>Data</strong>Member]<br />

public int ContactID { get; set; }<br />

[<strong>Data</strong>Member]<br />

public System.DateTime TimeStamp { get; set; }<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-21<br />

The following code example shows how the WCF service receives a DTO. The method uses the<br />

information in the DTO to add a new rewards claimed entity to the database and to update the related<br />

contact entity in the database. The AddRewardsClaim method first creates a new RewardsClaimed<br />

object and populates it from the DTO. It then retrieves the related contact entity from the database and<br />

updates the CurrentPoints property. Next, it adds the new RewardsClaimed object to the<br />

ObjectContext object. Finally, it calls the SaveChanges method.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub AddRewardsClaim(ByVal claimDTO As RewardsClaimedDTO)<br />

' Create a new RewardsClaimed object.<br />

Dim claim As New RewardsClaimed()<br />

claim.ContactID = claimDTO.ContactID<br />

claim.PointsUsed = claimDTO.NewPointsUsed<br />

claim.RewardID = claimDTO.RewardID<br />

claim.ClaimID = GetNextClaimID()<br />

claim.TimeStamp = DateTime.Now<br />

' Create an entity key for the related Contact object.<br />

Dim key As New EntityKey("AdventureWorksEntities.Contacts",<br />

"ContactID", claimDTO.ContactID)<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

' Fetch the related Contact object.<br />

Dim contact As Object = Nothing<br />

If context.TryGetObjectByKey(key, contact) Then<br />

' Adjust the points and add the claim.<br />

contact.CurrentPoints -=


10-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

End Sub<br />

End If<br />

claimDTO.NewPointsUsed<br />

contact.ModifiedDate = DateTime.Now<br />

context.RewardsClaimeds.AddObject(claim)<br />

context.SaveChanges()<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

public void AddRewardsClaim(RewardsClaimedDTO claimDTO)<br />

{<br />

// Create a new RewardsClaimed object.<br />

RewardsClaimed claim = new RewardsClaimed();<br />

claim.ContactID = claimDTO.ContactID;<br />

claim.PointsUsed = claimDTO.NewPointsUsed;<br />

claim.RewardID = claimDTO.RewardID;<br />

claim.ClaimID = GetNextClaimID();<br />

claim.TimeStamp = DateTime.Now;<br />

}<br />

// Create an entity key for the related Contact object.<br />

EntityKey key =<br />

new EntityKey("AdventureWorksEntities.Contacts", "ContactID",<br />

claimDTO.ContactID);<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

// Fetch the related Contact object.<br />

object contact = null;<br />

if (context.TryGetObjectByKey(key, out contact))<br />

{<br />

// Adjust the points and add the claim.<br />

((Contact)contact).CurrentPoints -=<br />

claimDTO.NewPointsUsed;<br />

((Contact)contact).ModifiedDate = DateTime.Now;<br />

context.RewardsClaimeds.AddObject(claim);<br />

}<br />

}<br />

context.SaveChanges();<br />

The following code example demonstrates how to use the information in a DTO to update an existing<br />

rewards claimed entity and its related contact entity.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub UpdateRewardsClaim(ByVal claimDTO As RewardsClaimedDTO)<br />

' Create the entity keys to retrieve the entities.<br />

Dim claimkey As New EntityKey(<br />

"AdventureWorksEntities.RewardsClaimed",<br />

"ClaimID", claimDTO.ClaimID)<br />

Dim contactkey As New EntityKey(<br />

"AdventureWorksEntities.Contacts",


End Sub<br />

"ContactID", claimDTO.ContactID)<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

' Fetch the RewardsClaimed object.<br />

Dim claim As Object = Nothing<br />

If context.TryGetObjectByKey(claimkey, claim) Then<br />

' Fetch the related contact.<br />

Dim contact As Contact =<br />

context.GetObjectByKey(contactkey)<br />

' Adjust the points and add the claim.<br />

contact.CurrentPoints +=<br />

claimDTO.OriginalPointsUsed<br />

contact.CurrentPoints -=<br />

claimDTO.NewPointsUsed<br />

contact.ModifiedDate = DateTime.Now<br />

claim.PointsUsed =<br />

claimDTO.NewPointsUsed<br />

claim.TimeStamp = DateTime.Now<br />

End If<br />

context.SaveChanges()<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

public void UpdateRewardsClaim(RewardsClaimedDTO claimDTO)<br />

{<br />

// Create the entity keys to retrieve the entities.<br />

EntityKey claimkey =<br />

new EntityKey("AdventureWorksEntities.RewardsClaimed",<br />

"ClaimID", claimDTO.ClaimID);<br />

EntityKey contactkey =<br />

new EntityKey("AdventureWorksEntities.Contacts",<br />

"ContactID", claimDTO.ContactID);<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

// Fetch the RewardsClaimed object.<br />

object claim = null;<br />

if (context.TryGetObjectByKey(claimkey, out claim))<br />

{<br />

// Fetch the related contact.<br />

Contact contact =<br />

(Contact)context.GetObjectByKey(contactkey);<br />

// Adjust the points and add the claim.<br />

((Contact)contact).CurrentPoints +=<br />

claimDTO.OriginalPointsUsed;<br />

((Contact)contact).CurrentPoints -=<br />

claimDTO.NewPointsUsed;<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-23


10-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

}<br />

}<br />

((Contact)contact).ModifiedDate = DateTime.Now;<br />

((RewardsClaimed)claim).PointsUsed =<br />

claimDTO.NewPointsUsed;<br />

((RewardsClaimed)claim).TimeStamp = DateTime.Now;<br />

context.SaveChanges();<br />

The following code example demonstrates how to use the information in a DTO to delete an existing<br />

rewards claimed entity and update its related contact entity.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub DeleteRewardsClaim(ByVal claimDTO As RewardsClaimedDTO)<br />

End Sub<br />

' Create the entity keys to retrieve the entities.<br />

Dim claimkey As New EntityKey(<br />

"AdventureWorksEntities.RewardsClaimed",<br />

"ClaimID", claimDTO.ClaimID)<br />

Dim contactkey As New EntityKey(<br />

"AdventureWorksEntities.Contacts",<br />

"ContactID", claimDTO.ContactID)<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

' Fetch the RewardsClaimed object.<br />

Dim claim As Object = Nothing<br />

If context.TryGetObjectByKey(claimkey, claim) Then<br />

' Fetch the related contact.<br />

Dim contact As Contact =<br />

context.GetObjectByKey(contactkey)<br />

End If<br />

' Adjust the points and add the claim.<br />

contact.CurrentPoints +=<br />

claimDTO.OriginalPointsUsed<br />

contact.ModifiedDate = DateTime.Now<br />

context.RewardsClaimeds().DeleteObject(claim)<br />

context.SaveChanges()<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

public void DeleteRewardsClaim(RewardsClaimedDTO claimDTO)<br />

{<br />

// Create the entity keys to retrieve the entities.


}<br />

EntityKey claimkey =<br />

new EntityKey("AdventureWorksEntities.RewardsClaimed",<br />

"ClaimID", claimDTO.ClaimID);<br />

EntityKey contactkey =<br />

new EntityKey("AdventureWorksEntities.Contacts",<br />

"ContactID", claimDTO.ContactID);<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

}<br />

// Fetch the RewardsClaimed object.<br />

object claim = null;<br />

if (context.TryGetObjectByKey(claimkey, out claim))<br />

{<br />

// Fetch the related contact.<br />

Contact contact =<br />

(Contact)context.GetObjectByKey(contactkey);<br />

// Adjust the points and add the claim.<br />

((Contact)contact).CurrentPoints +=<br />

claimDTO.OriginalPointsUsed;<br />

((Contact)contact).ModifiedDate = DateTime.Now;<br />

context.RewardsClaimeds<br />

.DeleteObject((RewardsClaimed)claim);<br />

}<br />

context.SaveChanges();<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-25<br />

The following three code examples show how the client application can add a new rewards claimed entity,<br />

update an existing rewards claimed entity, and delete a rewards claimed entity.<br />

[<strong>Visual</strong> Basic]<br />

Dim claims As List(Of RewardsClaimedDTO) =<br />

service.GetRewardsClaimedForContact(1)<br />

Dim newClaim As New RewardsClaimedDTO()<br />

newClaim.NewPointsUsed = 100<br />

newClaim.RewardID = 1<br />

newClaim.ContactID = 1<br />

service.AddRewardsClaim(newClaim)<br />

[<strong>Visual</strong> C#]<br />

List claims =<br />

service.GetRewardsClaimedForContact(1);<br />

RewardsClaimedDTO newClaim = new RewardsClaimedDTO();<br />

newClaim.NewPointsUsed = 100;<br />

newClaim.RewardID = 1;<br />

newClaim.ContactID = 1;<br />

service.AddRewardsClaim(newClaim);<br />

[<strong>Visual</strong> Basic]


10-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Dim claims As List(Of RewardsClaimedDTO) =<br />

service.GetRewardsClaimedForContact(1)<br />

claims(1).NewPointsUsed = 50<br />

service.UpdateRewardsClaim(claims(1))<br />

[<strong>Visual</strong> C#]<br />

List claims =<br />

service.GetRewardsClaimedForContact(1);<br />

claims[1].NewPointsUsed = 50;<br />

service.UpdateRewardsClaim(claims[1]);<br />

[<strong>Visual</strong> Basic]<br />

Dim claims As List(Of RewardsClaimedDTO) =<br />

service.GetRewardsClaimedForContact(1)<br />

service.DeleteRewardsClaim(claims(1))<br />

[<strong>Visual</strong> C#]<br />

List claims =<br />

service.GetRewardsClaimedForContact(1);<br />

service.DeleteRewardsClaim(claims[1]);


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-27<br />

Demonstration: Change Tracking <strong>with</strong> <strong>Data</strong> Transfer Objects in an N-Tier<br />

Solution<br />

Key Points<br />

• An end-to-end walkthrough of a solution that uses DTOs.<br />

Demonstration Steps<br />

1. If you did not perform these steps for a previous walkthrough, in the E:\Demofiles folder, run<br />

Demo.bat to set up the database for this demonstration, and then run EnvSetup.bat to configure IIS.<br />

2. In the E:\Demofiles\Mod10\Demo3\Solution folder, run ExSetup.bat as an administrator to configure<br />

the virtual directory in IIS for the application.<br />

3. Start <strong>Visual</strong> <strong>Studio</strong> 2010 as administrator.<br />

4. Open the RewardsDTO solution.<br />

5. Review the code in the solution.<br />

6. Run the application.<br />

7. Close the application and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: What is the purpose of the attributes that are used in the DTO classes?


10-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Connecting to the <strong>Data</strong>base<br />

Key Points<br />

In an n-tier application, the data access layer must connect to the database to retrieve data and to persist<br />

changes. One of the key reasons to adopt an n-tier architecture for you application is to improve the<br />

scalability of your solution. Making your data access layer stateless is one way to improve the scalability,<br />

but you should also consider how your data access layer connects to the database. The connection string<br />

in the web.config file in your service project usually determines the properties of your database<br />

connection. The following three sections of this topic describe elements of the connection string that are<br />

important factors in determining the scalability of your solution.<br />

Connection Pooling<br />

Many databases are limited to support a maximum number of connections. The database license often<br />

determines this maximum number. To minimize the number of connections that your application uses,<br />

you should close connections as soon as you no longer require them. However, it takes time to open and<br />

close database connections. Connection pooling is a technique that manages the tradeoff between two<br />

strategies for managing database connections in your application. The first strategy is to hang on to open<br />

connections to improve the performance of your application at the expense of other database users. The<br />

second strategy is to free up connections as soon as possible for other users at the expense of a<br />

performance hit for your application. Connection pooling handles this tradeoff by creating a pool of a<br />

limited number of active connections and recycling these connections in your application. When your<br />

application closes a connection, it does not really close but instead returns to the pool. When your<br />

application opens a connection, it does not really open a new connection but instead gets an existing<br />

connection from the pool.<br />

In ADO.NET, connection pooling is enabled by default, and ADO.NET creates pools when applications<br />

create connections <strong>with</strong> the same configuration. This means that connection strings must be identical for<br />

ADO.NET to be able to pool connections.<br />

You can also fine-tune the behavior of the pool by using the connection string elements that the<br />

following table describes.


Name Description<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-29<br />

Max Pool Size The maximum number of connections that is allowed in the pool.<br />

Min Pool Size The minimum number of connections that is allowed in the pool.<br />

Pooling If the value is true, the database connection is drawn from the appropriate pool. If<br />

there is no free connection in the pool, a connection is created and added to the pool.<br />

Multiple Active Result Sets<br />

For some scenarios, enabling multiple active result sets (MARS) can provide performance benefits for your<br />

database connections. MARS is a feature of Microsoft SQL Server® that enables you to execute multiple<br />

batches on a single connection. This enables you to code scenarios that would otherwise require you to<br />

use server-side cursors. MARS is enabled by default, and you can control it by using the connection string.<br />

Connection Timeouts<br />

Another setting that is defined in the connection string and can affect the responsiveness of the client<br />

application is the connection timeout. This setting specifies the length of time a client should wait for a<br />

connection to the database before it gives up the attempt and raises an error.<br />

Protecting Your Connection Strings<br />

The connection strings in your application describe how you connect to your database, and in some cases,<br />

they can contain user names and passwords. You should take steps to protect this information in your<br />

application. The Microsoft .NET Framework enables you to encrypt sensitive information in configuration<br />

files by using a feature called Protected Configuration.<br />

The .NET Framework includes two protection providers:<br />

1. The RSAProtectedConfigurationProvider provider uses a public key encryption algorithm.<br />

2. The DPAPIProtectedConfigurationProvider provider uses the Windows <strong>Data</strong> Protection API to<br />

encrypt data.<br />

Question: What is the effect on the client application of setting the connection timeout to too long a<br />

value? What is the effect on the client application of setting the connection timeout to too short a value?<br />

Additional Reading<br />

For more information about connection strings, see the SqlConnection.ConnectionString Property page at<br />

http://go.microsoft.com/fwlink/?LinkID=194081.<br />

For more information about how to protect connection strings, see the Connection Strings and<br />

Configuration Files (ADO.NET) page at http://go.microsoft.com/fwlink/?LinkID=194082.


10-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 2<br />

Managing Exceptions in an N-Tier Solution<br />

Two common exceptions can be thrown when you save data modifications to the database. You need to<br />

know how to catch and handle these errors to ensure that your users are not affected by them.<br />

This lesson explains how to manage exceptions in an n-tier application and describes how to transfer<br />

exception information over the network to the client application.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the purpose of the UpdateException and OptimisticConcurrencyException exceptions.<br />

• Handle concurrency exceptions in an n-tier application.<br />

• Deal <strong>with</strong> concurrency in an n-tier application.


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-31<br />

The UpdateException and OptimisticConcurrencyException Exceptions<br />

Key Points<br />

The Entity Framework can throw two exceptions when you call the SaveChanges method of the<br />

ObjectContext object to persist changes to the database.<br />

The Entity Framework throws an UpdateException exception when it cannot persist the modifications in<br />

the entity objects that the ObjectContext object manages to the database. Reasons for this failure can<br />

include:<br />

• An entity object contains invalid foreign key data that results in a referential integrity violation.<br />

• A property value in an entity object is outside the range that the database permits.<br />

• The key of the entity object that you are trying to insert already exists in the database.<br />

In these types of scenario, you should report the error to the client. Your client application can then<br />

decide what to do.<br />

The Entity Framework throws an OptimisticConcurrencyException exception when it detects that<br />

another user or process has modified a record in the database since the current user or process retrieved<br />

the record and modified it. The Entity Framework detects this situation by comparing the value of all of<br />

the entity properties that you have marked <strong>with</strong> a Concurrency Mode property value of Fixed in the<br />

EDM <strong>with</strong> the current value in the database. If the two values are the same, the Entity Framework assumes<br />

that no one else has modified the record. It is important that either the application or the database<br />

changes the value of these fields in the database whenever the record is modified.<br />

If your application receives an OptimisticConcurrencyException exception, there are two options<br />

available:<br />

• You can assume that the data in the database is correct and discard the current set of changes.<br />

• You can decide to keep the current set of changes and overwrite the data in the database.


10-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Which option is correct will depend on the specific requirements of your application. You may decide to<br />

ask the user which option to take. In this case, you must ensure that you give the user all of the relevant<br />

information about the conflict so that he or she can make an informed decision.<br />

Question: How do STEs, SEs, and DTOs differ in how they help your application to detect conflicts?


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-33<br />

Handling OptimisticConcurrencyException Exceptions in the Client<br />

Key Points<br />

In an n-tier application, you can add code to your data access layer that detects<br />

OptimisticConcurrencyException exceptions and then automatically applies one of the two conflict<br />

resolution strategies: assume that the data in the database is correct and discard the changes or overwrite<br />

the record in the database <strong>with</strong> the current changes.<br />

If you decide to permit your client application to select the option to take, you must send details of the<br />

conflict to the client and then enable the client to instruct the data access layer as to which option to take.<br />

The data access layer can notify the client application that it has detected an<br />

OptimisticConcurrencyException exception by throwing a FaultException exception that it populates<br />

<strong>with</strong> details of the conflict.<br />

The client can use the information in the FaultException exception to decide which option to take, and it<br />

can then request the data access layer to try to save the changes again.<br />

The data access layer can implement the option that the client selects by using one of the overloaded<br />

versions of the Refresh method that take a RefreshMode parameter before saving the changes. The<br />

following table shows the possible values of the RefreshMode parameter.<br />

RefreshMode<br />

value Description<br />

ClientWins Loads an entity into the ObjectContext object and applies the changes from the client.<br />

StoreWins Loads an entity into the ObjectContext object and discard any changes from the client.<br />

Question: What types of information might the client find useful in helping it to determine how to<br />

resolve a conflict?


10-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Additional Reading<br />

For more information about how to manage concurrency, see the Saving Changes and Managing<br />

Concurrency (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194083.


Managing Concurrency <strong>with</strong> Self-Tracking Entities<br />

Key Points<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-35<br />

This topic describes how to extend the Adventure Works application that manages rewards claimed and<br />

contact entities to include conflict detection.<br />

The code examples in this topic assume that your solution uses STEs and that the contact and rewards<br />

claimed entities have at least one field marked <strong>with</strong> a Concurrency Mode property value of Fixed.<br />

The data access layer sends conflict data to the client by using a ConcurrencyFault object. The following<br />

code example shows the ConcurrencyFault class and the definition of the<br />

UpdateContactWithRelatedRewardsClaimed method in the service interface.<br />

[<strong>Visual</strong> Basic]<br />

<br />

Public Class ConcurrencyFault<br />

<br />

Public ExceptionMessage As String<br />

End Class<br />

...<br />

<br />

<br />

<br />

Sub UpdateContactWithRelatedRewardsClaimed(ByVal contact As Contact)<br />

[<strong>Visual</strong> C#]<br />

[<strong>Data</strong>Contract]<br />

public class ConcurrencyFault


10-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

{<br />

}<br />

...<br />

[<strong>Data</strong>Member]<br />

public string ExceptionMessage;<br />

[FaultContract(typeof(ServiceFault))]<br />

[FaultContract(typeof(ConcurrencyFault))]<br />

[OperationContract]<br />

void UpdateContactWithRelatedRewardsClaimed(Contact contact);<br />

The following code example shows the UpdateContactWithRelatedRewardsClaimed method <strong>with</strong><br />

exception handling that includes the OptimisticConcurrencyException exception.<br />

[<strong>Visual</strong> Basic]<br />

Public Sub UpdateContactWithRelatedRewardsClaimed(ByVal contact As Contact)<br />

ValidateContactUpdate(contact)<br />

DeleteRewardsClaimed(contact)<br />

ApplyContactValues(contact)<br />

' Save all of the changes.<br />

Using context As AdventureWorksEntities =<br />

New AdventureWorksEntities()<br />

context.Contacts.ApplyChanges(contact)<br />

Try<br />

context.SaveChanges()<br />

' This exception occurs if the SaveChanges method fails<br />

' because of a concurrency exception.<br />

Catch ocEx As OptimisticConcurrencyException<br />

' Create a ConcurrencyFault object and populate it<br />

' <strong>with</strong> information about the causes of the<br />

' concurrency exception.<br />

Dim cf As New ConcurrencyFault() With<br />

{<br />

.ExceptionMessage = eventMessage<br />

}<br />

' Throw a WCF FaultException exception that encapsulates<br />

' the ConcurrencyFault object.<br />

Throw New FaultException(Of ConcurrencyFault)(<br />

cf, "Concurrency violation")<br />

' This exception occurs if the SaveChanges method fails<br />

' because of some validation or inconsistency error<br />

' in the data.<br />

Catch uEx As UpdateException<br />

' Create a ServiceFault object and throw a<br />

' WCF FaultException exception.<br />

' The inner exception of the UpdateException object<br />

' usually contains the reason for the exception.<br />

Dim sf = New ServiceFault With<br />

{<br />

.ExceptionMessage = String.Format(<br />

"Update exception occurred in {0}: {1}",<br />

"UpdateContactWithRelatedRewardsClaimed",<br />

uEx.InnerException.Message)<br />

}<br />

Throw New FaultException(Of ServiceFault)(<br />

sf, "Update Failure")<br />

' Catch and log all of the other exceptions and throw a<br />

' generalized FaultException exception.<br />

Catch ex As Exception


End Sub<br />

End Try<br />

End Using<br />

[<strong>Visual</strong> C#]<br />

Throw New FaultException("Failure")<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-37<br />

public void UpdateContactWithRelatedRewardsClaimed(Contact contact)<br />

{<br />

ValidateContactUpdate(contact);<br />

DeleteRewardsClaimed(contact);<br />

ApplyContactValues(contact);<br />

// Save all of the changes.<br />

using (AdventureWorksEntities context =<br />

new AdventureWorksEntities())<br />

{<br />

context.Contacts.ApplyChanges(contact);<br />

try<br />

{<br />

}<br />

context.SaveChanges();<br />

// This exception occurs if the SaveChanges method fails<br />

// because of a concurrency exception.<br />

catch (OptimisticConcurrencyException ocEx)<br />

{<br />

// Create a ConcurrencyFault object and populate it<br />

// <strong>with</strong> information about the causes of the<br />

// concurrency exception.<br />

ConcurrencyFault cf = new ConcurrencyFault() {<br />

ExceptionMessage = eventMessage };<br />

}<br />

// Throw a WCF FaultException exception that encapsulates<br />

// the ConcurrencyFault object.<br />

throw new FaultException(<br />

cf, "Concurrency violation");<br />

// This exception occurs if the SaveChanges method fails<br />

// because of some validation or inconsistency error<br />

// in the data.<br />

catch (UpdateException uEx)<br />

{<br />

// Create a ServiceFault object and throw a<br />

// WCF FaultException exception.<br />

// The inner exception of the UpdateException object<br />

// usually contains the reason for the exception.<br />

ServiceFault sf = new ServiceFault<br />

{<br />

ExceptionMessage = string.Format(<br />

"Update exception occurred in {0}: {1}",<br />

"UpdateContactWithRelatedRewardsClaimed",<br />

uEx.InnerException.Message)


10-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

}<br />

}<br />

};<br />

throw new FaultException(<br />

sf, "Update Failure");<br />

// Catch and log all of the other exceptions and throw a<br />

// generalized FaultException exception.<br />

catch (Exception ex)<br />

{<br />

throw new FaultException("Failure");<br />

}<br />

In this scenario, the Entity Framework can detect a conflict in the contact when you add a new rewards<br />

claim, because the data access layer updates the PointsUsed property of the Contact object. Therefore,<br />

you should not allow the client to overwrite the changes in the database, because to do so would mean<br />

that the contact entity and its related rewards claimed entities were inconsistent.


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-39<br />

Demonstration: Managing Concurrency <strong>with</strong> Self-Tracking Entities<br />

Key Points<br />

• An end-to-end walkthrough of a solution that uses STEs.<br />

Demonstration Steps<br />

1. If you did not perform these steps for a previous walkthrough, in the E:\Demofiles folder, run<br />

Demo.bat to set up the database for this demonstration, and then run EnvSetup.bat to configure IIS.<br />

2. In the E:\Demofiles\Mod10\Demo4\Solution folder, run ExSetup.bat as an administrator to configure<br />

the virtual directory in IIS for the application.<br />

3. Start <strong>Visual</strong> <strong>Studio</strong> 2010 as administrator.<br />

4. Open the RewardsConcurrency solution.<br />

5. Review the code.<br />

6. Run the application.<br />

7. Close the application and then close <strong>Visual</strong> <strong>Studio</strong>.<br />

Question: How can you determine whether a concurrency exception is thrown because another user has<br />

modified a record in the database or because another user has deleted a record in the database?


10-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab: Handling Updates in an N-Tier Solution by Using<br />

the Entity Framework<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Track the changes that are made in a client application by using STEs.<br />

• Handle any concurrency errors that the data access layer detects when it saves changes to the<br />

database.<br />

Introduction<br />

In this lab, you will extend the Adventure Works Orders n-tier application to support data modifications<br />

by using STEs. You will enable the client application to select the strategy for the data access layer to use<br />

when the data access layer detects a concurrency error.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-10 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


Lab Scenario<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-41<br />

The Adventure Works Orders application is an n-tier application that enables users to search for orders by<br />

using different criteria. The client application is a Windows Presentation Foundation (WPF) application<br />

that uses a WCF service to access the EDM.<br />

You have been asked to extend this application to enable users to make changes to orders. Users must be<br />

able to create new orders, modify existing orders, and delete orders.<br />

This is a multiuser application, so it must be able to detect and handle any concurrency conflicts that<br />

occur when two or more users edit the same data simultaneously. Users of the application should be able<br />

to choose what action to take when the application detects a conflict.


10-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 1: Handling Updates in the <strong>Data</strong> <strong>Access</strong> Tier<br />

Scenario<br />

You have been asked to extend the existing Adventure Works Orders application to support the<br />

modification of order data. You will use the existing STE classes to transfer data modification requests to<br />

the data access layer. In this first phase, you will not detect any concurrency errors.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the environment for the lab.<br />

2. Create the virtual directory for the Web service.<br />

3. Enable Secure Sockets Layer (SSL) for the OrdersWebService Web service.<br />

4. Open the starter project for this exercise.<br />

5. Modify the entity model and rebuild the self-tracking entities.<br />

6. Define the PlaceOrder, CancelOrder, and AmendOrder methods in the OrdersService project.<br />

7. Implement the PlaceOrder, CancelOrder, and AmendOrder methods in the OrdersService project.<br />

8. Update the WPF client application.<br />

9. Add a unit test for the PlaceOrder, CancelOrder, and AmendOrder methods.<br />

10. Build and test the application.<br />

Task 1: Prepare the environment for the lab<br />

1. Log on to the 10265A-GEN-DEV-10 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator to install IIS and create the user<br />

accounts for the application.<br />

3. In the E:\Labfiles folder, run AWReset.bat to create the AdventureWorks database.<br />

Task 2: Create the virtual directory for the Web service<br />

• In the E:\Labfiles\Lab10\CS\Ex1\Starter folder (if you are using Microsoft <strong>Visual</strong> C#®) or the<br />

E:\Labfiles\Lab10\VB\Ex1\Starter folder (if you are using Microsoft <strong>Visual</strong> Basic®), run ExSetup.bat as<br />

an administrator. This script adds the required virtual directories to IIS.<br />

Task 3: Enable SSL for the OrdersWebService Web service<br />

1. Open IIS Manager as an administrator.<br />

2. Add a new self-signed certificate named OrdersWebService.<br />

3. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.<br />

4. Configure the OrdersWebService application to require SSL security.<br />

5. Close IIS Manager.<br />

Task 4: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010 as an administrator.<br />

2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab10\VB\Ex1\Starter\OrdersDAL or<br />

E:\Labfiles\Lab10\CS\Ex1\Starter\OrdersDAL folder.<br />

Task 5: Modify the entity model and rebuild the self-tracking entities<br />

1. Open the AdventureWorks EDM in the ADO.NET Entity <strong>Data</strong> Model Designer (Entity Designer).


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-43<br />

2. In the AdventureWorks EDM model, set the StoreGeneratedPattern property of the SubTotal field<br />

of the SalesOrderHeader entity to None.<br />

3. Save the AdventureWorks EDM model.<br />

4. Delete the existing T4 template files.<br />

5. Re-create the STEs by using the T4 templates.<br />

6. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.<br />

7. Close the AdventureWorks EDM model.<br />

8. Review the task list.<br />

9. Open the AdditionalMethods code file by double-clicking the TODO: Ex1 - Add<br />

CalculateOrderTotal method to recalculate the sub total task in the task list. This task is located in<br />

the partial SalesOrderHeader class.<br />

10. Immediately after the TODO: Ex1 - Add CalculateOrderTotal method to recalculate the sub total<br />

comment, add a void method called CalculateOrderTotal that recalculates the value of the SubTotal<br />

property of the SalesOrderHeader object. For each SalesOrderDetail object, the total is calculated<br />

according the following formula:<br />

LineTotal = UnitPrice * (1 - UnitPriceDiscount) * Quantity<br />

11. Locate the next comment in the AdditionalMethods file by double-clicking the TODO: Ex1 - Validate<br />

an order object and verify that it contains order details task in the task list. This task is located in<br />

the partial SalesOrderHeader class.<br />

12. Immediately after the TODO: Ex1 - Validate an order object and verify that it contains order<br />

details comment, add a method called Validate that returns a Boolean value. This method should<br />

return true if the SalesOrderHeader object contains at least one SalesOrderDetail object; otherwise,<br />

it should return false.<br />

13. Save the AdditionalMethods file.<br />

Task 6: Define the PlaceOrder, CancelOrder, and AmendOrder methods in the<br />

OrdersService project<br />

1. Review the task list.<br />

2. Open the IOrdersService code file by double-clicking the TODO: Ex1 - PlaceOrder task in the task<br />

list. This task is located in the IOrdersService interface.<br />

3. Immediately after the TODO: Ex1 - PlaceOrder comment, define a method called PlaceOrder that<br />

returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method <strong>with</strong><br />

the OperationContract attribute and the FaultContract attribute <strong>with</strong> a type parameter of<br />

ServiceFault.<br />

4. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex1 -<br />

AmendOrder task in the task list. This task is located in the IOrdersService interface.<br />

5. Immediately after the TODO: Ex1 - AmendOrder comment, define a method called AmendOrder<br />

that returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method<br />

<strong>with</strong> the OperationContract attribute and the FaultContract attribute <strong>with</strong> a type parameter of<br />

ServiceFault.<br />

6. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex1 - CancelOrder<br />

task in the task list. This task is located in the IOrdersService interface.<br />

7. Immediately after the TODO: Ex1 - CancelOrder comment, define a method called CancelOrder<br />

that returns a Boolean value and takes a SalesOrderHeader object as a parameter. Mark the method<br />

<strong>with</strong> the OperationContract attribute and the FaultContract attribute <strong>with</strong> a type parameter of<br />

ServiceFault.<br />

8. Save the IOrdersService file.


10-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 7: Implement the PlaceOrder, CancelOrder, and AmendOrder methods in the<br />

OrdersService project<br />

1. Review the task list.<br />

2. Open the OrdersServiceImpl file by double-clicking the TODO: Ex1 -<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base task in the task list. This task is located<br />

in the OrdersServiceImpl class.<br />

3. Immediately after the TODO: Ex1 - updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base<br />

comment, define a method called updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base that<br />

returns a Boolean value and takes a string object called operationName as the first parameter and a<br />

SalesOrderHeader object called order as the second parameter. In the method, write code to<br />

perform the following tasks:<br />

a. Create a new AdventureWorksEntities context object.<br />

b. Call the ApplyChanges method on the SalesOrderHeaders entity set object in this context,<br />

passing the order object as a parameter.<br />

c. Call the SaveChanges method of the context object. Return true if the number of changes that<br />

are saved is greater than zero; otherwise, return false.<br />

d. If any exceptions are thrown, record the details by calling the logException method, and then<br />

throw a new FaultException exception. Specify the value of the operationName parameter and<br />

the order ID for the order in the log message and the fault.<br />

4. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -<br />

PlaceOrder implementation task in the task list. This task is located in the OrdersServiceImpl class.<br />

5. Immediately after the TODO: Ex1 - PlaceOrder implementation comment, define a method called<br />

PlaceOrder that returns a Boolean value and takes a SalesOrderHeader object called newOrder as a<br />

parameter. Mark the method <strong>with</strong> the PrincipalPermission attribute and specify that this method<br />

can only be run by members of the OrderAdmin security role. In the method, write code to perform<br />

the following tasks:<br />

a. Call the Validate method on the SalesOrderHeader object. If the validation fails, create a<br />

ServiceFault object <strong>with</strong> the message "Order has no details" and throw a new FaultException<br />

exception that wraps this ServiceFault object together <strong>with</strong> the message "Orders must have<br />

details".<br />

b. Call the CalculateOrderTotal method on the SalesOrderHeader object.<br />

c. Call the updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method, passing the string<br />

"PlaceOrder" as the first parameter and the newOrder object as the second parameter, and<br />

return the result of the method call.<br />

6. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -<br />

AmendOrder implementation task in the task list. This task is located in the OrdersServiceImpl<br />

class.<br />

7. Immediately after the TODO: Ex1 - AmendOrder implementation comment, define a method<br />

called AmendOrder that returns a Boolean value and takes a SalesOrderHeader object called order<br />

as a parameter. Mark the method <strong>with</strong> the PrincipalPermission attribute and specify that this<br />

method can only be run by members of the OrderAdmin security role. In the method, write code to<br />

perform the following tasks:<br />

a. Call the Validate method on the SalesOrderHeader object. If the validation fails, create a<br />

ServiceFault object <strong>with</strong> the message "Order has no details" and throw a new FaultException<br />

exception that wraps this ServiceFault object together <strong>with</strong> the message "Orders must have<br />

details".


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-45<br />

b. Call the CalculateOrderTotal method on the SalesOrderHeader object.<br />

c. Call the updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method, passing the string<br />

"AmendOrder" as the first parameter and the order object as the second parameter, and return<br />

the result of the method call.<br />

8. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex1 -<br />

CancelOrder implementation task in the task list. This task is located in the OrdersServiceImpl<br />

class.<br />

9. Immediately after the TODO: Ex1 - CancelOrder implementation comment, define a method called<br />

CancelOrder that returns a Boolean value and takes a SalesOrderHeader object called order as a<br />

parameter. Mark the method <strong>with</strong> the PrincipalPermission attribute and specify that this method<br />

can only be run by members of the OrderAdmin security role. In the method, write code to perform<br />

the following tasks:<br />

a. Call the MarkAsDeleted method on the order object.<br />

b. Call the updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method, passing the string<br />

"CancelOrder" as the first parameter and the order object as the second parameter, and return<br />

the result of the method call.<br />

10. Save the OrdersServiceImpl file.<br />

11. Build the solution and correct any errors.<br />

Task 8: Update the WPF client application<br />

1. In the OrderManagement project, open the OrderManagementWindow.xaml file.<br />

2. Click the Orders By Contact tab. The functionality that the controls on this tab provide has been<br />

extended to enable a user to create, edit, and delete orders for a contact, as follows:<br />

a. The New Order button creates a new order. Pressing INSERT in the TreeView control also<br />

creates a new order.<br />

b. Pressing DELETE in the TreeView control deletes an order.<br />

c. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an<br />

order; you cannot delete items from an order.<br />

3. Update the service reference in the OrderManagement project.<br />

4. Review the task list.<br />

5. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the<br />

TODO: Ex1 - Save the order to the database task in the task list. This task is located in the<br />

editOrder method, which is called when the user has made changes to an order and wants to save<br />

them.<br />

6. Immediately after the TODO: Ex1 - Save the order to the database comment, write code to<br />

perform the following tasks:<br />

a. Call the AmendOrder method of the service object, passing the order object as a parameter.<br />

b. If the call returns true, set the Content property of the statusOfLastOperation object to "Order<br />

saved". This object is a status bar item that appears at the bottom of the window.<br />

c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order<br />

not saved".<br />

7. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking<br />

the TODO: Ex1 - Delete the order from the database task in the task list. This task is located in the<br />

deleteOrder method, which is called when the user wants to cancel an order.


10-46 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

8. Immediately after the TODO: Ex1 - Delete the order from the database comment, write code to<br />

perform the following tasks:<br />

a. Call the CancelOrder method of the service object, passing the order object as a parameter.<br />

b. If the call returns true, set the Content property of the statusOfLastOperation object to "Order<br />

deleted".<br />

c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order<br />

not deleted".<br />

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

the TODO: Ex1 - Add the new order to the database task in the task list. This task is located in the<br />

addOrder method, which is called when the user wants to place a new order for a contact.<br />

10. Immediately after the TODO: Ex1 - Add the new order to the database comment, write code to<br />

perform the following tasks:<br />

a. Call the PlaceOrder method of the service object, passing the order object as a parameter.<br />

b. If the call returns true, set the Content property of the statusOfLastOperation object to "New<br />

order saved".<br />

c. If the call returns false, set the Content property of the statusOfLastOperation object to "Order<br />

not saved".<br />

d. Update the user interface (UI) by simulating the user double-clicking the getOrdersForContact<br />

button.<br />

11. Save the code file behind the OrderManagementWindow.xaml window.<br />

Task 9: Add a unit test for the PlaceOrder, CancelOrder, and AmendOrder methods<br />

1. Update the service reference in the OrdersServiceTest project.<br />

2. Review the task list.<br />

3. Open the OrdersServiceImplTest file by double-clicking the TODO: Ex1 - Implement a test for<br />

PlaceOrder, AmendOrder, CancelOrder task in the task list. This task is located in the<br />

CreateUpdateDeleteOrderTest method.<br />

4. Immediately after the TODO: Ex1 - Implement a test for PlaceOrder, AmendOrder, CancelOrder<br />

comment, add the following test code. This code performs the following tasks:<br />

a. It connects to the Web service as the user Bert <strong>with</strong> a password of Pa$$w0rd. This user is a<br />

member of the OrderAdmin role.<br />

b. It places a new order and verifies that the service has added the order correctly. It uses the<br />

CreateOrder helper method to create a new order and populate a SalesOrderHeader object.<br />

c. It modifies the order that was just added and verifies that the service has modified the order<br />

correctly.<br />

d. It deletes the order that was just added and verifies that the service has deleted the order<br />

correctly.<br />

[<strong>Visual</strong> Basic]<br />

service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"<br />

service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"<br />

Dim productID As Integer = 709<br />

Dim expectedOrderCount As Integer = 188<br />

Dim expectedOrderDetailsCount As Integer = 2<br />

' Create a new order.<br />

Dim order As SalesOrderHeader = CreateOrder()


order.CalculateOrderTotal()<br />

service.PlaceOrder(order)<br />

Dim actual As IEnumerable(Of SalesOrderHeader) =<br />

service.GetOrdersForProduct(productID)<br />

' Check the Order Header was added.<br />

Assert.AreEqual(expectedOrderCount + 1, actual.Count())<br />

' Check the Order Details were added.<br />

Dim addedOrder As SalesOrderHeader = actual.Last()<br />

Assert.AreEqual(expectedOrderDetailsCount,<br />

addedOrder.SalesOrderDetails.Count)<br />

' Modify the order.<br />

Dim expectedComment As String = "Modified"<br />

addedOrder.Comment = expectedComment<br />

service.AmendOrder(addedOrder)<br />

actual = service.GetOrdersForProduct(productID)<br />

Dim modifiedOrder As SalesOrderHeader = actual.Last()<br />

Assert.AreEqual(expectedComment, modifiedOrder.Comment)<br />

' Delete the order.<br />

service.CancelOrder(modifiedOrder)<br />

actual = service.GetOrdersForProduct(productID)<br />

Assert.AreEqual(expectedOrderCount, actual.Count())<br />

[<strong>Visual</strong> C#]<br />

service.ClientCredentials.Windows.ClientCredential.UserName<br />

= "Bert";<br />

service.ClientCredentials.Windows.ClientCredential.Password<br />

= "Pa$$w0rd";<br />

int productID = 709;<br />

int expectedOrderCount = 188;<br />

int expectedOrderDetailsCount = 2;<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-47<br />

// Create a new order.<br />

SalesOrderHeader order = CreateOrder();<br />

order.CalculateOrderTotal();<br />

service.PlaceOrder(order);<br />

IEnumerable actual = service.GetOrdersForProduct(productID);<br />

// Check the Order Header was added.<br />

Assert.AreEqual(expectedOrderCount+1, actual.Count());<br />

// Check the Order Details were added.<br />

SalesOrderHeader addedOrder = actual.Last();<br />

Assert.AreEqual(expectedOrderDetailsCount, addedOrder.SalesOrderDetails.Count);<br />

// Modify the order.<br />

string expectedComment = "Modified";<br />

addedOrder.Comment = expectedComment;<br />

service.AmendOrder(addedOrder);<br />

actual = service.GetOrdersForProduct(productID);<br />

SalesOrderHeader modifiedOrder = actual.Last();


10-48 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Assert.AreEqual(expectedComment, modifiedOrder.Comment);<br />

// Delete the order.<br />

service.CancelOrder(modifiedOrder);<br />

actual = service.GetOrdersForProduct(productID);<br />

Assert.AreEqual(expectedOrderCount, actual.Count());<br />

5. Save the OrdersServiceImplTest file.<br />

Task 10: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Run all of the tests in the solution.<br />

3. Verify that all of the tests succeed.<br />

4. Start the application in Debug mode.<br />

5. In the Order Management window perform the following tasks:<br />

a. Click the Orders By Contact tab.<br />

b. In the Username box, type Bert<br />

c. In the Password box, type Pa$$w0rd<br />

d. In the Contact ID box, type 200 and then click Get. Verify that you can add a new order, modify<br />

an existing order, and delete an order. Note that you can add line items to an order, but you<br />

cannot remove them after an order has been saved. However, you can amend existing line items.<br />

Note the following features of the application and the data in the database:<br />

a. Make sure that you use a valid product ID, for example, 905 or 906.<br />

b. Make sure that you use a discount of less than 1.0, for example, 0.05 otherwise the order will not<br />

be saved (a constraint in the database prevents you from creating orders that have a negative<br />

value.)<br />

c. The New Order button creates a new order. Pressing INSERT in the TreeView control also<br />

creates a new order.<br />

d. Pressing DELETE in the TreeView control deletes an order.<br />

e. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an<br />

order; you cannot delete items from an order.<br />

6. Close the application.<br />

7. Close the solution.


Exercise 2: Detecting and Handling Order Conflicts<br />

Scenario<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-49<br />

You have been asked to enhance the application so that it detects concurrency errors when two or more<br />

users attempt to edit the same order data. When the application detects a conflict, the client application<br />

should give the current user the choice of whether to overwrite the existing data in the database or<br />

discard the changes.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Modify the entity model and rebuild the self-tracking entities.<br />

3. Add the types that are required to serialize faults to the client.<br />

4. Modify the service implementation to detect and handle concurrency issues.<br />

5. Update the WPF client application.<br />

6. Update the unit tests for the PlaceOrder, CancelOrder, and AmendOrder methods.<br />

7. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

1. In the E:\Labfiles\Lab10\CS\Ex2\Starter folder (if you are using <strong>Visual</strong> C#) or the<br />

E:\Labfiles\Lab10\VB\Ex2\Starter folder (if you are using <strong>Visual</strong> Basic), run ExSetup.bat as an<br />

administrator.<br />

2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab10\VB\Ex2\Starter\OrdersDAL or<br />

E:\Labfiles\Lab10\CS\Ex2\Starter\OrdersDAL folder.<br />

Task 2: Modify the entity model and rebuild the self-tracking entities<br />

1. Open the AdventureWorks EDM in the Entity Designer.<br />

2. In the AdventureWorks EDM model, set the Concurrency Mode property of the RevisionNumber<br />

field of the SalesOrderHeader entity to Fixed.<br />

3. Save the AdventureWorks EDM model.<br />

4. Delete the existing T4 template files.<br />

5. Re-create the STEs by using the T4 templates.<br />

6. Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.<br />

7. Close the AdventureWorks EDM model.<br />

Task 3: Add the types that are required to serialize faults to the client<br />

1. Review the task list.<br />

2. Open the IOrdersService file by double-clicking the TODO: Ex2 - Create<br />

OptimisticConcurrencyExceptionReason enumeration task in the task list. This task is located in<br />

the IOrdersService file.<br />

3. Immediately after the TODO: Ex2 - Create OptimisticConcurrencyExceptionReason enumeration<br />

comment, add an enumeration called OptimisticConcurrencyExceptionReason <strong>with</strong> three values<br />

called None, ItemAlreadyDeleted, and ItemAlreadyAddedOrUpdated. Mark the enumeration <strong>with</strong><br />

the <strong>Data</strong>Contract attribute, and mark each value <strong>with</strong> the EnumMember attribute.<br />

4. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Create<br />

ConcurrencyFault class task in the task list.


10-50 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

5. Immediately after the TODO: Ex2 - Create ConcurrencyFault class comment, add a class called<br />

ConcurrencyFault <strong>with</strong> two public fields. The first field is called Reason and is of type<br />

OptimisticConcurrencyExceptionReason. The second field is called ConflictingValues and is of<br />

type Dictionary; both the key and the value are strings. Mark the class <strong>with</strong> the <strong>Data</strong>Contract<br />

attribute, and mark each field <strong>with</strong> the <strong>Data</strong>Member attribute. Additionally, mark the class <strong>with</strong> the<br />

ServiceKnownType attribute and specify the type of the OptimisticConcurrencyException<br />

enumeration; this enables WCF to serialize and deserialize the Reason field correctly.<br />

6. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Define<br />

ConflictResolutionStrategy enumeration task in the task list. This task is located in the<br />

OrdersService namespace.<br />

7. Immediately after the TODO: Ex2 - Define ConflictResolutionStrategy enumeration comment,<br />

add an enumeration called ConflictResolutionStrategy <strong>with</strong> three values called None, ClientWins,<br />

and StoreWins. Mark the enumeration <strong>with</strong> the <strong>Data</strong>Contract attribute, and mark each value <strong>with</strong><br />

the EnumMember attribute.<br />

8. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

ConflictResolutionStrategy as a known serializable type for the service task in the task list. This<br />

task is located just before the IOrdersService interface.<br />

9. Delete the TODO: Ex2 - Add ConflictResolutionStrategy as a known serializable type for the<br />

service comment, and replace it <strong>with</strong> the ServiceKnownType attribute <strong>with</strong> a parameter of type<br />

ConflictResolutionStrategy.<br />

10. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

ConcurrencyFault to the list of faults thrown by PlaceOrder task in the task list. This task is<br />

located just before the PlaceOrder method.<br />

11. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by PlaceOrder<br />

comment, and replace it <strong>with</strong> the FaultContract attribute <strong>with</strong> a parameter of type<br />

ConcurrencyFault.<br />

12. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

parameters to specify the conflict resolution strategy to use in PlaceOrder task in the task list.<br />

This task is located just before the PlaceOrder method.<br />

13. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in<br />

PlaceOrder comment, and modify the parameter list of the PlaceOrder method to include two<br />

additional parameters. The first new parameter is a Boolean parameter called<br />

resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type<br />

ConflictResolutionStrategy.<br />

14. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

ConcurrencyFault to the list of faults thrown by AmendOrder task in the task list. This task is<br />

located just before the AmendOrder method.<br />

15. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by AmendOrder<br />

comment, and replace it <strong>with</strong> the FaultContract attribute <strong>with</strong> a parameter of type<br />

ConcurrencyFault.<br />

16. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

parameters to specify the conflict resolution strategy to use in AmendOrder task in the task list.<br />

This task is located just before the AmendOrder method.<br />

17. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in<br />

AmendOrder comment, and modify the parameter list of the AmendOrder method to include two<br />

additional parameters. The first new parameter is a Boolean parameter called<br />

resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type<br />

ConflictResolutionStrategy.


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-51<br />

18. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

ConcurrencyFault to the list of faults thrown by CancelOrder task in the task list. This task is<br />

located just before the CancelOrder method.<br />

19. Delete the TODO: Ex2 - Add ConcurrencyFault to the list of faults thrown by CancelOrder<br />

comment, and replace it <strong>with</strong> the FaultContract attribute <strong>with</strong> a parameter of type<br />

ConcurrencyFault.<br />

20. Locate the next comment in the IOrdersService file by double-clicking the TODO: Ex2 - Add<br />

parameters to specify the conflict resolution strategy to use in CancelOrder task in the task list.<br />

This task is located just before the CancelOrder method.<br />

21. Delete the TODO: Ex2 - Add parameters to specify the conflict resolution strategy to use in<br />

CancelOrder comment, and modify the parameter list of the CancelOrder method to include two<br />

additional parameters. The first new parameter is a Boolean parameter called<br />

resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type<br />

ConflictResolutionStrategy.<br />

22. Save the IOrdersService file.<br />

Task 4: Modify the service implementation to detect and handle concurrency issues<br />

1. Review the task list.<br />

2. Open the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Save changes to the database,<br />

and possibly attempt to resolve any concurrency errors task in the task list. This task is located in the<br />

saveChangesTo<strong>Data</strong>base method.<br />

3. Immediately after the TODO: Ex2 - Save changes to the database, and possibly attempt to<br />

resolve any concurrency errors comment, write code to perform the following tasks:<br />

a. Call the SaveChanges method of the context object in a try block.<br />

b. If the number of changes is greater than zero, return true; otherwise, return false.<br />

c. Create a catch block to handle OptimisticConcurrencyException exceptions.<br />

d. If the resolveConcurrencyException parameter is false, re-throw the exception.<br />

e. If the resolveConcurrencyException parameter is true, check the value of the resolutionStrategy<br />

parameter:<br />

i. If the value of the resolutionStrategy parameter is StoreWins, call the Refresh method of the<br />

context object to refresh the contents of the changedObject object from the database, and<br />

then return false.<br />

Note that the changedObject object is a parameter that is passed to the<br />

saveChangesTo<strong>Data</strong>base method. It references the object that is updated, inserted, or<br />

deleted from the database.<br />

ii. If the value of the resolutionStrategy parameter is ClientWins, call the Refresh method of<br />

the context object to refresh the contents of the changedObject object from the client. If<br />

the number of changes is greater than zero, return true; otherwise, return false.<br />

iii. For any other value of the resolutionStrategy parameter, throw a new Exception exception to<br />

report an invalid conflict resolution strategy.<br />

4. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a<br />

ConcurrencyFault object, and populate it <strong>with</strong> the conflicting values task in the task list. This task<br />

is located in the determineCauseOfOptimisticConcurrencyException method. This method runs<br />

when a concurrency fault occurs when saving an order to the database. The purpose of this method is<br />

to determine the cause of the concurrency exception.


10-52 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

5. Immediately after the TODO: Ex2 - Create a ConcurrencyFault object, and populate it <strong>with</strong> the<br />

conflicting values comment, write code to perform the following tasks:<br />

a. Create a new ConcurrencyFault object.<br />

b. Use a Language-Integrated Query (LINQ) to Entities query to determine whether the order<br />

object is still in the database.<br />

c. If the order is not found, set the Reason property of the ConcurrencyFault object to<br />

ItemAlreadyDeleted, and set the ConflictingValues property of the ConcurrencyFault object<br />

to null (Nothing in <strong>Visual</strong> Basic).<br />

d. If the order has been updated by another user, set the Reason property of the<br />

ConcurrencyFault object to ItemAlreadyAddedOrUpdated, and add the properties of the<br />

order in the database to a new Dictionary object in the ConflictingValues property of the<br />

ConcurrencyFault object.<br />

e. Return the populated ConcurrencyFault object.<br />

6. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add<br />

parameters to the updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base task in the task list.<br />

This task is located just before the updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base<br />

method.<br />

7. Delete the TODO: Ex2 - Add parameters to the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base comment, and modify the parameter<br />

list of the updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method to include two<br />

additional parameters. The first new parameter is a Boolean parameter called<br />

resolveConcurrencyException. The second new parameter is called resolutionStrategy and is of type<br />

ConflictResolutionStrategy.<br />

8. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Log the<br />

details of the concurrency exception task in the task list. This task is located in the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method.<br />

9. Immediately after the TODO: Ex2 - Log the details of the concurrency exception comment, add<br />

code that creates a string containing an error message that includes the operation name and the sales<br />

order ID. Write the message to the log file by calling the logException method.<br />

10. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a<br />

ConcurrencyFault object and throw a WCF FaultException that encapsulates the<br />

ConcurrencyFault object task in the task list. This task is located in the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method.<br />

11. Immediately after the TODO: Ex2 - Create a ConcurrencyFault object and throw a WCF<br />

FaultException that encapsulates the ConcurrencyFault object comment, add code that creates a<br />

ConcurrencyFault object by calling the determineCauseOfOptimisticConcurrencyException<br />

method and throws a new FaultException exception of type ConcurrencyFault containing this<br />

ConcurrencyFault object.<br />

12. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Log the<br />

details of the update exception task in the task list. This task is located in the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method.<br />

13. Immediately after the TODO: Ex2 - Log the details of the update exception comment, add code<br />

that creates a string containing an error message that includes the operation name and the sales<br />

order ID. Write the message to the log file by calling the logException method.<br />

14. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Create a<br />

ServiceFault object and throw a WCF FaultException task in the task list. This task is located in the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method.


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-53<br />

15. Immediately after the TODO: Ex2 - Create a ServiceFault object and throw a WCF FaultException<br />

comment, add code that creates a ServiceFault object by using the message from the<br />

InnerException property of the UpdateException exception and throws a new FaultException<br />

exception of type ServiceFault by using this ServiceFault object.<br />

16. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add<br />

parameters to the PlaceOrder method task in the task list. This task is located just before the<br />

PlaceOrder method.<br />

17. Delete the TODO: Ex2 - Add parameters to the PlaceOrder method comment, and modify the<br />

parameter list of the PlaceOrder method to include two additional parameters. The first new<br />

parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is<br />

called resolutionStrategy and is of type ConflictResolutionStrategy.<br />

18. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass<br />

parameters from the PlaceOrder method task in the task list. This task is located in the PlaceOrder<br />

method.<br />

19. Modify the parameter list of the call to the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method to include two additional<br />

parameters. The first new parameter is the resolveConcurrencyException object. The second new<br />

parameter is the resolutionStrategy object.<br />

20. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add<br />

parameters to the AmendOrder method task in the task list. This task is located just before the<br />

AmendOrder method.<br />

21. Delete the TODO: Ex2 - Add parameters to the AmendOrder method comment, and modify the<br />

parameter list of the AmendOrder method to include two additional parameters. The first new<br />

parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is<br />

called resolutionStrategy and is of type ConflictResolutionStrategy.<br />

22. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass<br />

parameters from the AmendOrder method task in the task list. This task is located in the<br />

AmendOrder method.<br />

23. Modify the parameter list of the call to the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method to include two additional<br />

parameters. The first new parameter is the resolveConcurrencyException object. The second new<br />

parameter is the resolutionStrategy object.<br />

24. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Add<br />

parameters to the CancelOrder method task in the task list. This task is located just before the<br />

CancelOrder method.<br />

25. Delete the TODO: Ex2 - Add parameters to the CancelOrder method comment, and modify the<br />

parameter list of the CancelOrder method to include two additional parameters. The first new<br />

parameter is a Boolean parameter called resolveConcurrencyException. The second new parameter is<br />

called resolutionStrategy and is of type ConflictResolutionStrategy.<br />

26. Locate the next comment in the OrdersServiceImpl file by double-clicking the TODO: Ex2 - Pass<br />

parameters from the CancelOrder method task in the task list. This task is located in the<br />

CancelOrder method.<br />

27. Modify the parameter list of the call to the<br />

updateOrderEntityCollectionAndSaveChangesTo<strong>Data</strong>base method to include two additional<br />

parameters. The first new parameter is the resolveConcurrencyException object. The second new<br />

parameter is the resolutionStrategy object.<br />

28. Save the OrdersServiceImpl file.


10-54 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

29. Build the OrdersService project and correct any errors.<br />

Task 5: Update the WPF client application<br />

1. Update the service reference in the OrderManagement project.<br />

2. Review the task list.<br />

3. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the<br />

TODO: Ex2 - Try to save the order to the database task in the task list. This task is located in the<br />

editOrder method.<br />

4. Immediately after the TODO: Ex2 - Try to save the order to the database comment, write code to<br />

perform the following tasks:<br />

a. Call the AmendOrder method of the service object, passing the order object as the first<br />

parameter, false as the second parameter, and specifying a conflict resolution strategy of None<br />

as the third parameter.<br />

b. If the call returns true, set the Content property of the statusOfLastOperation status bar item<br />

to "Order saved".<br />

c. If the call returns false, set the Content property of the statusOfLastOperation status bar item<br />

to "Order not saved".<br />

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Ex2 - Handle a concurrency exception in AmendOrder task in the task<br />

list. This task is located in the editOrder method.<br />

6. Immediately after the TODO: Ex2 - Handle a concurrency exception in AmendOrder comment,<br />

write code to perform the following tasks:<br />

a. If the concurrency fault was caused by another user deleting the item, set the Content property<br />

of the statusOfLastOperation status bar item to "Order already cancelled by another user".<br />

b. If the concurrency fault was caused by another user amending the item, set the Content property<br />

of the statusOfLastOperation status bar item to "Order changed by another user", and then<br />

prompt the user by using a message box to determine whether he or she still wants to save the<br />

changes.<br />

c. If the user replies "Yes", call the AmendOrder method of the service object, passing the order<br />

object as the first parameter, true as the second parameter, and specifying a conflict resolution<br />

strategy of ClientWins as the third parameter, and then set the Content property of the<br />

statusOfLastOperation status bar item to "Order saved".<br />

d. f the user replies "No", call the AmendOrder method of the service object, passing the order<br />

object as the first parameter, true as the second parameter, and specifying a conflict resolution<br />

strategy of StoreWins as the third parameter, and then set the Content property of the<br />

statusOfLastOperation status bar item to "Order not saved".<br />

7. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking<br />

the TODO: Ex2 - Try to delete the order from the database task in the task list. This task is located<br />

in the deleteOrder method.<br />

8. Immediately after the TODO: Ex2 - Try to delete the order from the database comment, write<br />

code to perform the following tasks:<br />

a. Call the CancelOrder method of the service object, passing the order object as the first<br />

parameter, false as the second parameter, and specifying a conflict resolution strategy of None<br />

as the third parameter.<br />

b. If the call returns true, set the Content property of the statusOfLastOperation status bar item<br />

to "Order deleted".


Handling Updates in an N-Tier Solution by Using the Entity Framework 10-55<br />

c. If the call returns false, set the Content property of the statusOfLastOperation status bar item<br />

to "Order not deleted".<br />

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

the TODO: Ex2 - Handle a concurrency exception in CancelOrder task in the task list. This task is<br />

located in the deleteOrder method.<br />

10. Immediately after the TODO: Ex2 - Handle a concurrency exception in CancelOrder comment,<br />

write code to perform the following tasks:<br />

a. If the concurrency fault was caused by another user deleting the item, set the Content property<br />

of the statusOfLastOperation status bar item to "Order already cancelled by another user".<br />

b. If the concurrency fault was caused by another user amending the item, set the Content property<br />

of the statusOfLastOperation status bar item to "Order changed by another user", and then<br />

prompt the user by using a message box to determine whether he or she still wants to cancel the<br />

order.<br />

c. If the user replies "Yes", call the AmendOrder method of the service object, passing the order<br />

object as the first parameter, true as the second parameter, and specifying a conflict resolution<br />

strategy of ClientWins as the third parameter, and then set the Content property of the<br />

statusOfLastOperation status bar item to "Order cancelled".<br />

d. If the user replies "No", call the AmendOrder method of the service object, passing the order<br />

object as the first parameter, true as the second parameter, and specifying a conflict resolution<br />

strategy of StoreWins as the third parameter, and then set the Content property of the<br />

statusOfLastOperation status bar item to "Order not cancelled".<br />

11. Locate the next comment in the OrderManagementWindow.xaml code-behind file by double-clicking<br />

the TODO: Ex2 - Try to save the new order to the database task in the task list. This task is located<br />

in the addOrder method.<br />

12. Immediately after the TODO: Ex2 - Try to save the new order to the database comment, write<br />

code to perform the following tasks:<br />

a. Call the PlaceOrder method of the service object, passing the order object as the first<br />

parameter, false as the second parameter, and specifying a conflict resolution strategy of None<br />

as the third parameter.<br />

b. If the call returns true, set the Content property of the statusOfLastOperation status bar item<br />

to "New order saved".<br />

c. If the call returns false, set the Content property of the statusOfLastOperation status bar item<br />

to "Order not saved".<br />

13. Save the OrderManagementWindow.xaml code-behind file.<br />

Task 6: Update the unit tests for the PlaceOrder, CancelOrder, and AmendOrder<br />

methods<br />

1. Update the service reference in the OrdersServiceTest project.<br />

2. Review the task list.<br />

3. Open the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass additional<br />

parameters to PlaceOrder task in the task list. This task is located in the<br />

CreateUpdateDeleteOrderTest method.<br />

4. Immediately after the TODO: Ex2 - Pass additional parameters to PlaceOrder comment, modify<br />

the call to the PlaceOrder method to include two additional parameters. The first new parameter to<br />

the PlaceOrder method is true, and the second new parameter specifies a conflict resolution strategy<br />

of StoreWins.


10-56 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

5. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass<br />

additional parameters to AmendOrder task in the task list. This task is located in the<br />

CreateUpdateDeleteOrderTest method.<br />

6. Immediately after the TODO: Ex2 - Pass additional parameters to AmendOrder comment, modify<br />

the call to the AmendOrder method to include two additional parameters. The second parameter to<br />

the AmendOrder method is true, and the third parameter specifies a conflict resolution strategy of<br />

StoreWins.<br />

7. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Pass<br />

additional parameters to CancelOrder task in the task list. This task is located in the<br />

CreateUpdateDeleteOrderTest method.<br />

8. Immediately after the TODO: Ex2 - Pass additional parameters to CancelOrder comment, modify<br />

the call to the CancelOrder method to include two additional parameters. The second parameter to<br />

the CancelOrder method is true, and the third parameter specifies a conflict resolution strategy of<br />

StoreWins.<br />

9. Locate the next comment in the OrdersServiceImplTest file by double-clicking the TODO: Ex2 - Try<br />

to modify the original copy and test for a ConcurrencyFault task in the task list. This task is<br />

located in the AmendOrderConcurrencyTest method.<br />

10. Immediately after the TODO: Ex2 - Try to modify the original copy and test for a<br />

ConcurrencyFault comment, modify the Comment property of the addedOrder object. Next, call<br />

the AmendOrder method and add a test to check that the service returns a concurrency fault, as the<br />

following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

addedOrder.Comment = "Original order"<br />

Dim expected As Boolean = False<br />

Try<br />

service.AmendOrder(addedOrder, False,<br />

ConflictResolutionStrategy.None)<br />

Catch cf As FaultException(Of ConcurrencyFault)<br />

expected = True<br />

End Try<br />

Assert.IsTrue(expected)<br />

[<strong>Visual</strong> C#]<br />

addedOrder.Comment = "Original order";<br />

bool expected = false;<br />

try<br />

{<br />

service.AmendOrder(addedOrder, false,<br />

ConflictResolutionStrategy.None);<br />

}<br />

catch (FaultException cf)<br />

{<br />

expected = true;<br />

}<br />

Assert.IsTrue(expected);<br />

11. Save the OrdersServiceImplTest file.


Task 7: Build and test the application<br />

1. Build the solution and correct any errors.<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-57<br />

2. As an administrator, stop and restart IIS.<br />

3. In the E:\Labfiles folder, run AWReset.bat to reset the AdventureWorks database to a known state.<br />

4. In <strong>Visual</strong> <strong>Studio</strong>, run all of the tests in the solution.<br />

5. Verify that all of the tests succeed.<br />

6. Start the application <strong>with</strong>out debugging.<br />

7. In the Order Management window, click the Orders By Contact tab, and then in the Username box,<br />

type Bert<br />

8. In the Password box, type Pa$$w0rd<br />

9. In the Contact ID box, type 200 and then click Get. Verify that you can still add a new order, modify<br />

an existing order, and delete an order:<br />

a. Make sure that you use a valid product ID, for example, 905 or 906.<br />

b. Make sure that you use a discount of less than 1.0, for example, 0.05.<br />

c. The New Order button creates a new order. Pressing INSERT in the TreeView control also<br />

creates a new order.<br />

d. Pressing DELETE in the TreeView control deletes an order.<br />

e. Pressing ENTER in the TreeView control edits an order. You can only change or add items to an<br />

order; you cannot delete items from an order.<br />

10. If time allows, start a second instance of the application, and attempt to make conflicting changes to<br />

the same orders in each instance. Verify that the application detects the conflicts and resolves them.<br />

Some possible suggestions include:<br />

• Changing the order quantity for the same order in both instances.<br />

• Deleting an order in one instance, and attempting to modify the order quantity in the second<br />

instance.<br />

• Deleting the same order in both instances.<br />

11. Close the application.<br />

12. Close <strong>Visual</strong> <strong>Studio</strong>.


10-58 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Review<br />

Review Questions<br />

1. Why do you move the STE entity classes to a separate project?<br />

2. In Exercise 1, how is the order subtotal calculated?<br />

3. In Exercise 1, how do you report errors to the client application?<br />

4. In Exercise 2, why is the Concurrency Mode property of the RevisionNumber field set to Fixed?<br />

5. In Exercise 2, what information can a ConcurrencyFault object contain?


Module Review and Takeaways<br />

Review Questions<br />

Handling Updates in an N-Tier Solution by Using the Entity Framework 10-59<br />

1. Why can the ObjectContext object not track changes in an n-tier solution?<br />

2. When you attach an SE to the ObjectContext object, what state is it in?<br />

3. List some of the advantages of using DTOs in an n-tier solution.<br />

4. What are the options for handling a conflict that the data access layer detects when it calls the<br />

SaveChanges method?<br />

Best Practices Related to Using Custom Entity Classes in Your Entity Framework<br />

Application<br />

Supplement or modify the following best practices for your own work situations:<br />

• Select the format for transferring data over the network that is most appropriate for your application.<br />

• Make sure that all of your entities have a suitable property for detecting conflicts.<br />

• Make sure that your application can use connection pooling.<br />

• Select the strategy for resolving conflicts that is most appropriate for your application.


10-60 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010


Module 11<br />

Building Occasionally Connected <strong>Solutions</strong><br />

Contents:<br />

Lesson 1: Offline <strong>Data</strong> Caching by Using XML 11-3<br />

Lesson 2: Using the Sync Framework 11-17<br />

Lab: Building Occasionally Connected <strong>Solutions</strong> 11-34<br />

Building Occasionally Connected <strong>Solutions</strong> 11-1


11-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

In some scenarios, you will want to enable access to your applications when users are not connected to<br />

the corporate network. There are two ways of implementing this: providing offline data for local read-only<br />

use or providing offline data for local read/write use, <strong>with</strong> changes being uploaded to the central<br />

database when the user reconnects to the corporate network.<br />

This module describes how to access offline or occasionally connected data in client applications.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Cache data in local XML files by using Language-Integrated Query (LINQ) to XML.<br />

• Implement an occasionally connected application by using the <strong>Microsoft®</strong> Sync Framework.


Lesson 1<br />

Offline <strong>Data</strong> Caching by Using XML<br />

Building Occasionally Connected <strong>Solutions</strong> 11-3<br />

XML is an easy format in which to cache data locally in an application. You can use LINQ to XML to save<br />

server data in local XML files; if the Web service that provides the data is subsequently unavailable, you<br />

can use the local cached files as your data source.<br />

This lesson explains how to test whether a Web service is available, how to use LINQ to XML to cache data<br />

locally, how to use LINQ to XML to load data from cached files, and how to encrypt XML files.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Detect the availability of a Web service.<br />

• Describe the functionality of LINQ to XML.<br />

• Describe the key LINQ to XML classes.<br />

• Persist entities to XML.<br />

• Load XML into entities.<br />

• Encrypt XML files.


11-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Detecting the Availability of a Web Service<br />

Key Points<br />

The easiest way to check the availability of a Web service is to try to access the service. If the service<br />

responds, you know that it is available; if it does not, you know that it is unavailable.<br />

You can use the Download<strong>Data</strong> method of the WebClient class to try to elicit a response from a Web<br />

service, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

' Detect the availability of a Web service.<br />

Private Sub DetectTheAvailabilityOfWebService(ByVal _string As webServiceUri)<br />

Dim response As Byte()<br />

Dim result As Boolean<br />

Dim client As New System.Net.WebClient()<br />

Try<br />

response = client.Download<strong>Data</strong>(webServiceUri)<br />

Catch ex As Exception<br />

result = False<br />

End Try<br />

Dim str As String = Encoding.UTF8.GetString(response)<br />

If str.IndexOf("xml") > -1 Then result = True<br />

If result = True Then<br />

MessageBox.Show("Web service available at " & webServiceUri)<br />

Else<br />

MessageBox.Show("Web service unavailable")<br />

End If<br />

End Sub<br />

[<strong>Visual</strong> C#]


Detect the availability of a Web service.<br />

private void DetectTheAvailabilityOfWebService(webServiceUri string)<br />

{<br />

byte[] response;<br />

bool result;<br />

System.Net.WebClient client = new System.Net.WebClient();<br />

try<br />

{<br />

response = client.Download<strong>Data</strong>(webServiceUri);<br />

}<br />

catch (Exception)<br />

{<br />

result = false;<br />

}<br />

string str = Encoding.UTF8.GetString(response);<br />

if (str.IndexOf("xml") > -1)<br />

result = true;<br />

}<br />

if (result == true)<br />

{<br />

MessageBox.Show("Web service available at " + webServiceUri);<br />

}<br />

else<br />

{<br />

MessageBox.Show("Web service unavailable");<br />

}<br />

Building Occasionally Connected <strong>Solutions</strong> 11-5<br />

Question: Why does the code example test for the string "xml" being present in the response?


11-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Introduction to LINQ to XML<br />

Key Points<br />

As you have seen in previous modules in this course, the LINQ technologies provide a consistent way to<br />

interact <strong>with</strong> databases and objects. LINQ to XML provides an in-memory XML interface that uses the<br />

familiar LINQ language to interact <strong>with</strong> XML.<br />

LINQ to XML provides a new way of programming against XML. It supports the in-memory document<br />

manipulation functionality that the Document Object Model (DOM) provides, but it also supports LINQ<br />

query expressions that make it easier for developers to move from working <strong>with</strong> databases to working<br />

<strong>with</strong> XML.<br />

LINQ to XML provides the following XML programming capabilities:<br />

• Load XML from files or streams<br />

• Serialize XML to files or streams<br />

• Create XML<br />

• Query XML<br />

• Manipulate an in-memory XML tree<br />

• Validate XML trees<br />

• Transform XML trees from one shape to another<br />

Question: Why would you choose to use LINQ to XML instead of DOM?


LINQ to XML Classes<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-7<br />

The LINQ to XML classes reside in the System.Xml.Linq namespace. The key classes that you will use<br />

include:<br />

• XElement. The XElement class is the main class in LINQ to XML programming, and it represents an<br />

XML element. You can use it on its own; you do not need to contain it in an XDocument object. This<br />

enables you to create and manipulate XML trees <strong>with</strong>out defining documents. You use the XElement<br />

class to create elements, modify the contents of elements, create, modify, and delete child elements,<br />

add attributes, and serialize elements.<br />

• XAttribute. The XAttribute class represents an XML attribute or key/value pair. When you add<br />

attributes to an element, the order of addition is preserved, and you can then access the attributes<br />

knowing their order.<br />

• XDocument. The XDocument class represents an XML document, including the XML declaration,<br />

processing instructions, comments, and an element. The XDocument class can have only one<br />

XElement node, just as an XML document can have only one root element.<br />

Question: Why can you have only one XElement node in an XDocument object?<br />

Additional Reading<br />

For more information about LINQ to XML classes, see the LINQ to XML Classes Overview page at<br />

http://go.microsoft.com/fwlink/?LinkID=194084.


11-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Persisting Entity <strong>Data</strong> to Local XML Files<br />

Key Points<br />

To persist entity data to XML, you can create a new XML element and then use LINQ to XML to convert<br />

each entity property into a child XML element, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Dim xml As XElement = Nothing<br />

xml = New XElement("Product",<br />

From p In products _<br />

Select New XElement("Product",<br />

New XElement("ProductID", p.ProductID),<br />

New XElement("Name", p.Name),<br />

New XElement("ProductNumber", p.ProductNumber),<br />

New XElement("Color", p.Color),<br />

New XElement("Cost", p.Cost), _<br />

New XElement("ListPrice", p.ListPrice),<br />

New XElement("Size", p.Size)))<br />

[<strong>Visual</strong> C#]<br />

XElement xml = null;<br />

xml = new XElement("Product",<br />

from p in products<br />

select new XElement("Product",<br />

new XElement("ProductID", p.ProductID),<br />

new XElement("Name", p.Name),<br />

new XElement("ProductNumber", p.ProductNumber),<br />

new XElement("Color", p.Color),<br />

new XElement("Cost", p.Cost),<br />

new XElement("ListPrice", p.ListPrice),<br />

new XElement("Size", p.Size)));


You can then save the element to a local disk, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

xml.Save(productFileName)<br />

[<strong>Visual</strong> C#]<br />

xml.Save(productFileName);<br />

Building Occasionally Connected <strong>Solutions</strong> 11-9<br />

The Save method of the XElement class indents the XML and removes insignificant white space by<br />

default. To alter the default behavior, you can use the overload of the Save method that includes the<br />

SaveOptions parameter <strong>with</strong> which you specify indentation and white space options.<br />

Question: How can you preserve white space when you save an XElement object?


11-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Loading <strong>Data</strong> from XML Files into Entities<br />

Key Points<br />

To load data from an XML file into entities, you create a new XML document and then use LINQ to XML<br />

to convert each element into an entity property, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Dim doc As XDocument = Nothing<br />

doc = XDocument.Load(productFileName)<br />

Dim query = From product In doc.Descendants("Product")<br />

Select New Product With<br />

[<strong>Visual</strong> C#]<br />

XDocument doc = null;<br />

{<br />

}<br />

.ProductID = Convert.ToInt32(product.Element("ProductID").Value),<br />

.Name = product.Element("Name").Value,<br />

.ProductNumber = product.Element("ProductNumber").Value,<br />

.Color = product.Element("Color").Value,<br />

.Cost = product.Element("Cost").Value,<br />

.ListPrice = product.Element("ListPrice").Value,<br />

.Size = product.Element("Size").Value<br />

doc = XDocument.Load(productFileName);<br />

var query = from product in doc.Descendants("Product")<br />

select new Product<br />

{


};<br />

ProductID = Convert.ToInt32(product.Element("ProductID").Value),<br />

Name = product.Element("Name").Value,<br />

ProductNumber = product.Element("ProductNumber").Value,<br />

Color = product.Element("Color").Value,<br />

Cost = product.Element("Cost").Value,<br />

ListPrice = product.Element("ListPrice").Value,<br />

Size = product.Element("Size").Value<br />

Building Occasionally Connected <strong>Solutions</strong> 11-11<br />

Similar to the Save method of the XElement class, the Load method of the XDocument class has an<br />

overload that takes a LoadOptions parameter, enabling you to configure indentation and white space<br />

options.<br />

Question: How can you configure indentation when you load an XDocument object?


11-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Encrypting XML Files<br />

Key Points<br />

XML documents are plain text; therefore, there is a risk involved when you save them locally on a hard<br />

disk. If the data in your files is sensitive, you should encrypt the XML before you save it to disk. You can<br />

encrypt XML data by using symmetric keys, asymmetric keys, and X.509 certificates.<br />

You can use the classes in the System.Security.Cryptography.Xml namespace to encrypt your XML<br />

documents. After the encryption process is complete, the document will contain an <br />

element that contains the encrypted XML.<br />

The following code example shows methods to encrypt and decrypt XML documents by using an<br />

asymmetric key.<br />

[<strong>Visual</strong> Basic]<br />

Imports System<br />

Imports System.Xml<br />

Imports System.Security.Cryptography<br />

Imports System.Security.Cryptography.Xml<br />

Class Utilities<br />

Private Function EncryptXML(ByVal xmlDoc__1 As XmlDocument) As XmlDocument<br />

' Create a new CspParameters object to specify<br />

' a key container.<br />

Dim cspParams As New CspParameters()<br />

cspParams.KeyContainerName = "XML_ENC_RSA_KEY"<br />

' Create a new RSA key and save it in the container.<br />

' This key will encrypt a symmetric key, which will<br />

' then be encrypted in the XML document.<br />

Dim rsaKey As New RSACryptoServiceProvider(cspParams)<br />

' Find the specified element in the XmlDocument<br />

' object and create a new XmlElement object.<br />

Dim elementToEncrypt As XmlElement =<br />

TryCast(XmlDoc.GetElementsByTagName("Product")(0), XmlElement)


' Throw an XmlException exception if the element was not found.<br />

If elementToEncrypt Is Nothing Then<br />

Throw New XmlException("Specified element not found")<br />

End If<br />

Dim sessionKey As RijndaelManaged = Nothing<br />

Try<br />

' Create a new instance of the EncryptedXml class<br />

' and use it to encrypt the XmlElement object <strong>with</strong><br />

' a new random symmetric key.<br />

' Create a 256-bit Rijndael key.<br />

sessionKey = New RijndaelManaged()<br />

sessionKey.KeySize = 256<br />

Dim eXml As New EncryptedXml()<br />

Building Occasionally Connected <strong>Solutions</strong> 11-13<br />

Dim encryptedElement As Byte() = eXml.Encrypt<strong>Data</strong>(elementToEncrypt,<br />

sessionKey, False)<br />

' Construct an Encrypted<strong>Data</strong> object and populate<br />

' it <strong>with</strong> the desired encryption information.<br />

Dim edElement As New Encrypted<strong>Data</strong>()<br />

edElement.Type = EncryptedXml.XmlEncElementUrl<br />

edElement.Id = EncryptionElementID<br />

' Create an EncryptionMethod element to indicate to the<br />

' receiver which algorithm to use for decryption.<br />

edElement.EncryptionMethod = New<br />

EncryptionMethod(EncryptedXml.XmlEncAES256Url)<br />

False)<br />

' Encrypt the session key and add it to an<br />

' EncryptedKey element.<br />

Dim ek As New EncryptedKey()<br />

Dim encryptedKey As Byte() = EncryptedXml.EncryptKey(sessionKey.Key, Alg,<br />

ek.Cipher<strong>Data</strong> = New Cipher<strong>Data</strong>(encryptedKey)<br />

ek.EncryptionMethod = New EncryptionMethod(EncryptedXml.XmlEncRSA15Url)<br />

' Set the KeyInfoName element to specify the<br />

' name of the RSA key.<br />

Dim kin As New KeyInfoName()<br />

kin.Value = KeyName<br />

' Add the KeyInfoName element to the<br />

' EncryptedKey object.<br />

ek.KeyInfo.AddClause(kin)<br />

' Add the encrypted element data to the<br />

' Encrypted<strong>Data</strong> object.<br />

edElement.Cipher<strong>Data</strong>.CipherValue = encryptedElement<br />

' Replace the element from the original XmlDocument<br />

' object <strong>with</strong> the Encrypted<strong>Data</strong> element.<br />

EncryptedXml.ReplaceElement(elementToEncrypt, edElement, False)<br />

Catch e As Exception<br />

' Re-throw the exception.<br />

Throw e<br />

Finally<br />

If sessionKey IsNot Nothing Then<br />

sessionKey.Clear()


11-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

End If<br />

End Try<br />

End Function<br />

Private Function DecryptXML(ByVal xmlDoc As XmlDocument, ByVal Alg As RSA, ByVal<br />

KeyName As String) As XmlDocument<br />

' Create a new EncryptedXml object.<br />

Dim exml As New EncryptedXml(xmlDoc)<br />

' Add a key-name mapping.<br />

' This method can only decrypt documents<br />

' that present the specified key name.<br />

exml.AddKeyNameMapping(KeyName, Alg)<br />

' Decrypt and return the element.<br />

exml.DecryptDocument()<br />

Return exml<br />

End Function<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

using System;<br />

using System.Xml;<br />

using System.Security.Cryptography;<br />

using System.Security.Cryptography.Xml;<br />

class Utilities<br />

{<br />

XmlDocument EncryptXML(XmlDocument xmlDoc)<br />

{<br />

// Create a new CspParameters object to specify<br />

// a key container.<br />

CspParameters cspParams = new CspParameters();<br />

cspParams.KeyContainerName = "XML_ENC_RSA_KEY";<br />

// Create a new RSA key and save it in the container.<br />

// This key will encrypt a symmetric key, which will<br />

// then be encrypted in the XML document.<br />

RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);<br />

// Find the specified element in the XmlDocument<br />

// object and create a new XmlElement object.<br />

XmlElement elementToEncrypt = XmlDoc.GetElementsByTagName("Product")[0] as<br />

XmlElement;<br />

// Throw an XmlException exception if the element was not found.<br />

if (elementToEncrypt == null)<br />

{<br />

throw new XmlException("Specified element not found");<br />

}<br />

RijndaelManaged sessionKey = null;<br />

try<br />

{<br />

// Create a new instance of the EncryptedXml class<br />

// and use it to encrypt the XmlElement object <strong>with</strong><br />

// a new random symmetric key.<br />

// Create a 256-bit Rijndael key.<br />

sessionKey = new RijndaelManaged();


false);<br />

sessionKey.KeySize = 256;<br />

EncryptedXml eXml = new EncryptedXml();<br />

Building Occasionally Connected <strong>Solutions</strong> 11-15<br />

byte[] encryptedElement = eXml.Encrypt<strong>Data</strong>(elementToEncrypt, sessionKey,<br />

// Construct an Encrypted<strong>Data</strong> object and populate<br />

// it <strong>with</strong> the desired encryption information.<br />

Encrypted<strong>Data</strong> edElement = new Encrypted<strong>Data</strong>();<br />

edElement.Type = EncryptedXml.XmlEncElementUrl;<br />

edElement.Id = EncryptionElementID;<br />

// Create an EncryptionMethod element to indicate to the<br />

// receiver which algorithm to use for decryption.<br />

edElement.EncryptionMethod = new<br />

EncryptionMethod(EncryptedXml.XmlEncAES256Url);<br />

// Encrypt the session key and add it to an<br />

// EncryptedKey element.<br />

EncryptedKey ek = new EncryptedKey();<br />

byte[] encryptedKey = EncryptedXml.EncryptKey(sessionKey.Key, Alg, false);<br />

ek.Cipher<strong>Data</strong> = new Cipher<strong>Data</strong>(encryptedKey);<br />

ek.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url);<br />

// Set the KeyInfoName element to specify the<br />

// name of the RSA key.<br />

KeyInfoName kin = new KeyInfoName();<br />

kin.Value = KeyName;<br />

// Add the KeyInfoName element to the<br />

// EncryptedKey object.<br />

ek.KeyInfo.AddClause(kin);<br />

// Add the encrypted element data to the<br />

// Encrypted<strong>Data</strong> object.<br />

edElement.Cipher<strong>Data</strong>.CipherValue = encryptedElement;<br />

// Replace the element from the original XmlDocument<br />

// object <strong>with</strong> the Encrypted<strong>Data</strong> element.<br />

EncryptedXml.ReplaceElement(elementToEncrypt, edElement, false);<br />

}<br />

catch (Exception e)<br />

{<br />

// Re-throw the exception.<br />

throw e;<br />

}<br />

finally<br />

{<br />

if (sessionKey != null)<br />

{<br />

sessionKey.Clear();<br />

}<br />

}<br />

}<br />

XmlDocument DecryptXML(XmlDocument xmlDoc, RSA Alg, string KeyName)<br />

{<br />

// Create a new EncryptedXml object.<br />

EncryptedXml exml = new EncryptedXml(xmlDoc);<br />

// Add a key-name mapping.<br />

// This method can only decrypt documents<br />

// that present the specified key name.<br />

exml.AddKeyNameMapping(KeyName, Alg);


11-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

}<br />

}<br />

// Decrypt and return the element.<br />

exml.DecryptDocument();<br />

return exml;<br />

Question: Why should you secure the session key that you use to encrypt the data?<br />

Additional Reading<br />

For more information about encrypting and decrypting XML data, see the XML Encryption and Digital<br />

Signatures page at http://go.microsoft.com/fwlink/?LinkID=194085.


Lesson 2<br />

Using the Sync Framework<br />

Building Occasionally Connected <strong>Solutions</strong> 11-17<br />

The Sync Framework enables you to write code for applications that must support occasionally connected<br />

clients. For example, you can use it to develop an occasionally connected application (OCA) to run on a<br />

laptop or handheld device that is not usually connected to the network. Users can work <strong>with</strong> locally held<br />

data while disconnected and then synchronize <strong>with</strong> the central server data when they are connected at a<br />

later date.<br />

This lesson explains how synchronization works, how to configure it, how to synchronize data, and how to<br />

handle conflicts. It also explains how to use the Configure <strong>Data</strong> Synchronization dialog box to simplify<br />

some of these tasks.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe classic synchronization scenarios.<br />

• Describe how synchronization works.<br />

• Compare synchronization <strong>with</strong> merge replication and remote data access (RDA).<br />

• Describe the Sync Framework architecture.<br />

• Configure synchronization.<br />

• Synchronize data.<br />

• Handle conflicts.<br />

• Use the Configure <strong>Data</strong> Synchronization dialog box.<br />

• Describe the security considerations for synchronization.


11-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Synchronization Scenarios<br />

Key Points<br />

Often an enterprise must provide mobile and remote workers <strong>with</strong> access to data. These users may use<br />

laptops, handheld devices, or office desktops to connect to their corporate data by using a virtual private<br />

network (VPN) or a Web server. This network configuration has a number of disadvantages:<br />

• Network connection. Remote users require a constant network connection to the corporate network<br />

to access their data. For users who regularly visit customer sites, this is unlikely to be available.<br />

• Network speed. When users connect to data over slow or unreliable networks, it is undesirable to<br />

require them to download all of their data every time they want to use it.<br />

• Server scalability. As more users remotely access data, the server performance may degrade to an<br />

unacceptable level, requiring additional hardware to serve the additional users.<br />

To avoid these issues, you can implement an occasionally connected application by using the type of<br />

functionality that the Sync Framework provides. In this scenario, data is stored locally on a user's<br />

computer or device for offline use. When users have corporate connectivity, they can synchronize their<br />

data <strong>with</strong> that of the server to ensure that each data source has the most up-to-date information.<br />

Question: What issues can you foresee when you provide synchronization functionality for remote users?


Understanding Synchronization<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-19<br />

During synchronization, data in one data source is used to update data in another data source. The Sync<br />

Framework provides ADO.NET classes to enable you to synchronize your data between sources. The data<br />

sources can be any database for which there is an ADO.NET data provider.<br />

Change Tracking<br />

The Sync Framework uses change tracking to minimize the volume of data that is transferred between<br />

data sources. Change tracking enables the database to track insertions, deletions, and changes made to<br />

rows. Consequently, when the two databases synchronize, each database has a list of the rows that have<br />

changed, and there is no need to transfer entire tables of data over the connection.<br />

Change tracking is often implemented by using row versions and triggers. This requires changes to the<br />

tables in every data source, and the running of the triggers can impact performance. Microsoft SQL<br />

Server® 2008 provides a new type of change tracking, SQL Server 2008 Change Tracking, in which the<br />

server tracks changes to rows <strong>with</strong>out the use of triggers. This avoids the schema changes and<br />

performance implications of traditional change tracking when you are synchronizing SQL Server<br />

databases.<br />

SQL Server 2008 also provides customizable retention thresholds for tracking data. This prevents tracking<br />

tables growing indefinitely and uses a background process to clean up old tracking data.<br />

Synchronization Modes<br />

The Sync Framework supports four modes of operation that determine the direction of data flow during<br />

the synchronization process:<br />

• Snapshot. In this mode, the client table is dropped at the beginning of the synchronization process,<br />

and then a snapshot of the server table is applied to the client.<br />

• Download only. In this mode, only rows that have been inserted, updated, or deleted at the server<br />

are downloaded to the client. Changes at the client are not passed to the server.


11-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

• Upload only. In this mode, only rows that have been inserted, updated, or deleted at the client are<br />

uploaded to the server. Changes at the server are not passed to the client.<br />

• Bidirectional. In this mode, the client downloads changes from the server and uploads changes to<br />

the server. Conflicts may occur.<br />

Conflicts<br />

Whenever multiple users update data at different locations, data conflicts may occur. When you<br />

synchronize the data sources, one change has to take precedence over the other.<br />

The Sync Framework provides you <strong>with</strong> conflict detection and resolution functionality that you can use to<br />

write conflict-resolution logic in your applications.<br />

Question: What advantages over traditional change tracking does SQL Server Change Tracking provide?<br />

Additional Reading<br />

For more information about synchronization, see the Introduction to Sync Framework <strong>Data</strong>base<br />

Synchronization page at http://go.microsoft.com/fwlink/?LinkID=194086.


Synchronization vs. Merge Replication and RDA<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-21<br />

The Sync Framework may at first appear to provide similar services to those that merge replication and<br />

RDA provide. However, there are key differences in the functionality of each of these technologies and<br />

hence the scenarios where they are best employed.<br />

Merge Replication<br />

Merge replication is intended as a tool for database administrators to synchronize SQL Server databases. It<br />

provides wizards and stored procedures that simplify the configuration and maintenance of the system.<br />

The Sync Framework is intended as a tool for developers to synchronize many types of data source,<br />

including heterogeneous databases, over services such as Windows® Communication Foundation (WCF).<br />

If your application requires synchronization of non-SQL Server databases or synchronization over different<br />

protocols, you should use the Sync Framework.<br />

RDA<br />

RDA is designed for developers to synchronize data between SQL Server Compact 3.5 databases and<br />

other editions of SQL Server.<br />

Comparisons<br />

Use the information in the following table to determine the appropriate technology for your<br />

synchronization applications.<br />

Key feature RDA<br />

Merge<br />

replication Sync Framework<br />

Synchronize by using services No No Yes<br />

Heterogeneous database support No No Yes


11-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Key feature RDA<br />

Merge<br />

replication Sync Framework<br />

Incremental change tracking Upload only Yes Yes<br />

Conflict detection and resolution No Yes Yes<br />

Automatic schema and data<br />

initialization<br />

Yes Yes Yes<br />

Large data set support Yes Yes Yes<br />

Automatic schema change<br />

propagation<br />

No Yes No<br />

Automatic data repartitioning No Yes No<br />

Device support Yes Yes Yes<br />

Question: You have an application that accesses data in a SQL Server database. Users are either based at<br />

one of three local offices <strong>with</strong> desktop computers, at home <strong>with</strong> laptops, or visiting customer sites <strong>with</strong><br />

PDAs. You want to provide a way for all users to work <strong>with</strong> local data and then send their changes to the<br />

central server whenever they have a network connection. They should also receive changes made by other<br />

users since they were last connected. Which technology should you use?<br />

Additional Reading<br />

For more information about merge replication, see the Merge Replication page at<br />

http://go.microsoft.com/fwlink/?LinkID=194087.<br />

For more information about RDA, see the Using Remote <strong>Data</strong> <strong>Access</strong> (RDA) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194088.


Sync Framework Architecture<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-23<br />

The Sync Framework consists of a set of components that work together to provide synchronization<br />

functionality to your applications.<br />

Synchronization Providers<br />

A synchronization provider communicates between a data source and other components in the<br />

synchronization system. It hides the complexities of the data store from the other synchronization<br />

components and enables you to write data store–agnostic synchronization code. Providers retrieve<br />

changes that have been made since the last synchronization session, apply incremental changes, and<br />

detect conflicts.<br />

Providers are available for a range of data source types:<br />

• <strong>Data</strong>base synchronization providers. You can use the database synchronization providers to support<br />

collaborative and offline scenarios. For SQL Server Compact databases, use the SqlCeSyncProvider<br />

provider, for other SQL Server databases, use the SqlSyncProvider provider, and for other database<br />

types, use the DbSyncProvider provider.<br />

• File synchronization provider. You can use the file synchronization provider to synchronize files and<br />

folders in NTFS, FAT, or server message block (SMB) file systems.<br />

• Web feed synchronization components. You can use the Web feed synchronization components to<br />

write providers that represent FeedSync XML files or to synchronize data of another type <strong>with</strong> an RSS<br />

or Atom feed.<br />

• Custom providers. You can use custom providers to create synchronization providers for any type of<br />

data store.<br />

Synchronization Orchestrators<br />

The synchronization orchestrator (or agent) communicates <strong>with</strong> the two synchronization providers in a<br />

session to retrieve and apply changes to the two data stores. It begins by determining the order and


11-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

direction in which changes should be applied. Next, it calls the remote synchronization provider to<br />

retrieve and apply changes at the remote data store. It then calls the local synchronization provider to<br />

retrieve and apply changes at the local data store.<br />

Synchronization Adapters<br />

A synchronization adapter is defined for each table to be synchronized. The synchronization adapter<br />

supplies the synchronization provider <strong>with</strong> the Structured Query Language (SQL) commands that it<br />

requires to work <strong>with</strong> the database. When you work <strong>with</strong> SQL Server databases, the Sync Framework<br />

automatically generates the commands for you.<br />

Question: You want to synchronize a SQL Server Express database. Which synchronization provider<br />

should you use?


Configuring Synchronization<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-25<br />

Before you can create a synchronization system, you must decide how you want to initialize the local<br />

database, which synchronization direction you want to support, and if appropriate, which change-tracking<br />

mechanism you want to use.<br />

Initializing the Local <strong>Data</strong>base<br />

When you set up synchronization, the first task is to initialize the local database. Generally, to initialize the<br />

database, the user schema and data must be copied from the server database to the client database, and<br />

change tracking must then be enabled on the database. The Sync Framework automatically performs this<br />

process the first time that a table is synchronized. Alternatively, you can manually create the client<br />

database and specify that it should be retained when synchronization happens.<br />

During the initialization process, some types of constraint are not copied, and some column types are<br />

managed differently. It is at this stage that the client database is assigned a ClientId property to uniquely<br />

identify it to the server.<br />

Synchronization Settings<br />

There are two key settings that you can configure when you synchronize data:<br />

• SyncDirection. You use the SyncDirection property of a synchronizing table to configure the<br />

direction in which data flows during the synchronization process. You can set it to Snapshot,<br />

DownloadOnly, UploadOnly, or Bidirectional.<br />

• SyncAdapter. You use the SyncAdapter properties to configure the SQL commands to use to<br />

retrieve and insert data during the synchronization session. For example, you configure the<br />

SelectIncrementalInsertsCommand property to a SQL statement that retrieves the initial and<br />

incremental inserts from the server. Similarly, you configure the InsertCommand property to a SQL<br />

statement that applies inserts made at the client to the server during upload-only and bidirectional<br />

synchronization.


11-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Change Tracking<br />

By default, changes are automatically tracked in the client database so that those changes can be<br />

synchronized <strong>with</strong> the server database. If you want to implement bidirectional or download-only<br />

synchronization, you must track changes in the server database. There are two ways to do this: create your<br />

own custom tracking mechanism by using row versions and triggers or use SQL Server 2008 Change<br />

Tracking. The first option can be complex and inhibit performance, so it is recommended that when you<br />

use SQL Server 2008, you use the inbuilt change tracking. SQL Server Change Tracking avoids the use of<br />

triggers and does not require the creation of extra database objects to track data.<br />

To configure change tracking, you first enable change tracking on the database and then on each table<br />

that requires tracking.<br />

Question: When should you use a custom change-tracking system?<br />

Additional Reading<br />

For code examples and more information about initializing the local database, see the How to: Initialize<br />

the Client <strong>Data</strong>base and Work <strong>with</strong> Table Schema page at<br />

http://go.microsoft.com/fwlink/?LinkID=194089.<br />

For more information about synchronization settings, see the How to: Specify Snapshot, Download,<br />

Upload, and Bidirectional Synchronization page at http://go.microsoft.com/fwlink/?LinkID=194090.<br />

For more information about change tracking, see the Tracking Changes in the Server <strong>Data</strong>base page at<br />

http://go.microsoft.com/fwlink/?LinkID=194091.


Synchronizing <strong>Data</strong><br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-27<br />

After you configure synchronization, you can write code to perform the synchronization session. In this<br />

topic, you will see how to perform an upload-only synchronization.<br />

Upload client changes to a server database<br />

1. Add references to the following namespaces:<br />

• Microsoft.Synchronization<br />

• Microsoft.Synchronization.<strong>Data</strong><br />

• Microsoft.Synchronization.<strong>Data</strong>.Server<br />

• Microsoft.Synchronization.<strong>Data</strong>.SqlServerCe<br />

• System.<strong>Data</strong><br />

• System.<strong>Data</strong>.SqlClient<br />

• System.<strong>Data</strong>.SqlServerCe<br />

2. Derive a class from the SqlServerSyncProvider class. Add code to the class to perform the following<br />

steps:<br />

a. In the class constructor, instantiate a SqlConnection object, passing the connection string of the<br />

database as a parameter to the constructor.<br />

b. Create a SqlCommand object to retrieve a new anchor value from the server and set the<br />

SelectNewAnchorCommand property of the class to this SqlCommand object.<br />

c. Create a SyncAdapter object, define commands for the SelectIncrementalInsertsCommand,<br />

InsertCommand, UpdateCommand, and DeleteCommand properties of the SyncAdapter<br />

object, and then add the SyncAdapter object to the server synchronization provider.


11-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

3. Derive a class from the SqlCeClientSyncProvider class. Add code to the class to perform the<br />

following step:<br />

• In the class constructor, define the ConnectionString property of the class.<br />

4. Derive a class from the SyncAgent class. Add code to the class constructor to perform the following<br />

steps:<br />

a. Set the LocalProvider property of the class to your SqlCeClientSyncProvider object.<br />

b. Set the RemoteProvider property of the class to your SqlServerSyncProvider object.<br />

c. Declare and instantiate a SyncGroup object.<br />

d. Declare and instantiate a SyncTable object for each table to be included in the synchronization.<br />

e. Set the CreationOption property of each of the SyncTable objects.<br />

f. Set the SyncDirection property of each of the SyncTable objects.<br />

g. Set the SyncGroup property of each of the SyncTable objects to group related tables into one<br />

synchronization transaction.<br />

h. Add each SyncTable object to the SyncTables collection of the SyncAgent object.<br />

5. In your application code, add code to perform the following steps:<br />

a. Declare and instantiate your SyncAgent object.<br />

b. Call the Synchronize method of your SyncAgent object.<br />

Question: What do you use a SyncGroup object for?<br />

Additional Reading<br />

For more information about how to perform the other types of synchronization, see the How to: Specify<br />

Snapshot, Download, Upload, and Bidirectional Synchronization page at<br />

http://go.microsoft.com/fwlink/?LinkId=194090.<br />

For a complete code listing of the synchronization process, see the How to: Upload Incremental <strong>Data</strong><br />

Changes to a Server page at http://go.microsoft.com/fwlink/?LinkID=194093.


Handling Conflicts<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-29<br />

<strong>Data</strong> conflicts can occur in synchronization applications where changes can be made at more than one<br />

node. Conflicts and errors are detected at the row level if a change has been made to that row at two or<br />

more nodes. Although the Sync Framework includes conflict detection, it is preferable to design your<br />

applications so that conflicts do not occur; the detection and resolution process increases network traffic<br />

and processing requirements.<br />

If the conflict occurs from an upload of data, it is detected by the server synchronization provider<br />

(typically the SqlServerSyncProvider provider). If the conflict occurs from a download of data, it is<br />

detected by the client synchronization provider (typically the SqlCeClientSyncProvider provider). When a<br />

conflict occurs, the synchronization provider raises the ApplyChangeFailed event, and you can determine<br />

the outcome of the conflict by providing a handler for this event.<br />

The handler is passed an ApplyChangeFailedEventArgs parameter that has two roles. First, it gives you<br />

information about the conflict that has occurred. Second, it enables you to determine the result of the<br />

conflict.<br />

The Conflict member is a SyncConflict object that gives details about the conflict. The SyncConflict class<br />

has a member named ConflictType that indicates what type of conflict has occurred, as summarized in<br />

the following table.<br />

ConflictType value Description<br />

ClientInsertServerInsert The client and server both insert a row that has the same primary key.<br />

ClientUpdateServerUpdate The client and server change the same row.<br />

ClientUpdateServerDelete The client updates a row and the server deletes the same row.<br />

ClientDeleteServerUpdate The client deletes a row and the server updates the same row.


11-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

ConflictType value Description<br />

ErrorsOccurred An error prevents a row from being applied. The Error property of the<br />

ApplyChangeFailedEventArgs parameter gives more details.<br />

In addition, the Conflict property has two properties named ClientChange and ServerChange, which are<br />

<strong>Data</strong>Table objects, that indicate which rows from the client and server that conflicted.<br />

You can use all of this information to decide the outcome of the conflict. You inform the Sync Framework<br />

of your decision by setting the Action property of the ApplyChangeFailedEventArgs parameter. The<br />

Action property is an ActionApply property <strong>with</strong> the values that are described in the following table.<br />

ActionApply value Description<br />

Continue This is the default behavior. The change is not performed.<br />

RetryApplyingRow Another attempt is made to change the row.<br />

RetryWithForceWrite Another attempt is made to change the row, regardless of the values that<br />

will be changed.<br />

The ApplyChangeFailedEventArgs parameter has two properties named Connection and Transaction so<br />

you can access the database and, if necessary, change the data, which is useful for the<br />

RetryApplyingRow setting. If you use the RetryWithForceWrite setting, you indicate that you want the<br />

change to occur regardless of the values that will be changed. Forced changes on the client are handled<br />

by the client synchronization provider. Forced changes on the server require logic in the code that applies<br />

changes to the server; in this case, the @sync_force_write session variable is set so that the SQL statement<br />

that applies the change can use this variable to force the write.<br />

Question: Where do you catch conflicts and write code to resolve them?


Using the Configure <strong>Data</strong> Synchronization Dialog Box<br />

Key Points<br />

Building Occasionally Connected <strong>Solutions</strong> 11-31<br />

In addition to hand coding data synchronization, you can use features in Microsoft <strong>Visual</strong> <strong>Studio</strong>® to<br />

configure synchronization. If you add a local database cache item to a project to store data locally, it<br />

automatically includes a .sync file that will include the configuration of the synchronization. To configure<br />

the file, you use the Configure <strong>Data</strong> Synchronization dialog box that opens when you add the local<br />

database cache item to the project.<br />

By default, the Configure <strong>Data</strong> Synchronization dialog box creates a download-only synchronization.<br />

However, you can modify the code after it is generated to support upload-only or bidirectional<br />

synchronization.<br />

Configure data synchronization<br />

1. Define a server connection to a SQL Server database.<br />

2. Define a client connection to a SQL Server Compact 3.5 database. If you want to create a new local<br />

database, leave the default setting for this value.<br />

3. Add tables to the synchronization group.<br />

4. Click Show Code Example to copy the code example to initiate synchronization.<br />

5. If you do not want to use download-only synchronization and plan to modify the generated code to<br />

support upload-only synchronization, clear the Use SQL Server change tracking check box. Also<br />

clear this check box if you do not currently have access to the server.<br />

6. Click OK.<br />

After you configure synchronization by using the dialog box, you must write code in your application to<br />

perform the synchronization. If you copied the code when you configured the synchronization, you can<br />

simply paste this in the appropriate place in your application code. The following code example shows the<br />

synchronization code that the dialog box generates.<br />

[<strong>Visual</strong> Basic]<br />

' Call SyncAgent.Synchronize() to initiate the synchronization process.


11-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

' Synchronization only updates the local database, not your project's data source.<br />

Dim syncAgent As New Local<strong>Data</strong>CacheSyncAgent()<br />

Dim syncStats As Microsoft.Synchronization.<strong>Data</strong>.SyncStatistics = syncAgent.Synchronize()<br />

' TODO: Reload your project data source from the local database (for example, call the<br />

TableAdapter.Fill method).<br />

[<strong>Visual</strong> C#]<br />

// Call SyncAgent.Synchronize() to initiate the synchronization process.<br />

// Synchronization only updates the local database, not your project's data source.<br />

Local<strong>Data</strong>CacheSyncAgent syncAgent = new Local<strong>Data</strong>CacheSyncAgent();<br />

Microsoft.Synchronization.<strong>Data</strong>.SyncStatistics syncStats = syncAgent.Synchronize();<br />

// TODO: Reload your project data source from the local database (for example, call the<br />

TableAdapter.Fill method).<br />

The code that is added to the application is in partial classes; therefore, if you want to change the<br />

synchronization type, you can create a partial class for the SyncAgent implementation and change the<br />

SyncDirection properties of the tables in the OnInitialized method.<br />

Question: You use the Configure <strong>Data</strong> Synchronization dialog box to add and configure a local<br />

database cache in your project, but no synchronization occurs. What else must you do?


Security Considerations for Synchronization<br />

Key Points<br />

Consider the following security recommendations when you configure synchronization:<br />

• Use the principle of least permission on synchronizing tables.<br />

Building Occasionally Connected <strong>Solutions</strong> 11-33<br />

• Configure server applications and server databases to expose minimal surface area to attack. For<br />

example, if you use Internet Information Services (IIS) as part of an n-tier application, do not enable<br />

the FTP service unless another application explicitly requires it.<br />

• Encrypt or password-protect data on disk and in transit.<br />

• Use stored procedures instead of SQL statements.<br />

• Validate data before you save it.<br />

Question: How can you encrypt data in transit?


11-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab: Building Occasionally Connected <strong>Solutions</strong><br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Use LINQ to XML to cache entity data in XML files.<br />

• Use the Sync Framework to synchronize a SQL Server Compact 3.5 database <strong>with</strong> a SQL Server Express<br />

database.<br />

Introduction<br />

In this lab, you will use LINQ to XML to cache data in local XML files. You will also use the Sync Framework<br />

to synchronize local data <strong>with</strong> server data.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-11 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


Lab Scenario<br />

Building Occasionally Connected <strong>Solutions</strong> 11-35<br />

You have been asked to extend the Orders application to support salespeople who need to be able to<br />

query and update the SalesOrderHeader and SalesOrderDetails tables when they work in the field.<br />

You decide to evaluate two different technologies for building this application. One technology caches<br />

data locally in XML files when it is retrieved from the existing data access tier and then uses LINQ to XML<br />

to access this data if the data access tier is inaccessible. The other technology caches data locally in a SQL<br />

Server Compact database and then uses the Sync Framework to refresh the data cache and merge<br />

changes when the application is online.


11-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exercise 1: Modifying the Orders Application to Use Offline XML <strong>Data</strong><br />

Scenario<br />

In this exercise, you will modify an existing application that connects to the existing data access tier Web<br />

service. You will modify the logic in the application to cache data locally in encrypted XML files when it is<br />

retrieved. If the Web service is not available, the application will use LINQ to XML to query the local XML<br />

data instead. In this mode, the data is read-only.<br />

The main tasks for this exercise are as follows:<br />

1. Prepare the environment for the lab.<br />

2. Open the starter project for this exercise.<br />

3. Write code to construct XML file names.<br />

4. Write code to determine whether the Web service is available.<br />

5. Write code to cache the contacts.<br />

6. Write code to cache the orders.<br />

7. Build and test the application.<br />

Task 1: Prepare the environment for the lab<br />

1. Log on to the 10265A-GEN-DEV-11 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures IIS and creates the<br />

required users and groups.<br />

3. In the E:\Labfiles folder, run AWReset.bat.<br />

4. In the E:\Labfiles\Lab11\VB\Ex1\Starter folder (if you are using Microsoft <strong>Visual</strong> Basic®), or<br />

E:\Labfiles\Lab11\CS\Ex1\Starter folder (if you are using Microsoft <strong>Visual</strong> C#®), run ExSetup.bat as an<br />

administrator. This script adds the required virtual directories to IIS.<br />

5. Open IIS Manager as an administrator.<br />

6. Add a new self-signed certificate named OrdersWebService.<br />

7. Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.<br />

8. Configure the OrdersWebService application to require Secure Sockets Layer (SSL) security.<br />

9. Close IIS Manager.<br />

Task 2: Open the starter project for this exercise<br />

1. Run <strong>Visual</strong> <strong>Studio</strong> 2010 as an administrator.<br />

2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab11\VB\Ex1\Starter\OrdersDAL (if you<br />

are using <strong>Visual</strong> Basic) or E:\Labfiles\Lab11\CS\Ex1\Starter\OrdersDAL (if you are using <strong>Visual</strong> C#)<br />

folder.<br />

Task 3: Write code to construct XML file names<br />

1. In <strong>Visual</strong> <strong>Studio</strong>, review the task list.<br />

2. Open the code file behind the OrderManagementWindow.xaml window by double-clicking the<br />

TODO: Add the System.Xml.Linq namespace task in the task list.<br />

3. Immediately after the comment, add a statement that brings the System.Xml.Linq namespace into<br />

scope.<br />

4. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Define paths and filenames for XML files task in the task list.<br />

5. Immediately after the comment, add code that defines three private string variables named filePath,<br />

contactsFile, and ordersFile.


Building Occasionally Connected <strong>Solutions</strong> 11-37<br />

6. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Set the file paths task in the task list. This task is located in the<br />

OrderManagementWindow constructor.<br />

7. Immediately after the comment, add code that sets the filePath variable to the path of the user's My<br />

Documents folder and sets the contactsFile variable to this path concatenated <strong>with</strong> the file name<br />

contacts.xml.<br />

8. Immediately after the next comment, add code that sets the ordersFile variable to the My Documents<br />

path concatenated <strong>with</strong> the word orders. The remainder of the file name will be constructed at run<br />

time.<br />

Task 4: Write code to determine whether the Web service is available<br />

1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Check that the Web service is available. Return True if it is, false<br />

otherwise task in the task list.<br />

2. Immediately after the comment, add a private method called CheckWebServiceExists that takes an<br />

OrdersWebServiceClient object as a parameter and returns a Boolean value. This method should try<br />

to access the Orders Web service and return true or false to indicate whether a response was<br />

obtained.<br />

Task 5: Write code to cache the contacts<br />

1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the first TODO: Check whether the Web service is still operational task in the task<br />

list. . This task is located in the getContacts_Click method.<br />

2. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service<br />

does not exist, call the getContactsFromLocalCache method and return from the method.<br />

3. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Cache the contacts task in the task list.<br />

4. Immediately after the comment, add a call to the SaveContactsToLocalCache method, passing the<br />

contacts object.<br />

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Get contact information from the local cache file task in the task list.<br />

6. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Instantiate a new instance of the list of contacts and then update the message in the<br />

statusMessage status bar item <strong>with</strong> the text "Fetching contacts …"<br />

b. If the contacts XML file exists, call the LoadContactsFromLocalCache method, display the data<br />

in the contactsGrid grid, display the number of contacts retrieved in the numContactRows<br />

label, and then update the message in the statusMessage status bar item <strong>with</strong> the text "Ready".<br />

c. If there are no cached contacts, update the message in the statusMessage status bar item <strong>with</strong><br />

the text "No cached data available".<br />

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Save contact information to the local cache file task in the task list.<br />

8. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Construct an XElement object that contains the data in the contacts object that is passed to the<br />

method.<br />

b. Save the XElement object to the file specified by the contactsFile variable.


11-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Helper method. Read contact information from the local cache file<br />

task in the task list.<br />

10. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Load the contents of the contacts file into an XDocument object.<br />

b. Convert the XML into a list of Contact objects and return this list.<br />

Task 6: Write code to cache the orders<br />

1. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the second TODO: Check whether the Web service is still operational task in the<br />

task list. This task is located in the getOrders_Click method.<br />

2. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service<br />

does not exist, call the getOrdersFromLocalCache method and return from the method.<br />

3. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the first TODO: Cache the orders task in the task list. This task is located in the<br />

getOrders_Click method.<br />

4. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the<br />

orders object and the name of the cache file as parameters. Construct the name of the cache file by<br />

concatenating the text "General.xml" to the end of the value in the ordersFile string variable.<br />

5. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the third TODO: Check whether the Web service is still operational task in the<br />

task list. This task is located in the getOrdersForContact_Click method.<br />

6. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service<br />

does not exist, call the getOrdersForContactFromLocalCache method and return from the method.<br />

7. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the second TODO: Cache the orders task in the task list. This task is located in the<br />

getOrdersForContact_Click method.<br />

8. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the<br />

orders object and the name of the cache file as parameters. Construct the name of the cache file by<br />

concatenating the text "contactN.xml" to the end of the value in the ordersFile string variable, where<br />

N is the contactID of the contact.<br />

9. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the fourth TODO: Check whether the Web service is still operational task in the<br />

task list. This task is located in the getOrdersForProduct_Click method.<br />

10. Immediately after the comment, add a call to the CheckWebServiceExists method. If the service<br />

does not exist, call the getOrdersForProductFromLocalCache method and return from the method.<br />

11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the third TODO: Cache the orders task in the task list. This task is located in the<br />

getOrdersForProduct_Click method.<br />

12. Immediately after the comment, add a call to the SaveOrdersToLocalCache method, passing the<br />

orders object and the name of the cache file as parameters. Construct the name of the cache file by<br />

concatenating the text "productN.xml" to the end of the value in the ordersFile string variable, where<br />

N is the productID of the product.<br />

13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Get general order information from a local cache file task in the task<br />

list.<br />

14. Delete the existing code in this method, and then add code that performs the following tasks:


Building Occasionally Connected <strong>Solutions</strong> 11-39<br />

a. Create a new empty list of orders and update the statusMessage status bar item <strong>with</strong> the text<br />

"Fetching orders …".<br />

b. If the general orders XML file exists, call the LoadOrdersFromLocalCache method to populate<br />

the list of orders, call the displayOrders method to display the data in the ordersTree TreeView<br />

control in the window, display the number of orders in the numOrderRows label, and then<br />

update the statusMessage status bar item <strong>with</strong> the text "Ready".<br />

Note: The general orders XML file has the name "xxxxGeneral.xml" where the value of the xxxx prefix is<br />

specified by the ordersFile variable.<br />

c. If there are no cached orders, update the statusMessage status bar item <strong>with</strong> the text "No<br />

cached data available" and clear the ordersTree TreeView control.<br />

15. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Get order information for a specified contact from a local cache file<br />

task in the task list.<br />

16. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Create a new empty list of orders and update the statusMessage status bar item <strong>with</strong> the text<br />

"Fetching orders …".<br />

b. If the XML file containing orders for the specified contact exists, call the<br />

LoadOrdersForContactFromLocalCache method to populate the list of orders, call the<br />

displayOrders method to display the data in the ordersForContractTree TreeView control in<br />

the window, display the number of orders in the numOrderForContactRows label, and then<br />

update the statusMessage status bar item <strong>with</strong> the text "Ready".<br />

Note: The orders XML file has the name "contactN.xml" located in the folder specified by the ordersFile<br />

variable where N is the contact ID.<br />

c. If there are no cached orders, update the statusMessage status bar item <strong>with</strong> the text "No<br />

cached data available" and clear the ordersForContractTree TreeView control.<br />

17. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Get order information for a specified product from a local cache file<br />

task in the task list.<br />

18. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Create a new empty list of orders and update the statusMessage status bar item <strong>with</strong> the text<br />

"Fetching orders …".<br />

b. If the XML file containing orders for the specified contact exists, call the<br />

LoadOrdersForProductFromLocalCache method to populate the list of orders, call the<br />

displayOrders method to display the data in the ordersForProductTree TreeView control in<br />

the window, display the number of orders in the numOrderForProductRows label, and then<br />

update the statusMessage status bar item <strong>with</strong> the text "Ready".<br />

Note: The orders XML file has the name "productN.xml" located in the folder specified by the ordersFile<br />

variable where N is the product ID.<br />

c. If there are no cached orders, update the statusMessage status bar item <strong>with</strong> the text "No<br />

cached data available" and clear the ordersForProductTree TreeView control.


11-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

19. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Save order information to the specified cache file task in the task list.<br />

20. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Construct an XElement object that contains the data in the orders object that is passed to the<br />

method.<br />

b. Save the XElement object to the file specified by the fileName variable.<br />

21. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Helper method to load general order information from a local cache<br />

file task in the task list.<br />

22. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Load the contents of the orders file into an XDocument object. The name of the orders file is<br />

held in the fileName variable.<br />

b. Iterate through the XML content in the XDocument object and convert it into a list of Order<br />

objects, and then return this list.<br />

23. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Helper method to load order information for a contact from a local<br />

cache file task in the task list.<br />

24. Delete the existing code in this method, and then add code that performs the following tasks:<br />

a. Load the contents of the orders file into an XDocument object. The name of the orders file is<br />

held in the fileName variable.<br />

b. Iterate through the XML content in the XDocument object and convert it into a list of Order<br />

objects, and then return this list.<br />

Note: Use the getOrderDetailsFromCache method to retrieve the order details for each order from the<br />

local cache.<br />

25. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Helper method to load order information for a product from a local<br />

cache file task in the task list.<br />

26. Delete the code in this method, and then add code that performs the following tasks:<br />

a. Load the contents of the contacts file into an XDocument object. The name of the orders file is<br />

held in the fileName variable.<br />

b. Iterate through the XML content in the XDocument object and convert it into a list of Order<br />

objects, and then return this list.<br />

Note: Use the getOrderDetailsForProductFromCache method to retrieve the order details for each<br />

order from the local cache.<br />

27. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by<br />

double-clicking the TODO: Helper method to retrieve order details for an order task in the task<br />

list.<br />

28. Delete the existing code in this method, and uncomment the code at end of the method definition so<br />

that the method receives an enumerable list of XElement objects containing order information as a<br />

parameter.<br />

29. Add code to the method that performs the following tasks:


Building Occasionally Connected <strong>Solutions</strong> 11-41<br />

a. Instantiate a trackable collection of SalesOrderDetail objects.<br />

b. Extract the details of each order from the list of XElement objects and store them in the list of<br />

SalesOrderDetail objects.<br />

c. Add each SalesOrderDetail object to the trackable collection and then return this collection.<br />

30. Locate the next comment in the code file behind the OrderManagementWindow.xaml window file by<br />

double-clicking the TODO: Helper method to retrieve order details for an order for a specified<br />

product task in the task list.<br />

31. Delete the existing code in this method, and uncomment the code at end of the method definition so<br />

that the method receives an enumerable list of XElement objects containing order information and<br />

the productID value as parameters.<br />

32. Add code to the method that performs the following tasks:<br />

a. Instantiate a trackable collection of SalesOrderDetail objects.<br />

b. Extract the order details from the list of XElement objects and store them in the list of<br />

SalesOrderDetail objects.<br />

c. Add each SalesOrderDetail object to the trackable collection and then return it.<br />

Task 7: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application.<br />

3. In the Username box, type Fred and in the Password box, type Pa$$w0rd<br />

4. Retrieve contacts from 1 to 412.<br />

5. Open Windows Explorer, move to the C:\Users\Admin\My Documents folder, and then verify that a<br />

new XML file named contacts.xml has been created.<br />

6. Open the contacts.xml file in Windows Internet Explorer® to verify that the contact data that is<br />

displayed in the application has been written to the file.<br />

7. In the Order Management application, in the Username box, type Bert and in the Password box,<br />

type Pa$$w0rd<br />

8. On the General Orders tab, retrieve orders from 1 to 43784.<br />

9. In Windows Explorer, verify that a new XML file named ordersGeneral.xml has been created.<br />

10. Open the ordersGeneral.xml file in Internet Explorer to verify that the contact data that is displayed in<br />

the application has been written to the file.<br />

11. On the Orders By Contact tab, retrieve orders for contact 1.<br />

12. In Windows Explorer, verify that a new XML file named orderscontact1.xml has been created.<br />

13. On the Orders By Product tab, retrieve orders for product 776.<br />

14. In Windows Explorer, verify that a new XML file named ordersproduct776.xml has been created.<br />

15. Close Windows Explorer, and then close the Order Management application.<br />

16. Open IIS Manager, and then stop the Orders Web service.<br />

17. In <strong>Visual</strong> <strong>Studio</strong>, start the Order Management application.<br />

18. In the Order Management application, in the Username box, type Fred and in the Password box,<br />

type Pa$$w0rd<br />

19. Retrieve contacts from 1 to 412, and then verify that you can access the cached data.


11-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

20. In the Order Management application, in the Username box, type Bert and in the Password box,<br />

type Pa$$w0rd<br />

Note: It is not actually necessary to specify the credentials of a user when retrieving information from the<br />

local cache; these credentials are only required by the Web service.<br />

21. On the General Orders tab, retrieve orders from 1 to 43784, and then verify that you can access the<br />

cached data.<br />

22. On the Orders By Contact tab, retrieve orders for contact 1, and then verify that you can access the<br />

cached data.<br />

23. On the Orders By Product tab, retrieve orders for product 776, and then verify that you can access<br />

the cached data.<br />

24. On the Orders By Product tab, retrieve orders for product 777, and then verify that there is no<br />

cached data available.<br />

25. Close the application.<br />

26. In IIS Manager, start the Orders Web service.<br />

27. In <strong>Visual</strong> <strong>Studio</strong>, run all of the tests in the solution.<br />

28. Verify that all of the tests succeed.<br />

29. Save and close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


Building Occasionally Connected <strong>Solutions</strong> 11-43<br />

Exercise 2: Modifying the Orders Application to Synchronize Locally<br />

Cached <strong>Data</strong><br />

Scenario<br />

In this exercise, you will use the Sync Framework to implement an OCA. <strong>Data</strong> will be cached locally on the<br />

client computer by using a SQL Server Compact database. You will modify the Orders application to use<br />

this local database and synchronize <strong>with</strong> the data access tier when a connection is available.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project for this exercise.<br />

2. Add local data caching to the Orders.<br />

3. Configure the synchronization.<br />

4. Add synchronization code to the OrdersService project.<br />

5. Add synchronization code to the user interface.<br />

6. Build and test the application.<br />

Task 1: Open the starter project for this exercise<br />

1. Open <strong>Visual</strong> <strong>Studio</strong> 2010.<br />

2. Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab11\VB\Ex2\Starter\OrdersDAL (if you<br />

are using <strong>Visual</strong> Basic) or E:\Labfiles\Lab11\CS\Ex2\Starter\OrdersDAL (if you are using <strong>Visual</strong> C#)<br />

folder.<br />

Task 2: Add local data caching to the Orders application<br />

1. Add a new local database cache named AWCache to the OrdersDAL project.<br />

2. Configure the server connection to connect to the AdventureWorks database on the local computer.<br />

3. Configure the client connection to connect to the AdventureWorksLocal.sdf SQL Server Compact<br />

3.5 database in the E:\Labfiles\Lab11\VB\Ex2\Starter (if you are using <strong>Visual</strong> Basic) or<br />

E:\Labfiles\Lab11\CS\Ex2\Starter (if you are using <strong>Visual</strong> C#) folder.<br />

4. Add the Contact (Person), SalesOrderDetail (Sales), and SalesOrderHeader (Sales) tables to the<br />

synchronized database.<br />

5. Configure synchronization to not use SQL Server Change Tracking, and then initiate the first-time<br />

synchronization.<br />

6. Rebuild the solution.<br />

Task 3: Configure synchronization<br />

1. Add a new class named AWCacheSyncAgent to the OrdersDAL project.<br />

2. Modify the AWCacheSyncAgent class to be a public partial class.<br />

3. Add a private partial method called OnInitialized to the class. This method should take no<br />

parameters and not return a value. In this method, add code that sets the SyncDirection property of<br />

each synchronized table to be Bidirectional. You can access the synchronized tables by using the<br />

_person_ContactSyncTable, _sales_SalesOrderDetailSyncTable, and<br />

_sales_SalesOrderHeaderSyncTable fields in the AWCacheSyncAgent class.<br />

Note: If you are using <strong>Visual</strong> Basic, do not declare the OnInitialized method as Partial.


11-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 4: Add synchronization code to the OrdersService project<br />

1. Add a reference to the Microsoft.Synchronization.<strong>Data</strong> version 2.0.0.0 assembly to the<br />

OrdersService project.<br />

2. Review the task list.<br />

3. Open the code file for the IOrdersService interface by double-clicking the first TODO: Synchronize<br />

the AdventureWorksLocal SQL Server CE database <strong>with</strong> the AdventureWorks database in SQL<br />

Server Express task in the task list. This task is located in the IOrdersService code file.<br />

4. Below the summary and returns comments, declare a method named SyncWithServer that takes no<br />

parameters and returns a Boolean value.<br />

5. Open the code file for the OrdersServiceImpl class by double-clicking the TODO: Namespace<br />

containing types required by Synchronization Services task in the task list.<br />

6. After the comment, add a statement that brings the Microsoft.Synchronization.<strong>Data</strong> namespace<br />

into scope.<br />

7. Locate the next comment by double-clicking the second TODO: Synchronize the<br />

AdventureWorksLocal SQL Server CE database <strong>with</strong> the AdventureWorks database in SQL<br />

Server Express task in the task list.<br />

8. Below the summary and returns comments, implement the SyncWithServer method. In this method,<br />

use the AWCacheSyncAgent object to synchronize the local SQL Server Compact data <strong>with</strong> SQL<br />

Server Express.<br />

Task 5: Add synchronization code to the user interface<br />

1. Locate the Sync<strong>Data</strong>base_Click method in the code file behind the OrderManagementWindow.xaml<br />

window by double-clicking the TODO: Add code here to synchronize data between SQL Server<br />

Express and AdventureWorksLocal.sdf task in the task list.<br />

2. Add code to this method to call the SyncWithServer method and display the success of the call to<br />

the user.<br />

Task 6: Build and test the application<br />

1. Build the solution and correct any errors.<br />

2. Start the application <strong>with</strong>out debugging.<br />

3. Synchronize the data <strong>with</strong> SQL Server Express.<br />

4. Retrieve the orders for the contact <strong>with</strong> a Contact ID value of 1.<br />

5. Display the edit window for the details of the second order that this contact made.<br />

6. Change the quantity of the order to 4 and the discount to 0.5.<br />

7. Synchronize the data <strong>with</strong> SQL Server Express.<br />

8. In <strong>Visual</strong> <strong>Studio</strong>, using Server Explorer, examine the data in the AdventureWorks SQL Server Express<br />

database and verify that the changes were synchronized.<br />

9. In Server Explorer, locate the details for order 44132, change the OrderQty value to 99, and then<br />

press ENTER.<br />

10. Return to the Order Management application and synchronize the data <strong>with</strong> SQL Server Express<br />

again.<br />

11. Requery the orders for contact 1 and verify that order 44132 has been updated.<br />

12. Close the Order Management application.<br />

13. Save and close the solution, and then close <strong>Visual</strong> <strong>Studio</strong>.


Lab Review<br />

Review Questions<br />

Building Occasionally Connected <strong>Solutions</strong> 11-45<br />

1. Which class do you use to create attributes?<br />

2. What direction of synchronization does the Configure <strong>Data</strong> Synchronization dialog box create?


11-46 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. What key advantage over DOM does LINQ to XML provide?<br />

2. Why would you choose to use synchronization instead of merge replication?<br />

Best Practices Related to Caching <strong>Data</strong> by Using LINQ to XML<br />

Supplement or modify the following best practices for your own work situations:<br />

• Unless you require functionality from the XDocument class, use the XElement class for simpler<br />

coding.<br />

• If you save sensitive data in XML files, encrypt the files to ensure greater security.<br />

Best Practices Related to Synchronization<br />

Supplement or modify the following best practices for your own work situations:<br />

• If you implement download-only or bidirectional synchronization by using SQL Server 2008<br />

databases, use SQL Server 2008 Change Tracking for better performance.<br />

• Configure server applications to expose the minimal surface area for attack.<br />

• Encrypt or password-protect data while it is being synchronized.<br />

• Validate data before you save it.


Module 12<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services<br />

Contents:<br />

Lesson 1: Introducing WCF <strong>Data</strong> Services 12-3<br />

Lesson 2: Creating a WCF <strong>Data</strong> Service 12-13<br />

Lesson 3: Consuming a WCF <strong>Data</strong> Service 12-41<br />

Lesson 4: Protecting <strong>Data</strong> and Operations in a WCF <strong>Data</strong> Service 12-66<br />

Lab: Creating and Using WCF <strong>Data</strong> Services 12-77<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-1


12-2 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Overview<br />

Windows® Communication Foundation (WCF) <strong>Data</strong> Services enables you to create highly flexible data<br />

services that you can use to provide access to data across the Internet or a corporate network. You can<br />

access these services by using Representational State Transfer (REST)-style URIs, and a wide variety of<br />

applications can easily consume them. WCF <strong>Data</strong> Services is built on top of standard Internet protocols<br />

such as HTTP and the Atom Publishing Protocol, so it is an ideal choice for delivering data to AJAX<br />

applications and rich interactive applications (RIAs) that were built by using technologies such as<br />

<strong>Microsoft®</strong> Silverlight®.<br />

Objectives<br />

After completing this module, you will be able to:<br />

• Describe the purpose and features of WCF <strong>Data</strong> Services.<br />

• Expose data by using a WCF <strong>Data</strong> Service.<br />

• Implement a client application that can consume a WCF <strong>Data</strong> Service.<br />

• Grant and restrict access to resources that a WCF <strong>Data</strong> Service exposes.


Lesson 1<br />

Introducing WCF <strong>Data</strong> Services<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-3<br />

WCF <strong>Data</strong> Services follows the REST architectural model and uses open Web standards such as the Open<br />

<strong>Data</strong> Protocol (O<strong>Data</strong>). By following these standards, you can build solutions based on WCF <strong>Data</strong> Services<br />

that a wide variety of client applications can easily access, regardless of the technology that is used to<br />

implement them.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Describe the REST model for Web services.<br />

• Describe the role of O<strong>Data</strong>.<br />

• Describe how WCF <strong>Data</strong> Services expose data by using the REST model.<br />

• Describe options for hosting a WCF <strong>Data</strong> Service.


12-4 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Introduction to the REST Model<br />

Key Points<br />

The REST model uses a navigational scheme to represent business objects and resources over a network.<br />

In a REST-based architecture, clients initiate a request to the server for a given resource and the server<br />

processes the request and returns the appropriate response.<br />

REST services are designed to be accessed over HTTP, and require clients to use URLs or HTTP headers to<br />

access data and command operations. Typically, clients interact <strong>with</strong> REST services by using HTTP verbs<br />

such as GET, POST, PUT, and DELETE, which they can use to query, create, modify, and delete resources.<br />

For example, an organization might provide access to customer information, exposing the details of each<br />

customer as a single resource, by using a scheme similar to the following.<br />

http://adventureworks.com/customers/14<br />

<strong>Access</strong>ing this URL causes the Web service to retrieve the data for Customer 14. This data can be returned<br />

in several simple formats such as JavaScript Object Notation (JSON) or "plain old XML" (POX), or even<br />

syndication formats such as Atom. If the Adventure Works organization chooses to use POX, the result<br />

that is returned by querying the URL shown above might look something like the following code example.<br />

<br />

<br />

14<br />

<br />

<br />

Yang<br />

<br />

<br />

John<br />

<br />

<br />

Mr<br />


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-5<br />

The key to designing a REST-based solution is to understand how to divide a business model into a set of<br />

resources. In some cases, such as customers, this might be straightforward, but in other situations, it can<br />

be more of a challenge.<br />

Unlike services that have been developed by using WCF, which use SOAP, messages that are sent and<br />

received by using the REST model are much more compact. This is primarily because REST does not<br />

provide the same routing, policy, or security facilities that the WS-* specifications provide, and you have<br />

to rely on the underlying infrastructure that the Web server provides to protect REST Web services.<br />

However, this minimalist approach means that a REST Web service is usually much more efficient than the<br />

equivalent SOAP Web service when transmitting and receiving messages.<br />

Question: How does the REST model of Web services differ from the SOAP model?


12-6 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The Role of the Open <strong>Data</strong> Protocol<br />

Key Points<br />

Within an organization, the ability to share and reuse data is often limited by the application that creates<br />

or manages it. Sometimes, the data is organized and structured in such a way that it is difficult to utilize it,<br />

or even gain access to it, from applications other than those that were originally intended to be used <strong>with</strong><br />

it.<br />

The need to reuse information across application boundaries increases as applications become more<br />

interconnected, and this information becomes more valuable to an organization the more it can be<br />

shared and accessed by other systems. Unfortunately, the use of closed data formats often limits this<br />

access.<br />

The O<strong>Data</strong> protocol is an open Web protocol for querying and updating data that removes many of the<br />

obstacles that prevent interoperability between services publishing data and the applications that<br />

consume it. It is very useful for building interoperable REST Web services. O<strong>Data</strong> makes it possible to<br />

expose data from a wide variety of data sources that can then be consumed programmatically by any<br />

client that is using a simple HTTP/AtomPub protocol as a means of communication.<br />

O<strong>Data</strong> provides a URL syntax in addition to defining common query expressions that can make it easier to<br />

access and query data.<br />

WCF <strong>Data</strong> Services fully supports the O<strong>Data</strong> protocol. You can build a WCF <strong>Data</strong> Service to expose an<br />

entity data model as a set of resources by using the semantics and formats that the O<strong>Data</strong> protocol<br />

specifies. A WCF <strong>Data</strong> Service can utilize the relationships between entities in an Entity <strong>Data</strong> Model (EDM),<br />

and publish these as navigational links between resources.<br />

Apart from WCF, several Microsoft tools and services support the O<strong>Data</strong> protocol, including Microsoft<br />

SharePoint® Server 2010, Microsoft Excel® 2010 (through Microsoft SQL Server® PowerPivot for Excel),<br />

Windows Azure Storage, and SQL Server 2008 R2. Microsoft provides client libraries for building<br />

Microsoft .NET Framework applications, Silverlight, and AJAX, and client libraries are also available for PHP<br />

and Java.


Question: What is the purpose of the O<strong>Data</strong> protocol?<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-7


12-8 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Exposing <strong>Data</strong> by Using a WCF <strong>Data</strong> Service<br />

Key Points<br />

By using WCF <strong>Data</strong> Services, you can create highly flexible data services that are tightly integrated <strong>with</strong><br />

the Web and that client applications can easily consume. WCF <strong>Data</strong> Services use URIs to address data and<br />

simple, well-known formats to represent that data, such as XML and Atom. This results in data being<br />

served as a REST-style resource collection that is easy to interact <strong>with</strong> by using the standard HTTP verbs.<br />

By using WCF <strong>Data</strong> Services, you can expose data from relational data sources such as SQL Server through<br />

an EDM conceptual schema that is created by using the ADO.NET Entity Framework, and enable a client<br />

application to query and maintain data by using this schema.<br />

Note: WCF <strong>Data</strong> Services can also expose nonrelational data by using technologies, but this<br />

requires building customized classes. WCF <strong>Data</strong> Services operate most naturally <strong>with</strong> the model<br />

that the ADO.NET Entity Framework presents.<br />

Defining a WCF <strong>Data</strong> Service<br />

A WCF <strong>Data</strong> Service is based on the System.<strong>Data</strong>.Services.<strong>Data</strong>Service generic class. This class expects a<br />

type parameter that is a collection that contains at least one property that implements the IQueryable<br />

interface, such as the ObjectContext class for an entity set that is defined by using the Entity Framework.<br />

The <strong>Data</strong>Service type implements the basic functionality to expose the entities in this collection as a<br />

series of REST resources. The following code example shows the definition of a WCF <strong>Data</strong> Service based<br />

on an ObjectContext class called AdventureWorks that is generated by using the ADO.NET Entity<br />

Framework.<br />

[<strong>Visual</strong> Basic]<br />

Public Class AdventureWorks<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorks)


...<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class AdventureWorks<strong>Data</strong>Service : <strong>Data</strong>Service<br />

{<br />

...<br />

}<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-9<br />

You can implement methods in a WCF <strong>Data</strong> Service that specify the entities to expose from the underlying<br />

entity model, and that configure the size of datasets that the data service presents. You can also override<br />

methods that are inherited from the <strong>Data</strong>Service class to customize the way in which the service<br />

operates.<br />

By default, WCF <strong>Data</strong> Services use a simple addressing scheme that exposes the entity sets that are<br />

defined <strong>with</strong>in the specified entity data model. When you consume a WCF <strong>Data</strong> Service, you address these<br />

entity resources as an entity set that contains instances of an entity type. The URI in the following code<br />

example returns all of the Reward entities that were defined in the entity model that was used to<br />

construct a WCF <strong>Data</strong> Service.<br />

http://adventureworks.com/Sales.svc/Rewards<br />

The "/Rewards" element of the URI points to the Rewards entity set, which is the container for Reward<br />

instances.<br />

Question: What is the purpose of the <strong>Data</strong>Service class?


12-10 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Hosting a WCF <strong>Data</strong> Service<br />

Key Points<br />

You host a WCF <strong>Data</strong> Service <strong>with</strong>in an environment such as ASP.NET, which provides the required<br />

networking functionality, or by using a custom WCF host application.<br />

Hosting a WCF <strong>Data</strong> Service by Using an ASP.NET Web Application<br />

If you use the WCF <strong>Data</strong> Services template to add a data service to an ASP.NET Web application or Web<br />

site, the template automatically configures the Web application or Web site <strong>with</strong> a WCF endpoint that a<br />

client application can use to connect to the service.<br />

You can also add an endpoint for a WCF <strong>Data</strong> Service to an ASP.NET Web application as follows:<br />

1. Add a service (.svc) file to the Web application.<br />

2. In the markup of the .svc file, set the value of the Factory attribute to<br />

System.<strong>Data</strong>.Services.<strong>Data</strong>ServiceHostFactory. This type constructs instances of the specified<br />

<strong>Data</strong>Service type by using the information that is provided in the configuration file.<br />

3. Set the value of the Service attribute to the fully qualified class name of the WCF <strong>Data</strong> Service.<br />

The resultant ServiceHost declaration should resemble the following code example.<br />

[<strong>Visual</strong> Basic]<br />

WCF<strong>Data</strong>ServiceHost.svc<br />

<br />

[<strong>Visual</strong> C#]<br />

WCF<strong>Data</strong>ServiceHost.svc<br />


Service="AdventureWorks<strong>Data</strong>Service" %><br />

Hosting a WCF <strong>Data</strong> Service by Using the WebServiceHost Class<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-11<br />

To host a WCF <strong>Data</strong> Service in a Windows service or application, you can use the WebServiceHost class.<br />

The WebServiceHost class is a specialized implementation of the ServiceHost class that is used for<br />

hosting services by using HTTP and HTTPS. The WebServiceHost class can create an instance of a service<br />

object, configure the endpoints of the service by using the specified bindings and create listener objects<br />

for each endpoint address.<br />

When you create a WebServiceHost object, you specify the type of the class that implements the service<br />

in addition to the base address that the service should listen to for requests, as the following code<br />

example shows.<br />

[<strong>Visual</strong> Basic]<br />

...<br />

Dim baseAddress As New Uri("http://localhost:8000/dataService")<br />

Dim host As New WebServiceHost(GetType(AdventureWorks<strong>Data</strong>Service), baseAddress)<br />

[<strong>Visual</strong> C#]<br />

...<br />

Uri baseAddress = new Uri("http://localhost:8000/dataService");<br />

WebServiceHost host = new<br />

WebServiceHost(typeof(AdventureWorks<strong>Data</strong>Service),baseAddress);<br />

If you do not specify an endpoint for a service, the WebServiceHost class automatically creates a default<br />

endpoint at the service's base address for HTTP and HTTPS base addresses. In this case, the<br />

WebServiceHost class automatically performs the following tasks:<br />

• It configures the endpoint's binding to work <strong>with</strong> the associated Internet Information Services (IIS)<br />

security settings when it is used in a secure virtual directory.<br />

• It disables the HTTP help page and the WSDL GET functionality for the metadata endpoint.<br />

• It adds the WebHttpBehavior behavior to all endpoints that do not already have the behavior and<br />

that have a WebMessageEncodingElement configuration element.<br />

• If all of the operations on the service have either empty HTTP request bodies or deal <strong>with</strong> the HTTP<br />

request body as a stream, the WebServiceHost class automatically configures the appropriate<br />

content type mapper for the binding.<br />

If you specify an endpoint manually, you must bind the WebServiceHost object to a WebHttpBinding<br />

binding, which you can create programmatically by calling the AddServiceEndpoint method as shown in<br />

the following code example.<br />

[<strong>Visual</strong> Basic]<br />

...<br />

Dim binding As New WebHttpBinding()<br />

host.AddServiceEndpoint(GetType(System.<strong>Data</strong>.Services.IRequestHandler),<br />

binding, "WebServiceHost")<br />

[<strong>Visual</strong> C#]<br />

...<br />

WebHttpBinding binding = new WebHttpBinding();


12-12 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

host.AddServiceEndpoint(typeof(System.<strong>Data</strong>.Services.IRequestHandler),<br />

binding, "WebServiceHost");<br />

Note: The WebHttpBinding binding is required by services that send and receive requests that contain<br />

POX data, rather than SOAP messages.<br />

After you have created a WebServiceHost object and defined an endpoint, the service can start listening<br />

for requests by calling the Open method as shown in the following code example.<br />

[<strong>Visual</strong> Basic]<br />

...<br />

host.Open()<br />

[<strong>Visual</strong> C#]<br />

...<br />

host.Open();<br />

You stop a service by calling the Close method of the WebServiceHost class. This method stops the WCF<br />

runtime listening for any more requests and gracefully shuts down the service.<br />

The following table describes the events that the WebServiceHost class provides. You can use these<br />

events to track the state of the WebServiceHost object.<br />

Event Description<br />

Closed Occurs when the WebServiceHost object has shut down.<br />

Closing Occurs when the WebServiceHost object is executing the<br />

Close method.<br />

Faulted Occurs when the WebServiceHost object encounters an<br />

unrecoverable error.<br />

Opened Occurs when the WebServiceHost object has successfully<br />

opened and is accepting requests.<br />

Opening Occurs when the WebServiceHost object is opening.<br />

UnknownMessageReceived Occurs when the WebServiceHost object receives an<br />

unknown message.<br />

Question: What is the difference between the WebServiceHost class that is used to host a WCF <strong>Data</strong><br />

Service and the ServiceHost class that many WCF services use?<br />

Additional Reading<br />

For more information about how to host a REST Web service in a custom application, see the<br />

WebServiceHost Class page at http://go.microsoft.com/fwlink/?LinkID=194094.


Lesson 2<br />

Creating a WCF <strong>Data</strong> Service<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-13<br />

You can add a WCF <strong>Data</strong> Service to a WCF service and to a Web application. Microsoft <strong>Visual</strong> <strong>Studio</strong>®<br />

provides the WCF <strong>Data</strong> Service template that you can use to build a WCF <strong>Data</strong> Service. This template<br />

generates a very basic data service class based on the <strong>Data</strong>Service type and adds configuration<br />

information to the host configuration file. The idea is that you use this class and configuration as a<br />

starting point and add the necessary functionality to expose your data.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Implement a WCF <strong>Data</strong> Service that exposes data from an entity model.<br />

• <strong>Access</strong> the data that is exposed through a WCF <strong>Data</strong> Service from a Web browser.<br />

• Filter, select, and navigate through data by using query expressions.<br />

• Page through data that a WCF <strong>Data</strong> Service has exposed.<br />

• Configure a WCF <strong>Data</strong> Service by using the <strong>Data</strong>ServiceConfiguration object.<br />

• Handle exceptions in a WCF <strong>Data</strong> Service.<br />

• Implement an operation in a WCF <strong>Data</strong> Service.<br />

• Deploy a WCF <strong>Data</strong> Service.


12-14 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Building a WCF <strong>Data</strong> Service for an Entity <strong>Data</strong> Model<br />

Key Points<br />

To build a WCF <strong>Data</strong> Service that can expose relational data that is stored in a SQL Server, or other thirdparty,<br />

database, you should start by using the ADO.NET Entity Framework to define the conceptual model<br />

and generate the appropriate entity classes, as described earlier in this course.<br />

You implement a WCF <strong>Data</strong> Service in an ASP.NET Web application, an ASP.NET Web site, or a WCF<br />

service. Use the appropriate <strong>Visual</strong> <strong>Studio</strong> template for the type of solution that you want to build. The<br />

examples shown in this lesson are based on an ASP.NET Web site.<br />

After you have created an ASP.NET Web site, you can use the WCF <strong>Data</strong> Service template to generate a<br />

basic data service and add it to your application, as described in the following procedure.<br />

Add a WCF <strong>Data</strong> Service to an ASP.NET Web site<br />

1. In Solution Explorer, right-click the Web site project, and then click Add New Item.<br />

2. In the Add New Item dialog box, in the Installed Templates pane, select WCF <strong>Data</strong> Service.<br />

3. In the Name box, enter a name for the WCF <strong>Data</strong> Service, and then click Add.<br />

A new WCF <strong>Data</strong> Service is added to your project and has an .svc extension.<br />

The WCF <strong>Data</strong> Service template generates the starter code that is needed to develop the service. The<br />

following code example shows the code that is provided by <strong>Visual</strong> <strong>Studio</strong> 2010 for a WCF <strong>Data</strong> Service<br />

called Product<strong>Data</strong>Service.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

' TODO: replace [[class name]] <strong>with</strong> your data class name<br />

Inherits <strong>Data</strong>Service(Of [[class name]])<br />

' This method is called only once to initialize service-wide policies.<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-15<br />

' TODO: set rules to indicate which entity sets and service operations are<br />

visible, updatable, etc.<br />

' Examples:<br />

' config.SetEntitySet<strong>Access</strong>Rule("MyEntityset", EntitySetRights.AllRead)<br />

' config.SetServiceOperation<strong>Access</strong>Rule("MyServiceOperation",<br />

ServiceOperationRights.All)<br />

config.<strong>Data</strong>ServiceBehavior.MaxProtocolVersion = <strong>Data</strong>ServiceProtocolVersion.V2<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service< /* TODO: put your data source class name<br />

here */ ><br />

{<br />

// This method is called only once to initialize service-wide policies.<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

// TODO: set rules to indicate which entity sets and service operations are<br />

visible, updatable, etc.<br />

// Examples:<br />

// config.SetEntitySet<strong>Access</strong>Rule("MyEntityset", EntitySetRights.AllRead);<br />

// config.SetServiceOperation<strong>Access</strong>Rule("MyServiceOperation",<br />

ServiceOperationRights.All);<br />

config.<strong>Data</strong>ServiceBehavior.MaxProtocolVersion = <strong>Data</strong>ServiceProtocolVersion.V2;<br />

}<br />

}<br />

Notice that the WCF <strong>Data</strong> Service inherits from the generic <strong>Data</strong>Service class. To enable the WCF<br />

<strong>Data</strong> Service to publish the data in an ADO.NET Entity Framework data model, specify the type of the<br />

ObjectContext object that is created for the entity model as the type parameter to the <strong>Data</strong>Service class.<br />

In the following code example, the AdventureWorksEntities type is an ObjectContext object that is<br />

generated for an entity model.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

...<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

...<br />

}<br />

Enabling <strong>Access</strong> to Resources in the <strong>Data</strong> Service<br />

For security reasons, WCF <strong>Data</strong> Services do not automatically expose any resources, such as entity<br />

collections that the entity model implements. You specify a policy that enables or disables access to<br />

resources in the InitializeService method of your data service. This method takes a<br />

<strong>Data</strong>ServiceConfiguration object, which you can use to define the access policy. The<br />

<strong>Data</strong>ServiceConfiguration class provides the SetEntitySet<strong>Access</strong>Rule method. The following code<br />

example shows an example of using this method.


12-16 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

config.SetEntitySet<strong>Access</strong>Rule("*", EntitySetRights.All)<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

config.SetEntitySet<strong>Access</strong>Rule("*", EntitySetRights.All);<br />

}<br />

}<br />

The parameters to the SetEntitySet<strong>Access</strong>Rule method are the name of a resource, and the access rights<br />

to grant over that resource. You can specify a resource explicitly, or you can use wildcard characters. The<br />

"*" value that is shown in the code example is a shorthand way of specifying all resources that the WCF<br />

<strong>Data</strong> Service publishes. The EntitySetRights.All value grants unrestricted access to these resources.<br />

Important: The previous code example uses the * wildcard character and the EntitySetRights.All value<br />

to set access rights on all of the entity collections that are available in the entity model. This is bad<br />

practice, and is shown for illustrative purposes. You should set access rights for resources on an individual<br />

basis. This is described in more detail later in this module.<br />

Question: What constraint is placed on the type parameter for the <strong>Data</strong>Service class?


<strong>Access</strong>ing a WCF <strong>Data</strong> Service by Using a Web Browser<br />

Key Points<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-17<br />

You can quickly access a WCF <strong>Data</strong> Service by using a Web browser. You specify the URL of a resource<br />

that the service exposes, and the browser issues the appropriate HTTP GET request to the service. The<br />

WCF <strong>Data</strong> Service processes the request and returns the data for the requested resource in AtomPub feed<br />

format.<br />

The URI format that WCF <strong>Data</strong> Services uses is based on the underlying entity model. You can access<br />

entity sets and individual entities in addition to traversing the relationships between entities.<br />

In the following code example, the Product<strong>Data</strong>Service WCF <strong>Data</strong> Service provides access to product<br />

information in the AdventureWorks database. The Product<strong>Data</strong>Service WCF <strong>Data</strong> Service is hosted by a<br />

Web site <strong>with</strong> the URL of http://MyHost/AdventureWorksSite. Using this service, you can retrieve a list of<br />

all of the entities that the service exposes by using a URI such as the following.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc<br />

The data is returned in the format that is shown in the following code example.<br />

<br />

<br />

<br />

Default<br />

<br />

Products<br />

<br />

<br />

ProductDocuments<br />

<br />


12-18 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

ProductListPriceHistories<br />

<br />

<br />

ProductModels<br />

<br />

<br />

ProductReviews<br />

<br />

<br />

<br />

In this example, the Product<strong>Data</strong>Service WCF <strong>Data</strong> Service publishes data from entity sets called<br />

Products, ProductDocuments, ProductListPriceHistories, and ProductReviews.<br />

You can list all of the data in an entity set by specifying the entity name. The following example fetches all<br />

of the Products from the Product<strong>Data</strong>Service WCF <strong>Data</strong> Service.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products<br />

Important: WCF <strong>Data</strong> Services is case-sensitive. If you specify data by using the wrong case, WCF <strong>Data</strong><br />

Services returns an HTTP 400 error.<br />

To return a single entity from an entity set, you supply a filter predicate. This filter predicate represents<br />

the primary key in single-key entities. For example, to return the product that has a ProductID of 321,<br />

you use the following URI.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products(321)<br />

The data that is returned contains all of the information for this product. This information includes the<br />

data type and value held in each field of the product, as the following code example shows.<br />

<br />

<br />

http://localhost:49224/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products(321)<br />

<br />

2010-02-19T16:20:17Z<br />

<br />

<br />

<br />

<br />

<br />

<br />


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-19<br />

ries" type="application/atom+xml;type=feed" title="ProductListPriceHistories"<br />

href="Products(321)/ProductListPriceHistories" /><br />

<br />

<br />

<br />

<br />

321<br />

Chainring Nut<br />

CN-6137<br />

false<br />

false<br />

Silver<br />

1000<br />

750<br />

0.0000<br />

0.0000<br />

<br />

<br />

<br />

<br />

0<br />

<br />

<br />

<br />

<br />

<br />

1998-06-01T00:00:00<br />

<br />

<br />

3314b1d7-ef69-4431-b6dd-dc75268bd5df<br />

2004-03-11T10:01:36.827<br />

<br />

<br />

<br />

Note: By default, Windows Internet Explorer® interprets the data that is returned by a query such as this<br />

as an AtomPub feed, and displays the data in AtomPub format. You can display the data in the format<br />

shown above by turning off the feed-reading view in the Internet Options dialog box in Internet<br />

Explorer.<br />

You can fetch the data for an individual field in an entity by specifying the name of the field at the end of<br />

the URI. For example, to fetch the name of product 321, use the following URI.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products(321)/Name<br />

The data that is returned looks like the following code example.<br />

<br />

Chainring Nut


12-20 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Navigating Related <strong>Data</strong><br />

Notice that, in the earlier code example, the data that is returned for product 321 includes links to related<br />

entities. You can use these links to fetch data from those entities. For example, to fetch all of the reviews<br />

for product 321, you can use the following URI.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products(321)/ProductReviews<br />

This query references another entity set and returns a list of all matching entities from this set. You can<br />

query individual product reviews, and fields <strong>with</strong>in products reviews, by specifying the ID of the review<br />

and appending the name of the field to the end of the URI, following the pattern shown in the previous<br />

examples.<br />

Question: Based on the Product<strong>Data</strong>Service example, what information does the following URI identify?<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/ProductModels(1)/Products


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-21<br />

Filtering, Selecting, and Ordering <strong>Data</strong> by Using Query Expressions<br />

Key Points<br />

When you query a resource, all of the data for that resource is retrieved by default. However, by using<br />

WCF <strong>Data</strong> Services, you can use HTTP query strings to limit and sort data. WCF <strong>Data</strong> Services supports a<br />

range of operators and expressions that you can combine to refine the data that a query returns in<br />

addition to sorting data.<br />

The following table describes the main query options that are available.<br />

Query option Description<br />

$filter Filters the returned entity set by using query operators and functions.<br />

$select Restricts the data that the URI returns to those fields that the query specifies.<br />

$orderby Orders results by using the specified query operators.<br />

$expand Returns a set of related entities together <strong>with</strong> the addressed entity in a single<br />

response.<br />

You construct query expressions by combining query options, query operators, and query functions. For<br />

example, the following URI returns all Product entities where the Color property is equal to Red.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc /Products?$filter=Color eq 'Red'<br />

The $filter option enables you to combine references to columns and literals to construct expressions by<br />

using query operators. The following table lists some of the common logical and arithmetic operators that<br />

are available.


12-22 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Query operator Description<br />

eq Returns true if both operands have the same value.<br />

ne Returns false if both operands have the same value.<br />

lt Returns true if the first operand is less than the second.<br />

gt Returns true if the first operand is greater than the second.<br />

le Returns true if the first operand is less than or equal to the second.<br />

ge Returns true if the first operand is greater than or equal to the second.<br />

and Combines expressions together. Returns true if both expressions are true.<br />

or Combines expressions together. Returns true if either expression is true.<br />

not Negates an expression.<br />

add Returns the sum of the operands (if they are numeric), or concatenates the<br />

operands together (if they are strings).<br />

sub Returns the value resulting from subtracting the second operand from the first. This<br />

operator is only defined for numeric operands.<br />

mul Returns the product of two operands. This operator is only defined for numeric<br />

operands.<br />

div Returns the result of dividing the first operand by the second. This operator is only<br />

defined for numeric operands.<br />

Mod Returns the integer remainder after dividing the first operand by the second. This<br />

operator is only defined for numeric operands.<br />

Selecting <strong>Data</strong><br />

The $filter option enables you to specify which entities you want to include in the results that are<br />

returned by referencing a URI. You can use the $select option to limit the fields or properties that are<br />

returned for each entity. The following example shows how to fetch the ProductID, Name, and<br />

ProductNumber fields for all Product entities.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc<br />

/Products?$select=ProductID,Name,ProductNumber<br />

Sorting <strong>Data</strong><br />

You can specify the order in which data is presented by using the $orderby option. You indicate the sort<br />

keys as a comma-delimited list of field or property names. <strong>Data</strong> is presented in ascending order of the<br />

specified sort keys, although you can specify the desc sort key to retrieve data in descending order. The<br />

following example fetches the ProductID, Name, and ProductNumber fields for all Product entities in<br />

descending order of the Name field.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc<br />

/Products?$select=ProductID,Name,ProductNumber&orderby=Name desc<br />

Note: You can combine query options by using the & character to join elements of an HTTP query.


Fetching Related <strong>Data</strong><br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-23<br />

You can fetch data that is related to an entity by using the $expand option. You specify a commaseparated<br />

list of related entities, and WCF <strong>Data</strong> Services navigates the relationships between these entities<br />

to retrieve the data that is associated <strong>with</strong> the entity that is specified as the primary target of the query.<br />

The following example fetches the data for all Product entities, and for each Product entity, it also<br />

retrieves the related ProductReview and ProductDocument entities.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc<br />

/Products?$expand=ProductReviews,ProductDocuments<br />

The names that are specified in the $expand option should be the names of the corresponding<br />

navigation properties that are used to link the entities together in the underlying entity model. Note that,<br />

if you attempt to directly fetch data from an entity that is not directly related to the primary entity that is<br />

specified by a query, the WCF <strong>Data</strong> Service generates an HTTP 400 error. However, you can fetch data<br />

indirectly through a related entity set. For example, in the Adventure Works database, the Product table<br />

is not directly related to the ProductModelIllustration table; instead, Product is related to another table<br />

called ProductModel, which is in turn related to ProductModelIllustration. If you include these tables in<br />

the entity model for the WCF <strong>Data</strong> Service, you can use the following query to fetch information from the<br />

Products entity set and retrieve all related data from the ProductModelIllustrations entity set through<br />

the ProductModel entity set.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products?$expand=ProductModel/Pr<br />

oductModelIllustrations<br />

Question: You use the following query to attempt to retrieve the data for all products that have a<br />

ProductID greater than 100, but the query fails <strong>with</strong> an HTTP 400 error. Why?<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc /Products?$select ProductID gt<br />

100<br />

Additional Reading<br />

For more information about addressing resources in a WCF <strong>Data</strong> Service, see the Addressing Resources<br />

(WCF <strong>Data</strong> Services) page at http://go.microsoft.com/fwlink/?LinkID=194095.


12-24 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Paging Through <strong>Data</strong> That a WCF <strong>Data</strong> Service Exposes<br />

Key Points<br />

By default, when you specify a query, WCF <strong>Data</strong> Services returns all resources that satisfy that query. If you<br />

have a large set of resources, performing an unfiltered query could potentially return a large amount of<br />

data and consume considerable bandwidth. In most cases, rather than submitting a query that fetches all<br />

of the data at once, you should instead use paging to fetch the data in manageable chunks. This strategy<br />

also avoids wasting bandwidth if the user is only really interested in the first few rows.<br />

Implementing Paging in a Query<br />

WCF <strong>Data</strong> Services provides the $top and $skip options to implement paging when you query data. The<br />

$top option fetches the first n items of data that satisfy the query, where n is a value that the user<br />

specifies. The following example retrieves the first five Product items from the Product<strong>Data</strong>Service<br />

service.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc /Products?$top=5<br />

The $skip option enables you to specify that a query should omit the first n results. You can use $skip<br />

and $top together to fetch data in chunks, or pages (page is just a term that means a chunk of data). The<br />

following example retrieves the next five Product items from the Product<strong>Data</strong>Service service, starting at<br />

item 6.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products?$skip=5&$top=5<br />

Note that, if you include options such as $filter and $orderby, they are evaluated first before the $top<br />

and $skip options are applied.<br />

Limiting Page Sizes in a WCF <strong>Data</strong> Service<br />

A WCF <strong>Data</strong> Service can limit the size of pages that a query returns by using the<br />

<strong>Data</strong>ServiceConfiguration object that configures the service. The <strong>Data</strong>ServiceConfiguration class


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-25<br />

provides the SetEntitySetPageSize method, which you can use to specify the maximum page size when<br />

the user queries an entity. The best place to call this method is in the InitializeService method that runs<br />

when the service is first instantiated.<br />

The SetEntitySetPageSize method takes two parameters: the name of an entity set, and the maximum<br />

page size for that entity set. The following code example limits the page size for the Products entity set<br />

to 10 items.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.SetEntitySetPageSize("Products", 10)<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.SetEntitySetPageSize("Products", 10);<br />

}<br />

}<br />

You can specify the "*"wildcard character for the name of the entity set, in which case the maximum page<br />

size is set for all entities that the WCF <strong>Data</strong> Service exposes. You can specify the value 0 for the second<br />

parameter to remove the page-size limitation.<br />

Any limits that you specify in this way override the maximum page size that the user may specify when<br />

fetching data. For example, if you limit the page size of the Products entity set to 10 items, if the user<br />

attempts to fetch the top 20 items, only the first 10 will be retrieved. However, if the user attempts to list<br />

the top five items, only the first five items will be fetched.<br />

Important: Paging requires that a service implements the V2 O<strong>Data</strong> protocol. You can specify the version<br />

of the O<strong>Data</strong> protocol that a WCF <strong>Data</strong> Service implements by setting the MaxProtocolVersion property<br />

of the <strong>Data</strong>ServiceBehavior property of the <strong>Data</strong>ServiceConfiguration object to the value<br />

<strong>Data</strong>ServiceProtocolVersion.V2, as the following code example shows.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.<strong>Data</strong>ServiceBehavior.MaxProtocolVersion = _<br />

<strong>Data</strong>ServiceProtocolVersion.V2<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]


12-26 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.<strong>Data</strong>ServiceBehavior.MaxProtocolVersion =<br />

<strong>Data</strong>ServiceProtocolVersion.V2;<br />

}<br />

}<br />

Question: You are using paging to fetch data in chunks. The WCF <strong>Data</strong> Service limits the maximum page<br />

size to 20 items for all entities. How can you fetch items 45 to 54 inclusive from a particular entity set?


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-27<br />

Configuring a WCF <strong>Data</strong> Service by Using a <strong>Data</strong>ServiceConfiguration<br />

Object<br />

Key Points<br />

When a WCF <strong>Data</strong> Service starts running, it creates a <strong>Data</strong>ServiceConfiguration object and passes it to<br />

the InitializeService static method of the data service class. In the InitializeService method, you have<br />

seen that you can call methods on this <strong>Data</strong>ServiceConfiguration object to modify the behavior of a<br />

WCF <strong>Data</strong> Service, specify which entity sets are visible through the service, indicate the access rights that<br />

users have over those entities, and set the maximum page size for retrieving data. The<br />

<strong>Data</strong>ServiceConfiguration class also provides properties that you can use to limit the operations that a<br />

user can perform by using your service.<br />

Limiting Use of the $expand Option<br />

When you query data by using the $expand option, the WCF <strong>Data</strong> Service fetches data that is related to<br />

the primary entity that you specify in query. This could potentially retrieve a large amount of information.<br />

You can set the MaxExpandCount property of the <strong>Data</strong>ServiceConfiguration object to limit the<br />

number of related entity types that a query returns that specifies the $expand option. If the user specifies<br />

a query that exceeds this limit, the query fails <strong>with</strong> an HTTP 400 error.<br />

Note: The MaxExpandCount property limits the number of related entity types, not the number of<br />

related entities. For example, if you set MaxExpandCount to 1, you can perform queries that fetch data<br />

from a single entity set that is related to the primary entity. There might be any number of related entities<br />

in this entity set. However, if you try to fetch related data from more than one related entity set, you will<br />

receive an HTTP 400 error.<br />

When you use the $expand option to fetch data that is related to the primary entity, remember that you<br />

can also fetch data that is indirectly related to the primary entity by specifying an appropriate path, as<br />

shown in the following example.


12-28 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products?$expand=ProductModel/Pr<br />

oductModelIllustrations<br />

You can limit the amount of indirection that a WCF <strong>Data</strong> Service supports by using the MaxExpandDepth<br />

property of the <strong>Data</strong>ServiceConfiguration object. In this example, the depth of the query is 2. If you<br />

want to restrict queries to fetching directly related data only, you can set the MaxExpandDepth property<br />

to 1.<br />

The following code example shows how to set the MaxExpandCount and MaxExpandDepth properties.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

End Sub<br />

End Class<br />

...<br />

config.MaxExpandCount = 1<br />

config.MaxExpandDepth = 1<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.MaxExpandCount = 1;<br />

config.MaxExpandDepth = 1;<br />

}<br />

}<br />

Note: Setting MaxExpandCount or MaxExpandDepth to zero effectively disables the $expand option<br />

for a WCF <strong>Data</strong> Service.<br />

Limiting Counts and Projections<br />

The <strong>Data</strong>ServiceConfiguration class contains another property called MaxResultsPerCollection that<br />

you can use to restrict the number of items that are returned from each entity set in a query.<br />

The <strong>Data</strong>ServiceBehavior property of the <strong>Data</strong>ServiceConfiguration class enables you to define some<br />

additional behaviors. In particular, the AcceptProjectionRequests property is a Boolean value that<br />

indicates whether queries can include the $select option to limit the fields that a query returns. In<br />

addition, the AcceptCountRequests property is another Boolean value that indicates whether queries can<br />

include the $count and $inlinecount options that return the number of matching entities. For example,<br />

the following query returns a single-integer value that indicates the number of Product entities in the<br />

Product<strong>Data</strong>Service service.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/Products/$count


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-29<br />

Counting data can temporarily lock resources. You can disable this functionality as shown in the following<br />

code example.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.<strong>Data</strong>ServiceBehavior.AcceptCountRequests = False<br />

End Sub<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

}<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

}<br />

...<br />

config.<strong>Data</strong>ServiceBehavior.AcceptCountRequests = false;<br />

Question: You want to prevent users from using the $expand option to fetch data that is related to an<br />

entity, so you set the MaxExpandDepth property of the <strong>Data</strong>ServiceConfiguration object for the<br />

service to zero. However, all queries—not just queries that use the $expand option—now fail <strong>with</strong> an<br />

HTTP 400 error. Why is this, and what value should you have set the MaxExpandDepth property to?


12-30 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Handling Exceptions in a WCF <strong>Data</strong> Service<br />

Key Points<br />

By default, when an exception occurs in a WCF <strong>Data</strong> Service when processing a request, the exception is<br />

passed to the HandleException method in the <strong>Data</strong>Service class (if the exception has not already been<br />

caught and handled elsewhere in your service). You can customize the way in which exceptions are<br />

handled by overriding this method in your data service. The HandleExceptionArgs object that is passed<br />

as a parameter to this method has properties that provide information about the cause of the exception.<br />

The two most useful properties are Exception, which contains an exception object that provides the<br />

details of the exception, and ResponseStatusCode, which contains the HTTP status code that will be<br />

returned to the client application that is submitting the request to your service. A common technique is to<br />

record the details of the exception to the application event log, as shown in the following code example.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

...<br />

Protected Overloads Overrides Sub HandleException(ByVal args As HandleExceptionArgs)<br />

logException(args.Exception, args.ResponseStatusCode.ToString())<br />

MyBase.HandleException(args)<br />

End Sub<br />

Private Sub logException(ByVal ex As Exception, ByVal responseCode As String)<br />

If Not EventLog.SourceExists(eventSource) Then<br />

EventLog.CreateEventSource(eventSource, eventLog)<br />

End If<br />

Dim eventMessage As String = String.Format("{0}: {1}: {2}", responseCode,<br />

ex.Message, System.Threading.Thread.CurrentPrincipal.Identity.Name)<br />

EventLog.WriteEntry(eventSource, eventMessage, EventLogEntryType.[Error])<br />

End Sub


Private Const eventSource As String = "Product<strong>Data</strong>Service"<br />

Private Const eventLog As String = "Application"<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service : <strong>Data</strong>Service<br />

{<br />

...<br />

protected override void HandleException(HandleExceptionArgs args)<br />

{<br />

logException(args.Exception,<br />

args.ResponseStatusCode.ToString());<br />

base.HandleException(args);<br />

}<br />

}<br />

private void logException(Exception ex, string responseCode)<br />

{<br />

if (!EventLog.SourceExists(eventSource))<br />

{<br />

EventLog.CreateEventSource(eventSource, eventLog);<br />

}<br />

string eventMessage = string.Format("{0}: {1}: {2}",<br />

responseCode, ex.Message,<br />

System.Threading.Thread.CurrentPrincipal.Identity.Name);<br />

EventLog.WriteEntry(eventSource, eventMessage,<br />

EventLogEntryType.Error);<br />

}<br />

private const string eventSource = "Product<strong>Data</strong>Service";<br />

private const string eventLog = "Application";<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-31<br />

When you build a client application, exceptions are passed across the network as serialized<br />

<strong>Data</strong>ServiceException objects. The HandleException method handles all exceptions, not just<br />

<strong>Data</strong>ServiceException exceptions. Therefore, in the HandleException method, if you want to pass<br />

information back to the client application, you must ensure that all exceptions are converted into<br />

<strong>Data</strong>ServiceException objects so that the runtime can serialize them and handle them correctly. The<br />

Exception property of the args HandleExceptionArgs parameter is writable. If you detect an exception<br />

other than a <strong>Data</strong>ServiceException exception in the HandleException method, you can construct a new<br />

<strong>Data</strong>ServiceException object that wraps the exception, assign it to the Exception property of the args<br />

object, and then pass the args object as the parameter to the HandleException method of the base<br />

<strong>Data</strong>Service class to propagate the exception to the client application.<br />

Note: If you create your own <strong>Data</strong>ServiceException object, ensure that you also specify a valid and<br />

appropriate HTTP status code. HTTP status codes range from 100 to 599, and each code has a specific<br />

meaning. For example, do not create a <strong>Data</strong>ServiceException object <strong>with</strong> HTTP status codes in the 200–<br />

299 range because these are intended to indicate successful operations. Similarly, status codes in the 100–<br />

199 range indicate informational messages that the Web server infrastructure uses to respond to protocol<br />

requests. Status codes in the 300–399 range indicate redirection messages that require a client application<br />

to take further action to resolve the request. The most common status codes are those in the 400–499<br />

range, which indicate errors that are caused by bad client application requests, and the codes in the 500–<br />

599 range, which indicate server errors.


12-32 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

For security reasons, the details of any exceptions are not propagated to the client by default. However,<br />

for debugging purposes, you can include detailed information about the causes of an exception by<br />

setting the UseVerboseErrors property of the <strong>Data</strong>ServiceConfiguration object to true.<br />

If an exception occurs before the data service has started (there is a problem <strong>with</strong> its configuration, for<br />

example), the HandleException method will not run. To debug problems such as this, you can<br />

temporarily define a WCF service behavior that sets the IncludeExceptionDetailInFaults property to<br />

true, either as part of the WCF configuration in the Web.config file, or by applying the ServiceBehavior<br />

attribute to the data service, as shown in the following code example.<br />

[<strong>Visual</strong> Basic]<br />

Imports System.ServiceModel<br />

...<br />

_<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

...<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

using System.ServiceModel;<br />

...<br />

[ServiceBehavior(IncludeExceptionDetailInFaults=true)]<br />

public class Product<strong>Data</strong>Service : <strong>Data</strong>Service<br />

{<br />

...<br />

}<br />

Important: If you set the IncludeExceptionDetailInFaults property to true for a data service, make sure<br />

that you disable this feature before you deploy the service in a production environment.<br />

Question: If you don't override the HandleException method in your data service, what happens if an<br />

exception occurs?


Implementing Operations in a WCF <strong>Data</strong> Service<br />

Key Points<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-33<br />

The primary purpose of a WCF <strong>Data</strong> Service is to provide access to data. However, you can also implement<br />

business operations that manipulate data.<br />

A WCF <strong>Data</strong> Service operation is simply a method of the data service that is visible to the REST<br />

infrastructure and that can be accessed by sending an HTTP GET, PUT, POST, or DELETE request.<br />

<strong>Data</strong> service operations that are accessed by using a GET request should be annotated <strong>with</strong> the WebGet<br />

attribute. These operations typically return data, although they may run some business logic that does not<br />

return a value. Operations that are accessed by using PUT, POST, or DELETE requests should be annotated<br />

<strong>with</strong> the WebInvoke attribute. These operations typically modify the data that the service uses in some<br />

way.<br />

Like entity sets, you must explicitly enable access to operations that a WCF data service exposes. You can<br />

perform this task by calling the SetServiceOperation<strong>Access</strong>Rule method of the<br />

<strong>Data</strong>ServiceConfiguration object when you initialize the service. You specify the name of the operation,<br />

and the appropriate access rights.<br />

Note: <strong>Access</strong> rights for resources are described in detail later in this module.<br />

A data service operation can take parameters and return one of the following data types:<br />

• IEnumerable or IQueryable (where T represents an entity type in the service). If an<br />

operation returns an enumerable collection based on an entity type that the service recognizes, a<br />

client application can perform queries by specifying HTTP URIs in the manner shown in the previous<br />

topics in this lesson. Implementing an operation that returns an enumerable collection in this way<br />

gives you detailed control over the contents of the collection; it is the responsibility of your code to<br />

generate this collection, possibly based on information that the client application provides.


12-34 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The following code example shows an operation that retrieves products that have a color specified as a<br />

parameter.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("ProductsByColor",<br />

ServiceOperationRights.ReadMultiple)<br />

End Sub<br />

...<br />

_<br />

Public Function ProductsByColor(ByVal color As String) As IQueryable(Of Product)<br />

If Not [String].IsNullOrEmpty(color) Then<br />

Return From p In Me.Current<strong>Data</strong>Source.Products _<br />

Where [String].Equals(p.Color, color) _<br />

Select p<br />

Else<br />

Throw New ArgumentException("Color must be specified", "color")<br />

End If<br />

End Function<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service : <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<br />

<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("ProductsByColor",<br />

ServiceOperationRights.ReadMultiple);<br />

}<br />

}<br />

...<br />

[WebGet]<br />

public IQueryable ProductsByColor(string color)<br />

{<br />

if (!String.IsNullOrEmpty(color))<br />

{<br />

return from p in this.Current<strong>Data</strong>Source.Products<br />

where String.Equals(p.Color, color)<br />

select p;<br />

}<br />

}<br />

else<br />

{<br />

throw new ArgumentException("Color must be specified",<br />

"color");<br />

}


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-35<br />

Note: You can access the data source for a WCF <strong>Data</strong> Service by using the Current<strong>Data</strong>Source property.<br />

You can invoke this operation by specifying a URI such as this. Notice that you specify the parameter to<br />

the method by using an HTTP query string.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/ProductsBuColor?color='Red'<br />

• T (where T represents an entity type in the service). An operation can return a single instance of an<br />

entity. The following code example shows an operation that retrieves a product that has a specified<br />

ID. Notice that you should also annotate an operation that returns a scalar value rather than a<br />

collection <strong>with</strong> the SingleResult attribute.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("ProductByID",<br />

ServiceOperationRights.ReadMultiple)<br />

End Sub<br />

...<br />

_<br />

_<br />

Public Function ProductByID(ByVal id As Integer) As Product<br />

Dim prod As Product = (From p In Me.Current<strong>Data</strong>Source.Products _<br />

Where p.ProductID = id _<br />

Select p).[Single]()<br />

Return prod<br />

End Function<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service : <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<br />

<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("ProductByID",<br />

ServiceOperationRights.ReadMultiple);<br />

}<br />

...<br />

[WebGet]<br />

[SingleResult]<br />

public Product ProductByID(int id)<br />

{<br />

Product prod = (from p in this.Current<strong>Data</strong>Source.Products<br />

where p.ProductID == id<br />

select p).Single();<br />

return prod;<br />

}<br />

}


12-36 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

• A primitive value. The next code example shows an operation that retrieves a count of all products<br />

that have a specified color.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("NumProductsByColor",<br />

ServiceOperationRights.ReadMultiple)<br />

End Sub<br />

...<br />

_<br />

_<br />

Public Function NumProductsByColor(ByVal color As String) As Integer<br />

If Not [String].IsNullOrEmpty(color) Then<br />

Return (From p In Me.Current<strong>Data</strong>Source.Products _<br />

Where [String].Equals(p.Color, color) _<br />

Select p).Count()<br />

Else<br />

Throw New ArgumentException("Color must be specified", "color")<br />

End If<br />

End Function<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service : <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<br />

<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("NumProductsByColor",<br />

ServiceOperationRights.ReadMultiple);<br />

}<br />

}<br />

...<br />

[WebGet]<br />

[SingleResult]<br />

public int NumProductsByColor(string color)<br />

{<br />

if (!String.IsNullOrEmpty(color))<br />

{<br />

return (from p in this.Current<strong>Data</strong>Source.Products<br />

where String.Equals(p.Color, color)<br />

select p).Count();<br />

}<br />

}<br />

else<br />

{<br />

throw new ArgumentException("Color must be specified",<br />

"color");<br />

}


• void. Not all operations have to return a value.<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-37<br />

Question: How do you specify that an operation in a WCF <strong>Data</strong> Service returns a scalar value rather than<br />

a collection?<br />

Additional Reading<br />

For more information about implementing service operations in a WCF <strong>Data</strong> Service, see the Service<br />

Operations (WCF <strong>Data</strong> Services) page at http://go.microsoft.com/fwlink/?LinkID=194096.


12-38 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Deploying a WCF <strong>Data</strong> Service<br />

Key Points<br />

You can deploy a WCF <strong>Data</strong> Service in several ways, depending on how you have implemented the WCF<br />

<strong>Data</strong> Service.<br />

Deploying a WCF <strong>Data</strong> Service in an ASP.NET Web Application<br />

If you have implemented the WCF <strong>Data</strong> Service in an ASP.NET Web application, you can use the Copy<br />

Web Site command. This command displays a dialog box that enables you to specify the target Web site<br />

and select the files and folders to copy to that Web site. You can also synchronize the target Web site <strong>with</strong><br />

the source Web site if the files on the source Web site are newer.<br />

Copy files by using the Copy Web Site command<br />

1. In Solution Explorer, right-click the ASP.NET Web application, and then click Copy Web Site.<br />

2. In the Connections list, select the site to connect to as the remote site.<br />

If the site that you want to connect to is not in the list, click Connect, and then use the Open Web<br />

Site dialog box to connect to the site that you want to copy files to or from.<br />

When the Copy Web Site tool opens the remote site, it examines the files on both sites and indicates<br />

their status (New, Unchanged, Changed, or Deleted). If there are two different versions of a file, one<br />

on the source site and one on the remote site, an arrow points from the newer version to the older<br />

version.<br />

Note: To see files that have been deleted, select the Show Deleted Files check box. The names of deleted<br />

files have a glyph next to them that indicates that they have been deleted.<br />

3. In the Source Web Site list box, select the files and folders that you want to copy.<br />

4. Click the copy button between the Source Web Site and Remote Web Site lists, using the button<br />

that indicates the direction in which you want to copy (you can also copy files from the target Web


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-39<br />

site back to the source Web site). To copy the recent version of a file to the site <strong>with</strong> the older version,<br />

click the synchronize button.<br />

The status pane at the bottom of the window displays the results of the copy operation.<br />

Note: Files that have not changed are not copied.<br />

Synchronization can detect situations that require you to indicate how it should proceed. The<br />

following table summarizes these conditions.<br />

Condition Result<br />

File has been deleted on a site. Prompts you to indicate whether you want to delete the<br />

corresponding file in the other site.<br />

A file has different timestamps on both<br />

sites. (The file has been added or edited<br />

at different times in both sites.)<br />

Prompts you to indicate which version of the file you want to<br />

keep.<br />

The Copy Web Site command is useful for manually copying the files for a Web application. You can<br />

also use the Publish Web Site Wizard, which compiles and copies the executable files and Web pages<br />

for your Web application to a target Web site, and configures the application. Note that the Publish<br />

Web Site Wizard does not copy the source code from any of the code-behind files to the target site; it<br />

only publishes the compiled code.<br />

To run the Publish Web Site Wizard, on the Build menu, click Publish Web Site.<br />

Deploying a WCF <strong>Data</strong> Service in a WCF Service Application<br />

If you have implemented the WCF <strong>Data</strong> Service <strong>with</strong>in a WCF service application, you can use the Create<br />

Package command to build a deployment package, or use the Publish command to publish the service<br />

to a Web site that supports one-click publishing. Both commands use the Package and Publish settings<br />

for the project to specify how to deploy the service.<br />

You configure the Package and Publish settings for a project by using the Package or Publish tab in the<br />

Properties window of the project. Alternatively, you can click the Package or Publish Settings command<br />

on the Project menu. The Package or Publish tabs enable you to specify that the files that make up the<br />

service should be compressed into a .zip file, and the name of the application and Web site that this file<br />

should be unzipped into when the service is deployed. You can also specify which files are included in the<br />

.zip file, that is, whether you include only the binaries and configuration files that are needed to run the<br />

application, or all files (including source files) that the project uses.<br />

Note: If you do not create a .zip file, the Create Package command instead creates a folder structure that<br />

contains all of your files when you generate the package.<br />

If your service accesses a SQL Server database, you can include and edit the necessary connection strings<br />

by using the Deploy SQL tab. The Deploy SQL tab also enables you to include SQL scripts to configure<br />

the database and that are run when the service is deployed.<br />

The Create Package command generates the .zip file or folder structure by using the Package or Publish<br />

settings. You can run the Create Package command by right-clicking the service in Solution Explorer and<br />

clicking Create Package, or by using the Create Package command on the Project menu.


12-40 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

The Create Package command also creates a command script that you can use to deploy the service. This<br />

command script uses MSDeploy to deploy the service, and it reads the values that you specified for the<br />

various deployment settings from an XML file that the Create Package command also generates. You can<br />

deploy a package manually by copying the files that are generated by the Create Package command to<br />

the target server and running the deployment script. Alternatively, you can use the Publish Web Wizard to<br />

automate the package and deployment process.<br />

You run the Publish Web Wizard by right-clicking the WCF Service project in Solution Explorer and<br />

clicking Publish, or by using the Publish command on the Build menu. The Publish Web Wizard lets you<br />

select the destination and the publication method:<br />

• Click MSDeploy to deploy the service to a Web server that supports one-click publishing.<br />

• Click FTP to copy the files for the service to a File Transfer Protocol (FTP) server.<br />

• Click File System to deploy the service to a local Web server.<br />

• Click FPSE to deploy the service by using Microsoft FrontPage Server Extensions.<br />

The remaining deployment settings are gathered from the Package and Publish settings for the service.<br />

Question: You need to deploy a WCF <strong>Data</strong> Service on multiple Web servers. Each instance should be<br />

configured in the same way. What is the easiest way to perform this task and ensure consistency across<br />

Web servers?


Lesson 3<br />

Consuming a WCF <strong>Data</strong> Service<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-41<br />

You can connect to a WCF <strong>Data</strong> Service and perform queries by using a browser such as Internet Explorer.<br />

However, in many cases, you need to query and process data by using your own applications. You can<br />

build a client application that connects to a WCF <strong>Data</strong> Service and consumes the data that the service<br />

exposes by generating a client library. The client library acts as a proxy for the service, providing access to<br />

the data in the service through method calls.<br />

In this lesson, you will learn how to generate a client library for a WCF <strong>Data</strong> Service and use this client<br />

library to build a client application that can connect to the WCF <strong>Data</strong> Service.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Generate a client library that an application can use to connect to a WCF <strong>Data</strong> Service.<br />

• Query data from a WCF <strong>Data</strong> Service in a client application.<br />

• Filter data and apply projections in a client application.<br />

• Describe how the WCF <strong>Data</strong> Services client library handles associations between entities.<br />

• Catch and handle exceptions that a WCF <strong>Data</strong> Service raises in a client application.<br />

• Invoke an operation in a WCF <strong>Data</strong> Service.<br />

• Materialize data from a WCF <strong>Data</strong> Service into a custom type.


12-42 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Generating the Client Library for a WCF <strong>Data</strong> Service<br />

Key Points<br />

The client library for a WCF <strong>Data</strong> Service consists of a class that is derived from the <strong>Data</strong>ServiceContext<br />

type that exposes one or more <strong>Data</strong>ServiceQuery objects as properties. The name of this class is usually<br />

the same as the name of the ObjectContext object that is used by the entity model on which the WCF<br />

<strong>Data</strong> Service is based. For example, the Product<strong>Data</strong>Service WCF <strong>Data</strong> Service that was shown in the<br />

previous lesson uses an ObjectContext object called AdventureWorksEntities to connect to the<br />

underlying entity model, so the name of the <strong>Data</strong>ServiceContext type that is generated for the client<br />

library is also AdventureWorksEntities.<br />

The <strong>Data</strong>ServiceContext class performs a similar role to the ObjectContext class in the Entity<br />

Framework. A client application connects to the data source (in this case, a WCF <strong>Data</strong> Service) through a<br />

<strong>Data</strong>ServiceContext object and fetches the data for the entities that the data service exposes by using<br />

the <strong>Data</strong>ServiceQuery properties. Each <strong>Data</strong>ServiceQuery property is a generic collection object that<br />

presents data from one of the underlying entities that provides the data for the WCF <strong>Data</strong> Service. In the<br />

Product<strong>Data</strong>Service WCF <strong>Data</strong> Service, the entity model provides access to the Product,<br />

ProductDocument, ProductListPriceHistory, ProductModel, and ProductReview tables in the<br />

AdventureWorks database. The AdventureWorksEntities class (derived from the <strong>Data</strong>ServiceContext<br />

class) has <strong>Data</strong>ServiceQuery properties called Products, ProductDocuments,<br />

ProductListPriceHistories, ProductModels, and ProductReviews.<br />

The client library also provides definitions of the types that each <strong>Data</strong>ServiceQuery collection contains<br />

(Product, ProductDocument, ProductListPriceHistory, ProductModel, and ProductReview). A client<br />

application can perform Language-Integrated Query (LINQ) queries against the <strong>Data</strong>ServiceQuery<br />

collection properties, and the client library constructs the appropriate HTTP request to fetch the<br />

corresponding data. The WCF <strong>Data</strong> Service fetches the matching data and populates the<br />

<strong>Data</strong>ServiceQuery collection. The client application can then iterate through this collection and retrieve<br />

the data for each item.


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-43<br />

You can generate the client library for a WCF <strong>Data</strong> Service by using the Add Service Reference dialog<br />

box in <strong>Visual</strong> <strong>Studio</strong>, or by using the WCF <strong>Data</strong> Service client utility from the command line.<br />

Adding a Service Reference<br />

You can use the Add Service Reference dialog box in a client application. This dialog box enables you to<br />

specify the URL of the WCF <strong>Data</strong> Service to connect to. The dialog box sends a metadata query to the<br />

specified URL, and uses the response to generate the appropriate <strong>Data</strong>ServiceContext class that contains<br />

the <strong>Data</strong>ServiceQuery properties, and also the classes for each of the entities that the WCF <strong>Data</strong> Service<br />

has exposed. The returned metadata is stored in the client project as an .edmx file. This is not the same as<br />

an .edmx file that is generated by using the ADO.NET Entity <strong>Data</strong> Model Designer (it has a different<br />

format), but you can view this metadata file by using the XML editor or any text editor.<br />

Note: You can issue a metadata query from a browser by using the $metadata operator. For example, to<br />

query the metadata for the Product<strong>Data</strong>Service service, use the following query.<br />

http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc/$metadata<br />

Add a data service reference<br />

1. If the data service is not part of the solution and is not already running, start the data service and<br />

note the URI of the data service.<br />

2. Right-click the client project, and then select Add Service Reference.<br />

3. If the data service is part of the current solution, click Discover.<br />

Alternatively, if the data service is not part of the current solution, in the Address text box, type the<br />

base URL of the data service, and then click Go.<br />

4. Click OK.<br />

Using the WCF <strong>Data</strong> Service Client Utility<br />

You can generate the client library for a WCF <strong>Data</strong> Service from the <strong>Visual</strong> <strong>Studio</strong> command prompt by<br />

using the <strong>Data</strong>SvcUtil command. The <strong>Data</strong>SvcUtil command provides several options, but as a minimum,<br />

you must specify the name of the code file to generate and the URL of the WCF <strong>Data</strong> Service. By default,<br />

the <strong>Data</strong>SvcUtil command generates C# code, but you can specify the /language option if you prefer to<br />

generate Microsoft <strong>Visual</strong> Basic® code.<br />

The following example generates a <strong>Visual</strong> Basic version of the client library for the Product<strong>Data</strong>Service<br />

WCF <strong>Data</strong> Service. The code is generated in a file called ProductClient.vb. You can then add the<br />

ProductClient.vb source file to your client application.<br />

<strong>Data</strong>svcutil /out:ProductClient.vb /language:VB<br />

/uri:http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc<br />

If you do not have direct access to the WCF <strong>Data</strong> Service, you can still generate the client library if you<br />

have an .edmx file or .csdl file that defines the conceptual model on which the WCF <strong>Data</strong> Service is based.<br />

The WCF <strong>Data</strong> Service developer can supply this file separately. To use an .edmx or .csdl file as the source<br />

for the client library, specify the /in option to the <strong>Data</strong>SvcUtil command, and provide the name of the file,<br />

as shown in the following code example.<br />

<strong>Data</strong>svcutil /out:ProductClient.cs /in:Product<strong>Data</strong>.edmx


12-44 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Question: What happens if the definition of a WCF <strong>Data</strong> Service changes after you have generated a<br />

client library for that service?<br />

Additional Reading<br />

For more information about using the <strong>Data</strong>SvcUtil command, see the WCF <strong>Data</strong> Service Client Utility<br />

(<strong>Data</strong>SvcUtil.exe) page at http://go.microsoft.com/fwlink/?LinkId=196759.


Querying <strong>Data</strong> in a Client Application<br />

Key Points<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-45<br />

To retrieve data from a WCF <strong>Data</strong> Service by using the client library, a client application can perform the<br />

following tasks:<br />

1. Create an instance of the type derived from the <strong>Data</strong>ServiceContext class in the client library, and<br />

connect to the WCF <strong>Data</strong> Service. The constructor for this class expects a Uri object that contains the<br />

address of the service.<br />

2. Retrieve data by querying the appropriate <strong>Data</strong>ServiceQuery collection in the <strong>Data</strong>ServiceContext<br />

object. When you query a <strong>Data</strong>ServiceQuery collection, the client library constructs an HTTP request<br />

that specifies the resource and any criteria that is required. The query is transmitted to the WCF <strong>Data</strong><br />

Service, and the data is returned and used to populate the <strong>Data</strong>ServiceQuery object.<br />

3. Iterate through the items in the <strong>Data</strong>ServiceQuery collection and process the objects that are<br />

returned.<br />

The following code example connects to the Product<strong>Data</strong>Service WCF <strong>Data</strong> Service by using the<br />

AdventureWorksEntities type in the client library (this is the class derived from <strong>Data</strong>ServiceContext).<br />

The parameter to the constructor is the address of the service. The code then queries the Products<br />

<strong>Data</strong>ServiceQuery property to fetch all products, and displays the name of each one.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

For Each product As Product In context.Products<br />

Console.WriteLine(product.Name)<br />

Next<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri


12-46 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

foreach (Product product in context.Products)<br />

{<br />

Console.WriteLine(product.Name);<br />

}<br />

Paging <strong>Data</strong><br />

When a client application performs a query, the number of items that are returned is governed by the<br />

page size of the WCF <strong>Data</strong> Service; if the page size that is specified for an entity set is 10, only the first 10<br />

items will be retrieved by using the corresponding <strong>Data</strong>ServiceQuery object. If you want to fetch<br />

additional pages, you can perform the query by using the Execute method of the <strong>Data</strong>ServiceQuery<br />

object. The Execute method returns the data as an IEnumerable collection that you can cast as a<br />

QueryOperationResponse object. The QueryOperationResponse object contains the results of the<br />

query as before, but also provides a method called GetContinuation. The GetContinuation method<br />

returns a <strong>Data</strong>ServiceQueryContinuation object that represents the URI for the next page of data, or<br />

null if there is no more data. You can pass the <strong>Data</strong>ServiceQueryContinuation object to a subsequent<br />

call to the Execute method of the <strong>Data</strong>ServiceQuery object to fetch the next page of data, return the<br />

results as another QueryOperationResponse object, and process the data in this page. You can repeat<br />

this process until the GetContinuation method returns a null value.<br />

The following code example shows how to read all of the Product data from the Product<strong>Data</strong>Service<br />

service by following this approach.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

Dim continuationUri As <strong>Data</strong>ServiceQueryContinuation(Of Product) = Nothing<br />

' Fetch the first page of results<br />

Dim response As QueryOperationResponse(Of Product) =<br />

DirectCast(context.Products.Execute(), QueryOperationResponse(Of Product))<br />

Do<br />

' The first time around continuationUri will be null,<br />

' and the first page has already been fetched<br />

' before the loop started<br />

' On subsequent iteration, fetch the next page<br />

' by using the <strong>Data</strong>ServiceQueryContinuation object<br />

If continuationUri IsNot Nothing Then<br />

End If<br />

response = context.Execute(continuationUri)<br />

' Iterate through the results in the page<br />

' and display the name of each product<br />

For Each product As Product In response<br />

Console.WriteLine(product.Name)<br />

Next<br />

' Iterate until there are no more pages<br />

Loop While (continuationUri = response.GetContinuation()) IsNot Nothing


[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

<strong>Data</strong>ServiceQueryContinuation continuationUri = null;<br />

// Fetch the first page of results<br />

QueryOperationResponse response =<br />

context.Products.Execute() as QueryOperationResponse;<br />

do<br />

{<br />

// The first time around continuationUri will be null,<br />

// and the first page has already been fetched<br />

// before the loop started<br />

// On subsequent iteration, fetch the next page<br />

// by using the <strong>Data</strong>ServiceQueryContinuation object<br />

if (continuationUri != null)<br />

{<br />

response = context.Execute(continuationUri);<br />

}<br />

// Iterate through the results in the page<br />

// and display the name of each product<br />

foreach (Product product in response)<br />

{<br />

Console.WriteLine(product.Name);<br />

}<br />

}<br />

// Iterate until there are no more pages<br />

while ((continuationUri = response.GetContinuation()) != null);<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-47<br />

Note: When you fetch a page of data, you retrieve a snapshot of the data as it was at the time when the<br />

page was fetched. Another user may update the data between your application fetching a page and your<br />

application displaying the data for that page, and you will not see this update. However, if a user modifies<br />

data on a page before your application fetches that page, you will see the new data.<br />

Counting Objects<br />

You can obtain a count of the number of objects that match a query by using the IncludeTotalCount<br />

method of the <strong>Data</strong>ServiceQuery object. This method causes the client library to include the $count<br />

query option in the HTTP request that it sends to the WCF <strong>Data</strong> Service. You can obtain the value that is<br />

returned by reading the TotalCount property of the QueryOperationResponse object that is returned<br />

when you run the query.<br />

The following code example shows how to include a count of the number of products in a query; the<br />

changes from the previous example are highlighted in bold. Note that the count is the total number of<br />

matching objects, and not the number of objects that are retrieved so far if you are performing a paged<br />

query.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

Dim continuationUri As <strong>Data</strong>ServiceQueryContinuation(Of Product) = Nothing


12-48 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Dim response As QueryOperationResponse(Of Product) =<br />

DirectCast(context.Products.IncludeTotalCount().Execute(), QueryOperationResponse(Of<br />

Product))<br />

Do<br />

If continuationUri IsNot Nothing Then<br />

response = context.Execute(continuationUri)<br />

End If<br />

For Each product As Product In response<br />

Next<br />

Console.WriteLine(product.Name)<br />

Loop While (continuationUri = response.GetContinuation()) IsNot Nothing<br />

Console.WriteLine("Number of products: {0}", response.TotalCount)<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

<strong>Data</strong>ServiceQueryContinuation continuationUri = null;<br />

QueryOperationResponse response =<br />

context.Products.IncludeTotalCount().Execute()<br />

as QueryOperationResponse;<br />

do<br />

{<br />

if (continuationUri != null)<br />

{<br />

response = context.Execute(continuationUri);<br />

}<br />

foreach (Product product in response)<br />

{<br />

Console.WriteLine(product.Name);<br />

}<br />

}<br />

while ((continuationUri = response.GetContinuation()) != null);<br />

Console.WriteLine("Number of products: {0}", response.TotalCount);<br />

Question: You have built a client application that fetches and displays a list of orders by querying a WCF<br />

<strong>Data</strong> Service. This application previously retrieved every order from the service, but recently you have<br />

noticed that it only displays the first 20. You have not made any changes to the application, and you know<br />

that there are at least 100 orders in the database. What might have changed that is causing this<br />

phenomenon?<br />

Additional Reading<br />

For more information about handling paged content in a WCF <strong>Data</strong> Services client application, see the<br />

Paged Content section of the Loading Deferred Content (WCF <strong>Data</strong> Services) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194098.


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-49<br />

For information about retrieving binary and streamed data in a WCF <strong>Data</strong> Services client application, see<br />

the Working <strong>with</strong> Binary <strong>Data</strong> (WCF <strong>Data</strong> Services) page at<br />

http://go.microsoft.com/fwlink/?LinkID=194099.


12-50 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Filtering and Projecting <strong>Data</strong> in a Client Application<br />

Key Points<br />

You can filter data in a client application by using a LINQ query against the appropriate<br />

<strong>Data</strong>ServiceQuery collection and specifying the relevant criteria. The client library converts the LINQ<br />

query into a URL that incorporates the equivalent $filter query option. You can also include a LINQ<br />

orderby clause, and the client library generates the equivalent $orderby query option.<br />

Note: The form of LINQ that the client library implements is known as LINQ to WCF <strong>Data</strong> Services. This<br />

version of LINQ automatically converts LINQ queries that are performed against a WCF <strong>Data</strong> Service into<br />

the equivalent HTTP queries.<br />

The following code example shows a LINQ to WCF <strong>Data</strong> Services query that fetches all red products from<br />

the Product<strong>Data</strong>Service WCF <strong>Data</strong> Service. The data is retrieved in name order.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

For Each product As Product In From p In context.Products _<br />

Where p.Color = "Red" _<br />

Order By p.Name _<br />

Select p<br />

Console.WriteLine("Name: {0}, Color:{1}", product.Name, product.Color)<br />

Next<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri


("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

foreach (Product product in from p in context.Products<br />

where p.Color == "Red"<br />

orderby p.Name<br />

select p)<br />

{<br />

}<br />

Console.WriteLine("Name: {0}, Color:{1}",<br />

product.Name, product.Color);<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-51<br />

If the WCF <strong>Data</strong> Service uses paging, performing a LINQ to WCF <strong>Data</strong> Services query in this manner will<br />

only retrieve the first page of data. You should use a <strong>Data</strong>ServiceQueryContinuation object to fetch<br />

each page of data in turn. The <strong>Data</strong>ServiceQuery class provides the AddQueryOption method that you<br />

can use in this situation. By using the AddQueryOption method, you can specify a query option and a<br />

value for that option. The option is then appended to the HTTP query that the client library sends to the<br />

WCF <strong>Data</strong> Service. The name of the query option and the syntax for specifying a value for that option are<br />

the same as that used when you specify an HTTP query directly. If you need to specify more than one<br />

query option, you can invoke the AddQueryOption method multiple times.<br />

The next code example shows how to retrieve all red products in name order from a WCF <strong>Data</strong> Service<br />

that implements paging. Notice that you invoke the AddQueryOption method before calling Execute to<br />

generate the result set.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

Dim continuationUri As <strong>Data</strong>ServiceQueryContinuation(Of Product) = Nothing<br />

Dim response As QueryOperationResponse(Of Product) =<br />

DirectCast(context.Products _<br />

.AddQueryOption("$filter", "Color eq 'Red'") _<br />

.AddQueryOption("$orderby", "Name") _<br />

.Execute(), QueryOperationResponse(Of Product))<br />

Do<br />

If continuationUri IsNot Nothing Then<br />

End If<br />

response = context.Execute(continuationUri)<br />

For Each product As Product In response<br />

Console.WriteLine(product.Name)<br />

Next<br />

Loop While (continuationUri = response.GetContinuation()) IsNot Nothing<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

<strong>Data</strong>ServiceQueryContinuation continuationUri = null;


12-52 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

QueryOperationResponse response = context.Products<br />

.AddQueryOption("$filter", "Color eq 'Red'")<br />

.AddQueryOption("$orderby", "Name")<br />

.Execute() as QueryOperationResponse;<br />

Do<br />

{<br />

if (continuationUri != null)<br />

{<br />

response = context.Execute(continuationUri);<br />

}<br />

foreach (Product product in response)<br />

{<br />

Console.WriteLine(product.Name);<br />

}<br />

}<br />

while ((continuationUri = response.GetContinuation()) != null);<br />

Projecting <strong>Data</strong><br />

You can limit the fields that a query returns by specifying a select clause in a LINQ to WCF <strong>Data</strong> Services<br />

query and projecting the data onto an appropriate type. Queries that define a select clause in this way<br />

are translated by the client library into HTTP requests that use the $select query option in the request<br />

URI.<br />

The following code example shows a query that fetches only the ProductID, Name, and Color fields for<br />

products.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

For Each product In (From p In context.Products _<br />

Where p.Color = "Red" _<br />

Order By (p.Name) _<br />

Select New With {<br />

.ID = p.ProductID, .Name = p.Name,<br />

.Color = p.Color<br />

})<br />

Next<br />

Console.WriteLine("ID: {0}, Name: {1}, Color:{2}",<br />

product.ID, product.Name, product.Color)<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

foreach (var product in from p in context.Products<br />

where p.Color == "Red"<br />

orderby p.Name<br />

select new {<br />

ID = p.ProductID, Name = p.Name,<br />

Color = p.Color<br />

})


{<br />

}<br />

Console.WriteLine("ID: {0}, Name: {1}, Color:{2}",<br />

product.ID, product.Name, product.Color);<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-53<br />

Question: You have built a client application that successfully fetches and displays a list of orders by<br />

querying a WCF <strong>Data</strong> Service. You decide that you do not need every field of the orders objects that the<br />

query returns, so you apply a projection that fetches only the orderID and orderDate fields (these are<br />

valid fields). However, the client application now generates the following exception message.<br />

The $select query option cannot be used to define a projection when the<br />

MaxProtocolVersion of the data service is set to <strong>Data</strong>ServiceProtocol.V1<br />

When you talk <strong>with</strong> the developer of the WCF <strong>Data</strong> Service, she assures you that the<br />

MaxProtocolVersion property of the <strong>Data</strong>ServiceBehavior object referenced by the<br />

<strong>Data</strong>ServiceConfiguration object that is used to configure the service is set to<br />

<strong>Data</strong>ServiceProtocolVersion.V2. What might be causing this problem?<br />

Additional Reading<br />

For more information about using projections in a WCF <strong>Data</strong> Services client application, see How to:<br />

Project Query Results (WCF <strong>Data</strong> Services) page at http://go.microsoft.com/fwlink/?LinkID=194100.


12-54 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Handling Associations Between Entities<br />

Key Points<br />

By default, when you submit a query, only the data for the entity that is directly referenced by that query<br />

is returned. For example, if you fetch data by using the Products <strong>Data</strong>ServiceQuery collection in the<br />

Product<strong>Data</strong>Service WCF <strong>Data</strong> Service, only products data is retrieved, even though products have<br />

relationships to other entities in the entity model that the WCF <strong>Data</strong> Service uses.<br />

Implementing Eager Loading<br />

If an entity has related data in other entities, you can fetch the data from those entities by using the<br />

Expand method. This method causes LINQ to WCF <strong>Data</strong> Services to include the $expand option to<br />

automatically fetch the related data from these entities. You provide the list of related entities as a<br />

comma-delimited string.<br />

The following code example retrieves all products that have a productID greater than 100. The call to the<br />

Expand method specifies the ProductReviews table, so the LINQ to WCF <strong>Data</strong> Services query<br />

automatically fetches the product reviews for each matching product. These reviews are available through<br />

the ProductReviews collection property of the Product type. The example displays the product ID and<br />

name of each product, and then prints the review date and the comments for each review for the product.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

For Each product As Product In From p In context.Products.Expand("ProductReviews") _<br />

Where p.ProductID > 100 _<br />

Select p<br />

Console.WriteLine("ID: {0}, Name: {1}", product.ProductID, product.Name)<br />

For Each review As ProductReview In product.ProductReviews<br />

Console.WriteLine("Date: {0}, Comments: {1}", review.ReviewDate,<br />

review.Comments)


Next<br />

Next<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

foreach (Product product in<br />

from p in context.Products.Expand("ProductReviews")<br />

where p.ProductID > 100<br />

select p)<br />

{<br />

Console.WriteLine("ID: {0}, Name: {1}",<br />

product.ProductID, product.Name);<br />

}<br />

foreach (ProductReview review in product.ProductReviews)<br />

{<br />

Console.WriteLine("Date: {0}, Comments: {1}",<br />

review.ReviewDate, review.Comments);<br />

}<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-55<br />

Note: LINQ to WCF <strong>Data</strong> Services does not support join operations; the following code example throws a<br />

NotSupportedException exception <strong>with</strong> the message "The method 'Join' is not supported."<br />

[<strong>Visual</strong> Basic]<br />

For Each product In From p In context.Products _<br />

Join r In context.ProductReviews On p.ProductID = r.ProductID _<br />

Where p.ProductID > 100 _<br />

Select New ()<br />

Next<br />

...<br />

[<strong>Visual</strong> C#]<br />

foreach (var product in from p in context.Products<br />

join r in context.ProductReviews<br />

on p.ProductID equals r.ProductID<br />

where p.ProductID > 100<br />

select new {<br />

ID = p.ProductID, Name = p.Name,<br />

r.ReviewDate, r.Comments })<br />

{<br />

}<br />

...<br />

Implementing Explicit Loading<br />

Using the eager loading strategy that the Expand method implements causes the data for the specified<br />

related entities to be retrieved as part of the same request that fetches the primary data for the query.


12-56 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

This approach is useful if you know that you will always need this related data, but it can be wasteful of<br />

bandwidth for the cases where you do not actually use these entities. As an alternative, you can use<br />

explicit loading. This strategy sends an additional query to the WCF <strong>Data</strong> Service that is requesting the<br />

related data for a specific object, but it has the advantage that it does not waste bandwidth by<br />

automatically fetching data that is not used.<br />

You can implement explicit loading by using the LoadProperty method of the <strong>Data</strong>ServiceContext<br />

object. You call the LoadProperty method each time you require data that is related to a particular entity;<br />

you specify the entity and the name of the <strong>Data</strong>ServiceQuery collection property that holds the related<br />

data.<br />

The following code example shows how to use the LoadProperty method to fetch the product reviews<br />

for a set of products.<br />

[<strong>Visual</strong> Basic]<br />

Dim context As New AdventureWorksEntities(New<br />

Uri("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"))<br />

For Each product As Product In From p In context.Products _<br />

Where p.ProductID > 100 _<br />

Select p<br />

Console.WriteLine("ID: {0}, Name: {1}", product.ProductID, product.Name)<br />

context.LoadProperty(product, "ProductReviews")<br />

For Each review As ProductReview In product.ProductReviews<br />

Console.WriteLine("Date: {0}, Comments: {1}", review.ReviewDate,<br />

review.Comments)<br />

Next<br />

Next<br />

[<strong>Visual</strong> C#]<br />

AdventureWorksEntities context = new AdventureWorksEntities(new Uri<br />

("http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"));<br />

foreach (Product product in from p in context.Products<br />

where p.ProductID > 100<br />

select p)<br />

{<br />

Console.WriteLine("ID: {0}, Name: {1}",<br />

product.ProductID, product.Name);<br />

}<br />

context.LoadProperty(product, "ProductReviews");<br />

foreach (ProductReview review in product.ProductReviews)<br />

{<br />

Console.WriteLine("Date: {0}, Comments: {1}",<br />

review.ReviewDate, review.Comments);<br />

}<br />

Question: You are building a client application for a WCF <strong>Data</strong> Service that needs to fetch all orders and<br />

the invoices related to these orders. You have attempted to perform the LINQ to WCF <strong>Data</strong> Services query<br />

in the following code example.<br />

[<strong>Visual</strong> Basic]


For Each order In From o In context.Orders _<br />

Join i In context.Invoices On o.OrderID = i.OrderID _<br />

Select New ()<br />

Next<br />

...<br />

[<strong>Visual</strong> C#]<br />

foreach (var order in from o in context.Orders<br />

join i in context.Invoices<br />

on o.OrderID equals i.OrderID<br />

select new { o.OrderID, i.InvoiceDate })<br />

{<br />

...<br />

}<br />

This code compiles, but fails when it runs. Why?<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-57


12-58 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Handling WCF <strong>Data</strong> Service Exceptions in a Client Application<br />

Key Points<br />

When a client application sends a request to a WCF <strong>Data</strong> Service, that request may fail for many reasons;<br />

possibly, the client application has attempted to access data that it does not have rights over, or it has<br />

tried to perform a query that requires functionality that the service has restricted.<br />

If the failure is due to the way in which the client application interacts <strong>with</strong> the service (as opposed to a<br />

failure caused by some other problem, such as a network failure when attempting to connect to the<br />

service), the service responds by throwing a <strong>Data</strong>ServiceException exception, as described in the<br />

previous lesson in this module. The <strong>Data</strong>ServiceException type is a serializable exception that is<br />

specifically designed to communicate the causes of a failure in a WCF <strong>Data</strong> Service. When the client library<br />

receives a <strong>Data</strong>ServiceException exception, it actually deserializes it as a <strong>Data</strong>ServiceClientException<br />

object, which it passes to your application.<br />

If your application was performing a query when the exception occurred, the<br />

<strong>Data</strong>ServiceClientException exception is wrapped in a <strong>Data</strong>ServiceQueryException object, <strong>with</strong> the<br />

message "An error occurred while processing the request." You can access the<br />

<strong>Data</strong>ServiceClientException exception that contains the reason for the exception by examining the<br />

InnerException property of the <strong>Data</strong>ServiceQueryException object.<br />

Note: The amount of detail about the cause of the exception that is passed across the network and<br />

reported by the <strong>Data</strong>ServiceClientException object is controlled by the UseVerboseErrors property of<br />

the <strong>Data</strong>ServiceConfiguration object that is used to configure the WCF <strong>Data</strong> Service.<br />

If a client application sends a request other than a query, the WCF <strong>Data</strong> Service can respond <strong>with</strong> a<br />

<strong>Data</strong>ServiceRequestException exception.<br />

A client application should be prepared to catch the <strong>Data</strong>ServiceQueryException type when it performs<br />

query operations, and the <strong>Data</strong>ServiceRequestException exception when it performs other types of


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-59<br />

operations such as modifying data. A client application should also be prepared to catch the<br />

<strong>Data</strong>ServiceClientException type to handle any other exceptions that the WCF <strong>Data</strong> Service throws<br />

when it performs other types of operations.<br />

Note: Modifying data in a WCF <strong>Data</strong> Service by using a client application is described in the next module.<br />

Question: You are building a client application for a WCF <strong>Data</strong> Service that queries data. In your<br />

application, you catch and handle the <strong>Data</strong>ServiceClientException exception whenever you submit a<br />

query, but your application periodically fails <strong>with</strong> an unhandled exception. You are certain that the error is<br />

being caused by the query operation, and that you are connecting successfully to the WCF <strong>Data</strong> Service.<br />

Why might errors that are thrown as a result of the query operation failing lead to unhandled exceptions<br />

in your application?


12-60 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Invoking WCF <strong>Data</strong> Service Operations<br />

Key Points<br />

If a WCF <strong>Data</strong> Service implements data service operations, you can invoke these operations by using the<br />

Execute method of the <strong>Data</strong>ServiceContext object in a client application. You specify a URI that contains<br />

the name of the operation and any parameters in the form of an HTTP query string.<br />

When you invoke a service operation, the value that the Execute method of the <strong>Data</strong>ServiceContext<br />

object in the client library returns is an enumerable collection. If the service operation returns a single,<br />

scalar value, you should extract that value from the collection by using a method such as First.<br />

The Product<strong>Data</strong>Service service that was described in Lesson 1 exposes several service operations:<br />

• ProductsByColor, which takes a string parameter called color and returns a collection of Product<br />

objects.<br />

• ProductByID, which takes an integer parameter called id and returns a single Product object.<br />

• NumProductsByColor, which takes a string parameter called color and returns an integer value.<br />

You can invoke these operations from a client application as shown in the following code examples.<br />

[<strong>Visual</strong> Basic]<br />

Dim serviceAddress As String = "http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"<br />

Dim context As New AdventureWorksEntities(New Uri(serviceAddress))<br />

' Call the ProductsByColor service operation<br />

' Provide an HTTP query string that specifies the color parameter<br />

For Each product As Product In context.Execute(Of Product)(New<br />

Uri("/ProductsByColor?color='Red'", UriKind.Relative))<br />

Console.WriteLine(product.Name)<br />

...<br />

Next


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-61<br />

' Call the ProductByID service operation<br />

' Provide an HTTP query string that specifies the id parameter<br />

' The service operation returns a single value, so call First<br />

' to extract this value from the enumerable collection generated<br />

' by the client library<br />

Dim product100 As Product = context.Execute(Of Product)(New Uri("/ProductByID?id=316",<br />

UriKind.Relative)).First()<br />

' Call the NumProductsByColor service operation<br />

' Provide an HTTP query string that specifies the color parameter<br />

' Call First to extract the scalar value from the enumerable<br />

' collection generated by the client library<br />

Dim numberOfBlackProducts As Integer = context.Execute(Of Integer)(New<br />

Uri("/NumProductsByColor?color='Black'", UriKind.Relative)).First()<br />

[<strong>Visual</strong> C#]<br />

string serviceAddress =<br />

"http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc";<br />

AdventureWorksEntities context =<br />

new AdventureWorksEntities(new Uri(serviceAddress));<br />

// Call the ProductsByColor service operation<br />

// Provide an HTTP query string that specifies the color parameter<br />

foreach (Product product in context.Execute<br />

(new Uri("/ProductsByColor?color='Red'", UriKind.Relative)))<br />

{<br />

Console.WriteLine(product.Name);<br />

...<br />

}<br />

// Call the ProductByID service operation<br />

// Provide an HTTP query string that specifies the id parameter<br />

// The service operation returns a single value, so call First<br />

// to extract this value from the enumerable collection generated<br />

// by the client library<br />

Product product100 = context.Execute<br />

(new Uri("/ProductByID?id=316", UriKind.Relative)).First();<br />

// Call the NumProductsByColor service operation<br />

// Provide an HTTP query string that specifies the color parameter<br />

// Call First to extract the scalar value from the enumerable<br />

// collection generated by the client library<br />

int numberOfBlackProducts = context.Execute<br />

(new Uri("/NumProductsByColor?color='Black'",<br />

UriKind.Relative)).First();<br />

Invoking a <strong>Data</strong> Service Operation Asynchronously<br />

Some service operations may take a significant time to run. The <strong>Data</strong>ServiceContext class provides an<br />

asynchronous version of the Execute method called BeginExecute that you can use to invoke longrunning<br />

operations on a background thread in a client application. You can use the BeginExecute<br />

method to implement the Asynchronous Design pattern. The parameters to the BeginExecute method<br />

are the URI of the data service operation (including any parameters), a callback method to run when the<br />

operation completes, and a reference to the <strong>Data</strong>ServiceContext object (passed as state information to<br />

the callback method). In the callback method, call the EndExecute method of the <strong>Data</strong>ServiceContext<br />

object to obtain the results of the service operation.


12-62 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Note: The <strong>Data</strong>ServiceContext class also provides an overloaded version of BeginExecute that enables<br />

you to specify a <strong>Data</strong>ServiceQueryContinuation object as the first parameter. You can use this version to<br />

perform an operation that returns one or more pages of data.<br />

The following code example shows how to run the ProductsByColor service operation on a separate<br />

thread by using the Asynchronous Design pattern.<br />

[<strong>Visual</strong> Basic]<br />

Private Shared Sub OnProductsQueryComplete(ByVal result As IAsyncResult)<br />

Dim context As AdventureWorksEntities = TryCast(result.AsyncState,<br />

AdventureWorksEntities)<br />

Dim redProducts As IEnumerable(Of Product) = context.EndExecute(Of Product)(result)<br />

For Each product As Product In redProducts<br />

Console.WriteLine(product.Name)<br />

Next<br />

End Sub<br />

...<br />

Dim serviceAddress As String = "http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"<br />

Dim context As New AdventureWorksEntities(New Uri(serviceAddress))<br />

context.BeginExecute(Of Product)(New Uri("/ProductsByColor?color='Red'",<br />

UriKind.Relative), OnProductsQueryComplete, context)<br />

...<br />

[<strong>Visual</strong> C#]<br />

private static void OnProductsQueryComplete(IAsyncResult result)<br />

{<br />

}<br />

...<br />

AdventureWorksEntities context =<br />

result.AsyncState as AdventureWorksEntities;<br />

IEnumerable redProducts =<br />

context.EndExecute(result);<br />

foreach (Product product in redProducts)<br />

{<br />

Console.WriteLine(product.Name);<br />

}<br />

string serviceAddress =<br />

"http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc";<br />

AdventureWorksEntities context =<br />

new AdventureWorksEntities(new Uri(serviceAddress));<br />

context.BeginExecute(<br />

new Uri("/ProductsByColor?color='Red'", UriKind.Relative),<br />

OnProductsQueryComplete, context);<br />

...


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-63<br />

Question: If you attempt to invoke an operation asynchronously, but provide an invalid parameter, where<br />

will the exception occur in your application?<br />

Additional Reading<br />

For more information about invoking a WCF <strong>Data</strong> Service operation asynchronously, see the<br />

Asynchronous Operations (WCF <strong>Data</strong> Services) page at http://go.microsoft.com/fwlink/?LinkID=194101.


12-64 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Materializing <strong>Data</strong> from a WCF <strong>Data</strong> Service into a Custom Type<br />

Key Points<br />

It may not always be feasible or desirable to generate the client library for a WCF <strong>Data</strong> Service<br />

automatically, by using <strong>Visual</strong> <strong>Studio</strong> tools. For example, if you already have a set of data classes to use in<br />

your client, you will not want to generate a new set. In these circumstances, you can retrieve and<br />

materialize data from a WCF <strong>Data</strong> Service into your own types that are defined by the client application.<br />

For example, you can retrieve products from the Products<strong>Data</strong>Service service into the custom type in the<br />

following code example that is defined by a client application.<br />

[<strong>Visual</strong> Basic]<br />

Public Class LocalProduct<br />

Public Property ProductID As Integer<br />

Public Property Name As String<br />

Public Property ListPrice As Decimal<br />

End Class<br />

[<strong>Visual</strong> C#]<br />

public class LocalProduct<br />

{<br />

public int ProductID { get; set; }<br />

public string Name { get; set; }<br />

public decimal ListPrice { get; set; }<br />

}<br />

This type contains a subset of the fields that are defined by the Product entity that the data service uses.<br />

You can perform queries that fetch data into collections of this type by using a raw <strong>Data</strong>ServiceContext<br />

object. The <strong>Data</strong>ServiceContext type is located in the System.<strong>Data</strong>.Services.Client namespace. WCF<br />

<strong>Data</strong> Services implements a soft binding between the types that a data service implements and the types<br />

that a client application defines. By default, the client-side runtime checks that every field that is retrieved<br />

from the data service has a corresponding property in the data type that the client application uses, and


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-65<br />

throws an InvalidOperationException exception if one or more fields are missing. You can suppress this<br />

check by setting the IgnoreMissingProperties property of the <strong>Data</strong>ServiceContext object to true.<br />

Note: If you specify a field in the local client type that has no corresponding member in the type that the<br />

data service defines, the member in the client type will be populated <strong>with</strong> the default value for the type of<br />

the data member.<br />

The following code example shows how to invoke the ProductsByColor data service operation in the<br />

Product<strong>Data</strong>Service service and retrieve the matching products into LocalProduct objects.<br />

[<strong>Visual</strong> Basic]<br />

Imports System.<strong>Data</strong>.Service.Client<br />

...<br />

Dim serviceAddress As String = "http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc"<br />

Dim context As New <strong>Data</strong>ServiceContext(New Uri(serviceAddress))<br />

context.IgnoreMissingProperties = True<br />

Dim query As IEnumerable(Of LocalProduct) = context.Execute(Of LocalProduct)(New<br />

Uri("/ProductsByColor?color='Red'", UriKind.Relative))<br />

For Each product As LocalProduct In query<br />

Console.WriteLine("{0}" & vbTab & "{1}" & vbTab & "{2}", product.ProductID,<br />

product.Name, product.ListPrice)<br />

Next<br />

[<strong>Visual</strong> C#]<br />

using System.<strong>Data</strong>.Service.Client;<br />

...<br />

string serviceAddress =<br />

"http://MyHost/AdventureWorksSite/Product<strong>Data</strong>Service.svc";<br />

<strong>Data</strong>ServiceContext context = new <strong>Data</strong>ServiceContext(<br />

new Uri(serviceAddress));<br />

context.IgnoreMissingProperties = true;<br />

IEnumerable query = context.Execute(<br />

new Uri("/ProductsByColor?color='Red'", UriKind.Relative));<br />

foreach (LocalProduct product in query)<br />

{<br />

Console.WriteLine("{0}\t{1}\t{2}", product.ProductID,<br />

product.Name, product.ListPrice);<br />

}<br />

Question: You are building a client application for a WCF <strong>Data</strong> Service that queries data. You have<br />

retrieved data into a custom type that the client application defines, but when you display the retrieved<br />

data, the value in one of the fields (which holds integer data) is always set to zero. What might be causing<br />

this?


12-66 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lesson 4<br />

Protecting <strong>Data</strong> and Operations in a WCF <strong>Data</strong> Service<br />

The data and operations that a WCF <strong>Data</strong> Service exposes may be sensitive items, and in many cases, you<br />

should restrict access to these entities and operations to authenticated client applications. This lesson<br />

describes how you can use the features that WCF <strong>Data</strong> Services provides to help protect data, and<br />

summarizes some best practices for configuring and protecting a WCF <strong>Data</strong> Service endpoint to help<br />

prevent the possibility of unauthorized access and reduce the attack surface of a data service.<br />

Objectives<br />

After completing this lesson, you will be able to:<br />

• Protect a WCF <strong>Data</strong> Service.<br />

• Set entity and operation rights.<br />

• Use the query interceptor to restrict access to data.<br />

• Restrict access to operations.


Protecting a WCF <strong>Data</strong> Service<br />

Key Points<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-67<br />

By definition, Web applications allow users access to resources, such as those that a WCF <strong>Data</strong> Service<br />

exposes, through a Web server or other host environment. You should configure the host environment for<br />

the data service to:<br />

• Guard resources against unauthorized access.<br />

• Restrict access levels by user or role.<br />

• Establish data integrity and confidentiality, providing a relatively secure environment for applications<br />

to consume your data service.<br />

• Establish control over how your application can gain access to restricted resources.<br />

The security model that WCF <strong>Data</strong> Services implements depends on protection that the host environment<br />

provides. If you are hosting the data service in a Web application or Web service running under IIS, you<br />

should configure the Web application or Web service to enable authentication, and implement an<br />

appropriate level of access control.<br />

You specify access to resources that a WCF <strong>Data</strong> Service exposes on a resource-by-resource basis. The<br />

level of access that you grant automatically applies to all users who can access the site. If you need to<br />

provide different levels of access to different users, you can use a query interceptor, as described later in<br />

this lesson. A query interceptor can take advantage of the security credentials that are passed in the HTTP<br />

context to the data service, and you can programmatically decide whether a specific user or role should<br />

be allowed to access a resource.<br />

To protect requests that are submitted to a WCF data service, and the corresponding data that is returned<br />

by these requests, you can configure the data service and the Web service to use the HTTPS protocol and<br />

transport-level security. You can configure the WebHttpBinding binding that a data service uses to<br />

support HTTPS by specifying an appropriate WebHttpSecurityMode value. The following table<br />

summarizes the possible values that you can specify for the security mode.


12-68 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Value Description<br />

None HTTP requests have no security applied.<br />

Transport Transport-level security is applied to HTTP requests.<br />

TransportCredentialOnly HTTP-based client authentication is provided, but<br />

requests are not protected.<br />

The following code example shows how to configure the endpoint for the Product<strong>Data</strong>Service service to<br />

use transport-level security, and specify that users are authenticated by using certificates.<br />

<br />

<br />

...<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

...<br />

<br />

...<br />

<br />

Question: How do you configure a WCF <strong>Data</strong> Service to implement message-level security?<br />

Additional Reading<br />

For more information about configuring authorization and authentication for a Web application, see the<br />

Web Application Security at Run Time page at http://go.microsoft.com/fwlink/?LinkID=194102.<br />

For more information about configuring the WebHttpBinding binding, see the page<br />

at http://go.microsoft.com/fwlink/?LinkID=194103.


Granting <strong>Access</strong> to Entities<br />

Key Points<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-69<br />

You have seen that, for security reasons, WCF <strong>Data</strong> Services do not automatically expose any resources,<br />

such as entity collections that the entity model implements. When a WCF <strong>Data</strong> Service starts running, the<br />

InitializeService method is passed a <strong>Data</strong>ServiceConfiguration object that you can use to specify the<br />

security policy for the service. The SetEntitySet<strong>Access</strong>Rule method provides the means to grant and<br />

deny access to users of the WCF <strong>Data</strong> Service.<br />

The SetEntitySet<strong>Access</strong>Rule method takes two parameters:<br />

• The name of the entity. This string can also contain the "*" wildcard character to indicate all entities.<br />

Note that you can either specify the name of an entity or the string "*"; you cannot combine "*" <strong>with</strong><br />

other characters to form entity set name patterns. If you need to provide access to multiple entity<br />

sets, you must call the SetEntitySet<strong>Access</strong>Rule method for each entity set.<br />

• The access rights to grant to the entity. This is a value from the<br />

System.<strong>Data</strong>.Services.EntitySetRights enumeration. This enumeration defines various read and<br />

write access rights. You can combine entity set rights by using the bitwise or operator.<br />

The following table summarizes the values in the EntitySetRights enumeration.<br />

Value Description<br />

None Denial of all rights to access data.<br />

ReadSingle Authorization to read single data items.<br />

ReadMultiple Authorization to read sets of data.<br />

WriteAppend Authorization to create new data items in datasets.<br />

WriteReplace Authorization to replace data.


12-70 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Value Description<br />

WriteDelete Authorization to delete data items from datasets.<br />

WriteMerge Authorization to merge data.<br />

AllRead Authorization to read data.<br />

AllWrite Authorization to write data.<br />

All Authorization to create, read, update, and delete data.<br />

The following code example grants access to the ProductDocuments entity in the Product<strong>Data</strong>Service<br />

service. The statement enables a client application to read single instances of the ProductDocuments<br />

entity from the data service.<br />

[<strong>Visual</strong> Basic]<br />

Public Class Product<strong>Data</strong>Service<br />

Inherits <strong>Data</strong>Service(Of AdventureWorksEntities)<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

End Sub<br />

End Class<br />

config.SetEntitySet<strong>Access</strong>Rule("ProductDocuments", EntitySetRights.ReadSingle)<br />

[<strong>Visual</strong> C#]<br />

public class Product<strong>Data</strong>Service: <strong>Data</strong>Service<br />

{<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

config.SetEntitySet<strong>Access</strong>Rule("ProductDocuments", EntitySetRights.ReadSingle);<br />

}<br />

}<br />

If a client application performs a query that retrieves more than a single ProductDocuments entity, the<br />

query will fail <strong>with</strong> an HTTP "Forbidden" error.<br />

Question: You want to grant access to all entity sets that have a name that begins <strong>with</strong> the letter “P” in<br />

the Product<strong>Data</strong>Service service (such as Products, ProductDocuments, ProductModels, and so on) by<br />

using the statement in the following code example.<br />

config.SetEntitySet<strong>Access</strong>Rule("P*", EntitySetRights.ReadSingle)<br />

However, when a client application attempts to query the Products entity set, the result is an exception.<br />

Why?


Restricting <strong>Data</strong> <strong>Access</strong> by Using Query Interceptors<br />

Key Points<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-71<br />

An interceptor enables you to inject custom logic into the request-processing pipeline that a data service<br />

implements. WCF <strong>Data</strong> Services supports two types of interceptors:<br />

• A query interceptor, which runs when an HTTP GET request is received for a specified entity set.<br />

• A change interceptor, which runs when an HTTP request to update, insert, or delete data for a<br />

specified entity set is received.<br />

You implement an interceptor by defining a method <strong>with</strong> a specified signature, and tagged <strong>with</strong> the<br />

QueryInterceptor or ChangeInterceptor attribute. This attribute also specifies the entity set to associate<br />

<strong>with</strong> the interceptor; when a client application sends a request to query data in this entity set, the<br />

appropriate query interceptor runs automatically. A query interceptor method takes no parameters and<br />

returns a lambda expression that determines which items in the entity set can be returned by the query<br />

results.<br />

Note: This module concentrates on performing query operations. Change interceptors are described in a<br />

later module.<br />

The lambda expression that is returned by a query interceptor should take a parameter that corresponds<br />

to an entity from the underlying entity set, and generate a Boolean value that indicates whether this item<br />

can be retrieved from this set. When the HTTP GET operation is performed, this filter is applied to the<br />

entity set, and only the entities that match the condition that this lambda expression specifies are fetched.<br />

The following code example shows a query interceptor for the Products entity set in the<br />

Product<strong>Data</strong>Service service. The query interceptor limits the entities that are returned to all products<br />

that have a list price that is at least twice the standard cost of the product.<br />

[<strong>Visual</strong> Basic]


12-72 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

_<br />

Public Function OnQueryProducts() As Expression(Of Func(Of Product, Boolean))<br />

Return Function(p) p.ListPrice > 2 * p.StandardCost<br />

End Function<br />

[<strong>Visual</strong> C#]<br />

[QueryInterceptor("Products")]<br />

public Expression OnQueryProducts()<br />

{<br />

return p => p.ListPrice > 2 * p.StandardCost;<br />

}<br />

You can use a query interceptor to restrict the data that is available to different users and roles. The<br />

following code example shows how to enable all users who have the ProductsAdministrator role to<br />

retrieve all products, but deny access to all other users.<br />

Note: The static System.Web.HttpContext.Current property provides access to the HTTP context for the<br />

current request. The User property contains the user security information for the request, including the<br />

identity and roles that the user has. This feature depends on the Web site that is hosting the data service<br />

being configured to authenticate users correctly.<br />

[<strong>Visual</strong> Basic]<br />

_<br />

Public Function OnQueryProducts() As Expression(Of Func(Of Product, Boolean))<br />

If HttpContext.Current.User.IsInRole("ProductsAdministrator") Then<br />

Return Function(p) True<br />

Else<br />

Return Function(p) False<br />

End If<br />

End Function<br />

[<strong>Visual</strong> C#]<br />

[QueryInterceptor("Products")]<br />

public Expression OnQueryProducts()<br />

{<br />

if (HttpContext.Current.User.IsInRole("ProductsAdministrator"))<br />

{<br />

return p => true;<br />

}<br />

else<br />

{<br />

return p => false;<br />

}<br />

}<br />

Question: You have added a query interceptor to a data service that limits access to entities in a specific<br />

entity set to users who have the EntityAdministrator role. However, when you test the data service by<br />

issuing queries from a Web browser, you find that all attempts to query the entity set return no items. You<br />

know that there are matching entities, and you are running the Web browser by using an account that is a<br />

member of the EntityAdministrator role. What might be causing the problem?


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-73<br />

Additional Reading<br />

For more information about using query interceptors in a WCF <strong>Data</strong> Service, see the Interceptors (WCF<br />

<strong>Data</strong> Services) page at http://go.microsoft.com/fwlink/?LinkID=194104.


12-74 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Granting <strong>Access</strong> to Operations<br />

Key Points<br />

As is the case <strong>with</strong> entities collections, by default, a WCF <strong>Data</strong> Service does not provide access to any<br />

operations that it implements. You can grant access to operations in the InitializeService method when<br />

the data service starts running, by using the SetServiceOperation<strong>Access</strong>Rule method of the<br />

<strong>Data</strong>ServiceConfiguration parameter.<br />

The SetServiceOperation<strong>Access</strong>Rule method is similar to the SetEntitySet<strong>Access</strong>Rule method that you<br />

use to grant or deny access to entity collections. The method takes two parameters: a string that specifies<br />

the name of the operation to grant access to (or "*" to specify all operations), and a value from the<br />

System.<strong>Data</strong>.Services.ServiceOperationRights enumeration. This enumeration contains values that<br />

enable you to specify the access rights to grant over the operation.<br />

The following table summarizes the values in the ServiceOperationRights enumeration.<br />

Value Description<br />

None Denies authorization to access the service operation.<br />

ReadSingle Grants authorization to read a single data item by using the service<br />

operation.<br />

ReadMultiple Grants authorization to read multiple data items by using the service<br />

operation.<br />

AllRead Grants authorization to read single or multiple data items that the service<br />

operation deploys.<br />

All Grants all rights over the service operation.<br />

OverrideEntitySetRights Overrides entity set rights that are explicitly defined in the data service <strong>with</strong><br />

the service operation rights. Use this value in combination <strong>with</strong> other


Value Description<br />

ServiceOperationRights values.<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-75<br />

Note: There are no explicit privileges for writing data. However, a service operation can insert, update, or<br />

delete entities if the user who is performing the operation has the appropriate access rights to the<br />

underlying data (if the WCF <strong>Data</strong> Service is hosted by a Web site that uses impersonation), or the WCF<br />

<strong>Data</strong> Service runs using an account that has sufficient access rights (if the Web site does not use<br />

impersonation).<br />

If a service operation returns one or more entities from a specific entity set, you may also need to grant<br />

the appropriate access to that entity set by using the SetEntitySet<strong>Access</strong>Rights method. However, the<br />

OverrideEntitySetRights value enables you to override any entity set access rights. For example, if you<br />

grant EntitySetRights.ReadSingle access over an entity set, and a service operation retrieves a collection<br />

of entities from this entity set, the service operation will fail <strong>with</strong> an HTTP 403 (Forbidden) error when you<br />

attempt to run it. However, if you specify OverrideEntitySetRights when you grant access to the service<br />

operation, the access rule that is defined for the service operation will override the restriction that is<br />

applied to the entity set and the service operation will run successfully.<br />

The following code example shows how to grant access to the ProductsByColor service operation in the<br />

Product<strong>Data</strong>Service service. Note that the Products entity set has ReadSingle access, but the access<br />

rights that are granted to the ProductsByColor service operation override this limitation.<br />

[<strong>Visual</strong> Basic]<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

...<br />

config.SetEntitySet<strong>Access</strong>Rule("Products", EntitySetRights.ReadSingle)<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule("ProductsByColor", ServiceOperationRights.All<br />

Or ServiceOperationRights.OverrideEntitySetRights)<br />

...<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

...<br />

config.SetEntitySet<strong>Access</strong>Rule(<br />

"Products", EntitySetRights.ReadSingle);<br />

}<br />

...<br />

config.SetServiceOperation<strong>Access</strong>Rule(<br />

"ProductsByColor", ServiceOperationRights.All |<br />

ServiceOperationRights.OverrideEntitySetRights);<br />

...<br />

Note: You must grant some degree of access to the entity set that a service operation returns. If you do<br />

not call the SetEntitySet<strong>Access</strong>Rule method, or you specify a value of EntitySetRights.None, the<br />

ServiceOperationRights.OverrideEntitySetRights value has no effect and access to the underlying<br />

entity set is denied.


12-76 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Paging and Interceptors<br />

If you have defined a page size for an entity set by using the SetEntitySetPageSize method, this page<br />

size still applies to entity sets that a service operation returns, regardless of whether you have specified<br />

the ServiceOperationRights.OverrideEntitySetRights value when you granted access to the service<br />

operation.<br />

Query interceptors are not invoked when you run a service operation, so you cannot use them to limit<br />

access to entities that a service operation returns.<br />

Question: You have created a service operation called ProductsByName that returns all products that<br />

have a name that matches a value that is specified as a parameter. You only want users to be able to<br />

access products by using this service operation, so you have decided not to grant access to the underlying<br />

Products entity set. Instead, you have simply specified the<br />

ServiceOperationRights.OverrideEntitySetRights value when you called the<br />

SetServiceOperation<strong>Access</strong>Rule method to grant access to the ProductsByName service operation, as<br />

shown in the following code example. What is wrong <strong>with</strong> this approach?<br />

[<strong>Visual</strong> Basic]<br />

Public Shared Sub InitializeService(ByVal config As <strong>Data</strong>ServiceConfiguration)<br />

config.SetServiceOperation<strong>Access</strong>Rule("ProductsByName",<br />

ServiceOperationRights.OverrideEntitySetRights)<br />

End Sub<br />

[<strong>Visual</strong> C#]<br />

public static void InitializeService(<strong>Data</strong>ServiceConfiguration config)<br />

{<br />

config.SetServiceOperation<strong>Access</strong>Rule(<br />

"ProductsByName",<br />

ServiceOperationRights.OverrideEntitySetRights);<br />

}


Lab: Creating and Using WCF <strong>Data</strong> Services<br />

Objectives<br />

After completing this lab, you will be able to:<br />

• Expose data by using WCF <strong>Data</strong> Services.<br />

• Consume a WCF <strong>Data</strong> Service in a Web application.<br />

• Restrict access to the data that a WCF <strong>Data</strong> Service exposes.<br />

• Implement a business operation in a WCF <strong>Data</strong> Service.<br />

Introduction<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-77<br />

In this exercise, you will build a WCF <strong>Data</strong> Service based on an entity model that provides access to data<br />

that is stored in the AdventureWorks database. You will test this data service by performing queries by<br />

using Internet Explorer, and then you will consume the data service in a client application. You will protect<br />

the data that the data service exposes and restrict access to the data for certain types of users. Finally, you<br />

will add a service operation to the data service and invoke this operation from the client application.<br />

Lab Setup<br />

For this lab, you will use the available virtual machine environment. Before you begin the lab, you must:<br />

• Start the 10265A-GEN-DEV-12 virtual machine, and then log on by using the following credentials:<br />

• User name: Student<br />

• Password: Pa$$w0rd


12-78 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Lab Scenario<br />

Adventure Works Cycles subcontracts the shipping of its products to various delivery companies. These<br />

companies need access to the Adventure Works order data so that they can collect and deliver the goods.<br />

You need to expose this information by using WCF <strong>Data</strong> Services and building a Web application that the<br />

delivery companies can use to access the data.


Exercise 1: Exposing Order <strong>Data</strong> as a WCF <strong>Data</strong> Service<br />

Scenario<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-79<br />

In this exercise, you will build a simple WCF <strong>Data</strong> Service that exposes the SalesOrderHeader, Contact,<br />

and Address tables from the AdventureWorks database by using an entity data model. You will see how<br />

to access the service from a Web browser, experiment <strong>with</strong> operators such as $select, and see how to<br />

navigate through the data.<br />

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

The main tasks for this exercise are as follows:<br />

1. Prepare the environment for the lab.<br />

2. Prepare the AdventureWorks database for the lab.<br />

3. Open the starter project.<br />

4. Add a data service to the Shipping<strong>Data</strong>ServiceSite project.<br />

5. Build and test the data service.<br />

Task 1: Prepare the environment for the lab<br />

1. Log on to the 10265A-GEN-DEV-12 virtual machine as Student <strong>with</strong> the password Pa$$w0rd.<br />

2. In the E:\Labfiles folder, run EnvSetup.bat as an administrator.<br />

Task 2: Prepare the AdventureWorks database for the lab<br />

• In the E:\Labfiles folder, run AWReset.bat.<br />

Task 3: Open the starter project<br />

1. In the E:\Labfiles\Lab12\VB\Ex1\Starter or E:\Labfiles\Lab12\CS\Ex1\Starter folder, run ExSetup.bat as<br />

an administrator.<br />

2. Open <strong>Visual</strong> <strong>Studio</strong> 2010 as an administrator.<br />

3. Open the existing solution, Shipping<strong>Data</strong>ServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex1\Starter or<br />

E:\Labfiles\Lab12\CS\Ex1\Starter folder.<br />

Task 4: Add a data service to the Shipping<strong>Data</strong>ServiceSite project<br />

1. Add a WCF data service named Shipping<strong>Data</strong>Service to the Shipping<strong>Data</strong>ServiceSite project.<br />

2. In the Code Editor, add statements to bring the Shipping<strong>Data</strong>ServiceLibrary and<br />

System.Diagnostics namespaces into scope.<br />

3. Add AdventureWorksEntities as the type of the <strong>Data</strong>Service class:<br />

• Locate the comment TODO: replace [[class name]] <strong>with</strong> your data class name for <strong>Visual</strong> Basic<br />

or TODO: put your data source class name here for <strong>Visual</strong> C#, delete the comment, and then<br />

type AdventureWorksEntities<br />

• If you are using <strong>Visual</strong> C#, locate the comment TODO: put your data source class name here,<br />

delete the comment, and then type AdventureWorksEntities<br />

4. In the InitializeService method, write code that performs the following tasks:<br />

a. Set the access permissions for the SalesOrderHeaders, Addresses, and Contacts entity sets to<br />

AllRead.<br />

b. Set the page size for the three entity sets to 10.


12-80 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

5. Write code that logs service exceptions to the event log. Your code must perform the following tasks:<br />

a. Declare a private constant string named eventSource (_eventSource in <strong>Visual</strong> Basic) <strong>with</strong> a value<br />

of Orders Service.<br />

b. Declare a private constant string named eventLog (_eventLog in <strong>Visual</strong> Basic) <strong>with</strong> a value of<br />

Application.<br />

c. Define a private method called logException that takes an exception and a string as parameters,<br />

checks that the eventSource (_eventSource in <strong>Visual</strong> Basic) object exists, and then writes an<br />

entry to the event log.<br />

d. Override the HandleException method to call the logException method, and then call the<br />

HandleException method of the base class.<br />

6. Save the Shipping<strong>Data</strong>Service.svc file.<br />

Task 5: Build and test the data service<br />

1. Build the solution and correct any errors.<br />

2. Browse to the service by using Internet Explorer.<br />

3. Explore the service by using Internet Explorer:<br />

Note: You must right-click anywhere on the page, and then click View Source to view the data in the<br />

following steps. Internet Explorer cannot display the XML data that the service returns.<br />

a. In Internet Explorer, visit http://localhost/Shipping<strong>Data</strong>ServiceSite/Shipping<strong>Data</strong>Service.svc<br />

/Contacts. Review the contact data.<br />

b. In Internet Explorer, visit http://localhost/Shipping<strong>Data</strong>ServiceSite/Shipping<strong>Data</strong>Service.svc<br />

/Contacts(7). Review the contact data.<br />

c. In Internet Explorer, visit http://localhost/Shipping<strong>Data</strong>ServiceSite/Shipping<strong>Data</strong>Service.svc<br />

/Contacts?$filter=LastName gt 'D'. Review the contact data.<br />

4. Close Internet Explorer.<br />

5. Close all instances of Notepad.<br />

6. Close the solution.


Exercise 2: Consuming a WCF <strong>Data</strong> Service<br />

Scenario<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-81<br />

In this exercise, you will modify a simple Web client application to consume the data service and display<br />

the data that is retrieved. This application enables users to query all of the data in the entities that the<br />

WCF <strong>Data</strong> Service exposes.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Call the data service to retrieve SalesOrderHeader entities from a client Web application.<br />

3. Call the data service to retrieve the details of a specific SalesOrderHeader entity.<br />

4. Call the data service to retrieve the details of a specific Address entity.<br />

5. Call the data service to retrieve the details of a specific Contact entity.<br />

6. Build and test the client Web site.<br />

Task 1: Open the starter project<br />

1. In the E:\Labfiles\Lab12\VB\Ex2\Starter or E:\Labfiles\Lab12\CS\Ex2\Starter folder, run ExSetup.bat as<br />

an administrator.<br />

2. Open the existing solution, Shipping<strong>Data</strong>ServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex2\Starter or<br />

E:\Labfiles\Lab12\CS\Ex2\Starter folder.<br />

Task 2: Call the data service to retrieve SalesOrderHeader entities from a client Web<br />

application<br />

1. Review the task list.<br />

2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Retrieve all<br />

SalesOrderHeaders <strong>with</strong> the chosen shipMethodID in the task list. This task is located in the<br />

SalesOrders method.<br />

3. In the SalesOrders method, immediately after the comment, write code that performs the following<br />

tasks:<br />

a. Assign a new AdventureWorksEntities object to the context variable.<br />

b. Set the Credentials property of the context object to the current default network credentials.<br />

c. Define a LINQ query named orders that retrieves all of the SalesOrderHeader entities <strong>with</strong> a<br />

ShipMethodID property equal to the value of the shipMethodID variable.<br />

4. Save the HomeController file.<br />

Task 3: Call the data service to retrieve the details of a specific SalesOrderHeader entity<br />

1. Review the task list.<br />

2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the<br />

SalesOrderHeader <strong>with</strong> SalesOrderID = id in the task list. This task is located in the Details method.<br />

3. In the Details method, immediately after the comment, write code that uses the AddQueryOption<br />

method of the SalesOrderHeaders entity set to filter the entity set and return the<br />

SalesOrderHeader entity <strong>with</strong> a SalesOrderID property equal to the value of the id variable.<br />

4. Save the HomeController file.


12-82 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Task 4: Call the data service to retrieve the details of a specific Address entity<br />

1. Review the task list.<br />

2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the Address<br />

<strong>with</strong> AddressID = id in the task list. This task is located in the AddressDetails method.<br />

3. In the AddressDetails method, immediately after the comment, write code that uses the<br />

AddQueryOption method of the Addresses entity set to filter the entity set and return the Address<br />

entity <strong>with</strong> an AddressID property equal to the value of the id variable.<br />

4. Save the HomeController file.<br />

Task 5: Call the data service to retrieve the details of a specific Contact entity<br />

1. Review the task list.<br />

2. Open the HomeController file by double-clicking the comment TODO: Ex2 - Return the Contact<br />

<strong>with</strong> ContactID = id in the task list. This task is located in the ContactDetails method.<br />

3. In the ContactDetails method, immediately after the comment, write code that uses the<br />

AddQueryOption method of the Contacts entity set to filter the entity set and return the Contact<br />

entity <strong>with</strong> a ContactID property equal to the value of the id variable.<br />

4. Save the HomeController file.<br />

Task 6: Build and test the client Web site<br />

1. Build the solution and correct any errors.<br />

2. Check that the ShippingDetailsSite project is set as the StartUp project.<br />

3. Start the application in Debug mode.<br />

4. Explore the service by using Internet Explorer:<br />

a. On the Adventure Works Delivery Information page, in the Show Orders by Shipping Method<br />

box, type 1 and then click Submit.<br />

b. On the SalesOrders page, click any of the Details hyperlinks.<br />

c. On the Details page, click one of the Contact Details hyperlinks.<br />

5. Close Internet Explorer.<br />

6. Close the solution.


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-83<br />

Exercise 3: Restricting <strong>Access</strong> to <strong>Data</strong> That a WCF <strong>Data</strong> Service Exposes<br />

Scenario<br />

In this exercise, you will add a query interceptor so that only users <strong>with</strong> a specific security role can access<br />

sensitive data. You will also update the service to implement paging to reduce the number of items<br />

fetched by a request, and decrease the possibility of wasting bandwidth by performing unconstrained<br />

queries.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Modify the data service to filter returned data based on the user's role membership.<br />

3. Modify the Web client application to support data paging.<br />

4. Build and test the data service and the client Web site.<br />

Task 1: Open the starter project<br />

1. In the E:\Labfiles\Lab12\VB\Ex3\Starter or E:\Labfiles\Lab12\CS\Ex3\Starter folder, run ExSetup.bat as<br />

an administrator.<br />

2. Open the existing solution, Shipping<strong>Data</strong>ServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex3\Starter or<br />

E:\Labfiles\Lab12\CS\Ex3\Starter folder.<br />

Task 2: Modify the data service to filter returned data based on the user's role<br />

membership<br />

1. Review the task list.<br />

2. Open the Shipping<strong>Data</strong>Service.svc file by double-clicking the comment TODO: Ex3 - Filter the query<br />

results based on localgroup membership in the task list. This task is located in the<br />

OnQuerySalesOrderHeaders method.<br />

3. Notice the QueryInterceptor attribute of the OnQuerySalesOrderHeaders method.<br />

4. In the OnQuerySalesOrderHeaders method, immediately after the comment, write code that<br />

performs the following tasks:<br />

a. If the current user is in the WorldwideShipping role, allow the user to view all SalesOrderHeader<br />

entities <strong>with</strong> non-null ShipDate properties.<br />

b. If the current user is in the USShipping role, allow the user to view all SalesOrderHeader entities<br />

<strong>with</strong> non-null ShipDate properties and <strong>with</strong> the ShipMethodID property equal to 1, 2, or 4.<br />

c. Otherwise, return no SalesOrderHeader entities.<br />

5. Save the Shipping<strong>Data</strong>Service.svc file.<br />

Task 3: Modify the Web client application to support data paging<br />

1. Review the task list.<br />

2. Open the HomeController file by double-clicking the comment TODO: Ex3 - Declare a<br />

QueryOperationResponse object in the task list. This task is located in the SalesOrders method.<br />

3. In the SalesOrders method, immediately after the comment, write code that declares a variable<br />

based on the QueryOperationResponse generic type, called response. Specify SalesOrderHeader as<br />

the type parameter, and initialize it to null.<br />

4. Locate the next comment TODO: Ex3 - Get the first page of SalesOrderHeader records in the<br />

SalesOrders method.


12-84 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

5. In the SalesOrders method, immediately after the comment, write code that assigns the result of<br />

calling the Execute method on the SalesOrderHeaders entity set to the response variable.<br />

6. Locate the next comment TODO: Ex3 - Get the next page of SalesOrderHeader records in the<br />

SalesOrders method.<br />

7. In the SalesOrders method, immediately after the comment, write code that assigns the result of<br />

calling the Execute method on the SalesOrderHeader entity set to the response variable. Use the<br />

value of the next (_next in <strong>Visual</strong> Basic) variable to instantiate a Uri object to pass as a parameter to<br />

the Execute method.<br />

8. Save the HomeController file.<br />

Task 4: Build and test the data service and the client Web site<br />

1. Build the solution and correct any errors.<br />

2. Check that the ShippingDetailsSite project is set as the StartUp project.<br />

3. Start the application in Debug mode.<br />

4. Test the application by using the three sets of credentials in the following table.<br />

User name Password Role<br />

Bill Pa$$w0rd USShipping<br />

James Pa$$w0rd None<br />

Mary Pa$$w0rd WorldwideShipping<br />

5. Close Internet Explorer.<br />

6. Close the solution.


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-85<br />

Exercise 4: Implementing a Business Operation in a WCF <strong>Data</strong> Service<br />

Scenario<br />

In this exercise, you will add a business operation to archive orders that were shipped more than a year<br />

ago to another table called ArchivedSalesOrderHeader. You will restrict access to this operation to users<br />

who are in the WorldwideShipping security role.<br />

You will modify the Web client application to invoke this operation asynchronously.<br />

The main tasks for this exercise are as follows:<br />

1. Open the starter project.<br />

2. Add a business operation to archive records in the data service.<br />

3. Call a business operation in the data service from a client Web application.<br />

4. Build and test the client Web site.<br />

Task 1: Open the starter project<br />

1. In the E:\Labfiles\Lab12\VB\Ex4\Starter or E:\Labfiles\Lab12\CS\Ex4\Starter folder, run ExSetup.bat as<br />

an administrator.<br />

2. Open the existing solution, Shipping<strong>Data</strong>ServiceSite.sln, in the E:\Labfiles\Lab12\VB\Ex4\Starter or<br />

E:\Labfiles\Lab12\CS\Ex4\Starter folder.<br />

Task 2: Add a business operation to archive records in the data service<br />

1. Review the task list.<br />

2. Open the Shipping<strong>Data</strong>Service.svc file by double-clicking the comment TODO: Ex4 - Configure<br />

Service Operation in the task list. This task is located in the InitializeService method.<br />

3. In the InitializeService method, immediately after the comment, write code that calls the<br />

SetServiceOperation<strong>Access</strong>Rule method of the config object and gives full permissions to the<br />

ArchiveSalesOrders function.<br />

4. Open the Shipping<strong>Data</strong>Service.svc file by double-clicking the comment TODO: Ex4 - Define the<br />

ArchiveSalesOrders operation in the task list. This task is located in the ArchiveSalesOrders<br />

method.<br />

5. Write code that performs the following tasks:<br />

a. Check the role membership of the current user. If the user is not in the WorldwideShipping role,<br />

throw an Unauthorized<strong>Access</strong>Exception exception.<br />

b. Calculate the archive date. This should be 365 days ago.<br />

c. Define a LINQ query that selects all of the SalesOrderHeader entities that are older than 365<br />

days and have a Status property equal to 8.<br />

d. For each of the SalesOrderHeader entities that the query selects, set the Status property to 8,<br />

and then create a new ArchivedSalesOrderHeader entity.<br />

e. Save the changes.<br />

6. Save the Shipping<strong>Data</strong>Service.svc file.<br />

Task 3: Call a business operation in the data service from a client Web application<br />

1. Review the task list.<br />

2. Open the HomeController file by double-clicking the comment TODO: Ex4 - Define the<br />

ArchiveOrders ActionResult in the task list. This task is located in the ArchiveOrders method.


12-86 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

3. Write code that performs the following tasks:<br />

a. Create a new AsyncCallback object for the ArchiveCompleteCallback method.<br />

b. Create a new AdventureWorksEntities context object and assign the current network<br />

credentials to the Credentials property.<br />

c. Invoke the business operation asynchronously by using the static archiveUrl property of the<br />

Constants class.<br />

d. Handle any <strong>Data</strong>ServiceQueryException exceptions by throwing a new ApplicationException<br />

exception.<br />

e. Return an ActionResult object by calling the Content method <strong>with</strong> a message Archive Complete<br />

as a parameter.<br />

Note: ASP.NET Model-View-Controller (MVC) 1.0 does not directly support asynchronous action methods.<br />

Therefore, the code should use a blocking call to the EndExecute method instead of using the<br />

ArchiveCompleteCallBack method. ASP.NET MVC 2 will enable you to write real asynchronous action<br />

methods.<br />

4. Save the HomeController file.<br />

Task 4: Build and test the client Web site<br />

1. Build the solution and correct any errors.<br />

2. Check that the ShippingDetailsSite project is set as the StartUp project.<br />

3. Start the application in Debug mode.<br />

4. Explore the service by using Internet Explorer:<br />

a. On the Adventure Works Delivery Information page, click ArchiveShipping Orders.<br />

b. Wait until the Archive Complete message appears in the browser.<br />

5. Close Internet Explorer.<br />

6. Close the solution.


Lab Review<br />

Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-87<br />

Review Questions<br />

1. What is the base type that provides the functionality that you can use to implement a WCF <strong>Data</strong><br />

Service?<br />

2. In a WCF <strong>Data</strong> Service that implements transport-level security, how do you programmatically pass<br />

the credentials of a user who is running a client application to the service?<br />

3. How can you restrict the data that is visible to users of a data service based on their credentials?<br />

4. What attribute do you use to specify that a service operation retrieves data?


12-88 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010<br />

Module Review and Takeaways<br />

Review Questions<br />

1. You want to host a WCF <strong>Data</strong> Service in a custom application rather than by using IIS. Which type can<br />

you use to provide the hosting functionality in your application?<br />

2. What is the difference between the $select and $filter query options when you retrieve data from a<br />

WCF <strong>Data</strong> Service?<br />

3. If a WCF <strong>Data</strong> Service implements paging, how do you programmatically retrieve all of the pages of<br />

data for an entity set in a client application?<br />

Best Practices Related to Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services<br />

Supplement or modify the following best practices for your own work situations:<br />

• Protect a WCF <strong>Data</strong> Service by configuring transport-level security.<br />

• Use query interceptors to restrict access to entities to the users and roles that need to use those<br />

entities.<br />

• In a service operation, always check that the user or role that is requesting the operation has the<br />

appropriate privileges.<br />

• Configure appropriate page sizes for each entity in a WCF <strong>Data</strong> Service to prevent user requests from<br />

accidentally flooding the network <strong>with</strong> data, and to reduce the possibility of denial-of-service attacks<br />

by applications that deliberately request large volumes of data.<br />

• Limit use of the resource-intensive operations in a WCF <strong>Data</strong> Service. Configure the<br />

<strong>Data</strong>ServiceConfiguration object to restrict use of query options such as $expand, $count, and<br />

$inlinecount.<br />

• Avoid enabling eager loading for large sets of related data.<br />

• Avoid using the "*" wildcard character as the parameter to the SetEntitySet<strong>Access</strong>Rule and<br />

SetServiceOperation<strong>Access</strong>Rule methods. Enable access to each entity and operation explicitly.


Querying <strong>Data</strong> by Using WCF <strong>Data</strong> Services 12-89<br />

• In a client application that consumes a WCF <strong>Data</strong> Service, always be prepared to handle<br />

<strong>Data</strong>ServiceClientException, <strong>Data</strong>ServiceQueryException, and <strong>Data</strong>ServiceRequestException<br />

exceptions.<br />

• In a client application that calls service operations in a WCF <strong>Data</strong> Service, invoke these operations<br />

asynchronously.


12-90 <strong>Developing</strong> <strong>Data</strong> <strong>Access</strong> <strong>Solutions</strong> <strong>with</strong> <strong>Microsoft®</strong> <strong>Visual</strong> <strong>Studio</strong>® 2010

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!