OFFICIAL
MICROSOFT
LEARNING
PRODUCT
10265A Developing Data Access Solutions with Microsoft Visual Studio 2010 ®
®
Be sure to access the extended learning content on your Course Companion CD enclosed on the back cover of the book.
ii
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. The names of manufacturers, products, or URLs are provided for informational purposes only and Microsoft makes no representations and warranties, either expressed, implied, or statutory, regarding these manufacturers or the use of the products with any Microsoft technologies. The inclusion of a manufacturer or product does not imply endorsement of Microsoft of the manufacturer or product. Links may be provided to third party sites. Such sites are not under the control of Microsoft and Microsoft is not responsible for the contents of any linked site or any link contained in a linked site, or any changes or updates to such sites. Microsoft is not responsible for webcasting or any other form of transmission received from any linked site. Microsoft is providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement of Microsoft of the site or the products contained therein. © 2010 Microsoft Corporation. All rights reserved. Microsoft, Excel, Hyper-V, IntelliSense, Internet Explorer, MSDN, SharePoint, Silverlight, SQL Server, Visual Basic, Visual C#, Visual Studio, Windows, Windows Azure, and Windows Server are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. All other trademarks are property of their respective owners.
Product Number: 10265A Part Number: 06380 Released: 10/2010
MICROSOFT LICENSE TERMS OFFICIAL MICROSOFT LEARNING PRODUCTS - TRAINER EDITION – Pre-Release and Final Release Versions These license terms are an agreement between Microsoft Corporation and you. Please read them. They apply to the Licensed Content named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft •
updates,
•
supplements,
•
Internet-based services, and
•
support services
for this Licensed Content, unless other terms accompany those items. If so, those terms apply. By using the Licensed Content, you accept these terms. If you do not accept them, do not use the Licensed Content. If you comply with these license terms, you have the rights below.
1. DEFINITIONS. a. “Academic Materials” means the printed or electronic documentation such as manuals,
workbooks, white papers, press releases, datasheets, and FAQs which may be included in the Licensed Content.
b. “Authorized Learning Center(s)” means a Microsoft Certified Partner for Learning Solutions
location, an IT Academy location, or such other entity as Microsoft may designate from time to time.
c. “Authorized Training Session(s)” means those training sessions authorized by Microsoft and
conducted at or through Authorized Learning Centers by a Trainer providing training to Students solely on Official Microsoft Learning Products (formerly known as Microsoft Official Curriculum or “MOC”) and Microsoft Dynamics Learning Products (formerly know as Microsoft Business Solutions Courseware). Each Authorized Training Session will provide training on the subject matter of one (1) Course.
d. “Course” means one of the courses using Licensed Content offered by an Authorized Learning Center during an Authorized Training Session, each of which provides training on a particular Microsoft technology subject matter.
e. “Device(s)” means a single computer, device, workstation, terminal, or other digital electronic or analog device.
f.
“Licensed Content” means the materials accompanying these license terms. The Licensed Content may include, but is not limited to, the following elements: (i) Trainer Content, (ii) Student Content, (iii) classroom setup guide, and (iv) Software. There are different and separate components of the Licensed Content for each Course.
g.
“Software” means the Virtual Machines and Virtual Hard Disks, or other software applications that may be included with the Licensed Content.
h. “Student(s)” means a student duly enrolled for an Authorized Training Session at your location.
i.
“Student Content” means the learning materials accompanying these license terms that are for use by Students and Trainers during an Authorized Training Session. Student Content may include labs, simulations, and courseware files for a Course.
j.
“Trainer(s)” means a) a person who is duly certified by Microsoft as a Microsoft Certified Trainer and b) such other individual as authorized in writing by Microsoft and has been engaged by an Authorized Learning Center to teach or instruct an Authorized Training Session to Students on its behalf.
k. “Trainer Content” means the materials accompanying these license terms that are for use by
Trainers and Students, as applicable, solely during an Authorized Training Session. Trainer Content may include Virtual Machines, Virtual Hard Disks, Microsoft PowerPoint files, instructor notes, and demonstration guides and script files for a Course.
l.
“Virtual Hard Disks” means Microsoft Software that is comprised of virtualized hard disks (such as a base virtual hard disk or differencing disks) for a Virtual Machine that can be loaded onto a single computer or other device in order to allow end-users to run multiple operating systems concurrently. For the purposes of these license terms, Virtual Hard Disks will be considered “Trainer Content”.
m. “Virtual Machine” means a virtualized computing experience, created and accessed using
Microsoft® Virtual PC or 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 of these license terms, Virtual Hard Disks will be considered “Trainer Content”.
n.
“you” means the Authorized Learning Center or Trainer, as applicable, that has agreed to these license terms.
2. OVERVIEW. Licensed Content. The Licensed Content includes Software, Academic Materials (online and electronic), Trainer Content, Student Content, classroom setup guide, and associated media. License Model. The Licensed Content is licensed on a per copy per Authorized Learning Center location or per Trainer basis.
3. INSTALLATION AND USE RIGHTS. a. Authorized Learning Centers and Trainers: For each Authorized Training Session, you may: i.
either install individual copies of the relevant Licensed Content on classroom Devices only for use by Students enrolled in and the Trainer delivering the Authorized Training Session, provided that the number of copies in use does not exceed the number of Students enrolled in and the Trainer delivering the Authorized Training Session, OR
ii. install one copy of the relevant Licensed Content on a network server only for access by classroom Devices and only for use by Students enrolled in and the Trainer delivering the Authorized Training Session, provided that the number of Devices accessing the Licensed Content on such server does not exceed the number of Students enrolled in and the Trainer delivering the Authorized Training Session. iii. and allow the Students enrolled in and the Trainer delivering the Authorized Training Session to use the Licensed Content that you install in accordance with (ii) or (ii) above during such Authorized Training Session in accordance with these license terms.
i.
Separation of Components. The components of the Licensed Content are licensed as a single unit. You may not separate the components and install them on different Devices.
ii. Third Party Programs. The Licensed Content may contain third party programs. These license terms will apply to the use of those third party programs, unless other terms accompany those programs.
b. Trainers: i.
Trainers may Use the Licensed Content that you install or that is installed by an Authorized Learning Center on a classroom Device to deliver an Authorized Training Session.
ii. Trainers may also Use a copy of the Licensed Content as follows:
A. Licensed Device. The licensed Device is the Device on which you Use the Licensed Content. You may install and Use one copy of the Licensed Content on the licensed Device solely for your own personal training Use and for preparation of an Authorized Training Session.
B. Portable Device. You may install another copy on a portable device solely for your own personal training Use and for preparation of an Authorized Training Session.
4. PRE-RELEASE VERSIONS. If this is a pre-release (“beta”) version, in addition to the other provisions in this agreement, these terms also apply:
a. Pre-Release Licensed Content. This Licensed Content is a pre-release version. It may not
contain the same information and/or work the way a final version of the Licensed Content will. We may change it for the final, commercial version. We also may not release a commercial version. You will clearly and conspicuously inform any Students who participate in each Authorized Training Session of the foregoing; and, that you or Microsoft are under no obligation to provide them with any further content, including but not limited to the final released version of the Licensed Content for the Course.
b. Feedback. If you agree to give feedback about the Licensed Content to Microsoft, you give to
Microsoft, without charge, the right to use, share and commercialize your feedback in any way and for any purpose. You also give to third parties, without charge, any patent rights needed for their products, technologies and services to use or interface with any specific parts of a Microsoft software, Licensed Content, or service that includes the feedback. You will not give feedback that is subject to a license that requires Microsoft to license its software or documentation to third parties because we include your feedback in them. These rights survive this agreement.
c. Confidential Information. The Licensed Content, including any viewer, user interface, features
and documentation that may be included with 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 may not disclose confidential information to third parties. You may disclose confidential information only to your employees and consultants who need to know the information. You must have written agreements with them that protect the confidential information at least as much as this agreement.
ii.
Survival. Your duty to protect confidential information survives this agreement.
iii. Exclusions. You may disclose confidential information in response to a judicial or governmental order. You must first give written notice to Microsoft to allow it to seek a
protective order or otherwise protect the information. Confidential information does not include information that •
becomes publicly known through no wrongful act;
•
you received from a third party who did not breach confidentiality obligations to Microsoft or its suppliers; or
•
you developed independently.
d.
Term. The term of this agreement for pre-release versions is (i) the date which Microsoft informs you is the end date for using the beta version, or (ii) the commercial release of the final release version of the Licensed Content, whichever is first (“beta term”).
e.
Use. You will cease using all copies of the beta version upon expiration or termination of the beta term, and will destroy all copies of same in the possession or under your control and/or in the possession or under the control of any Trainers who have received copies of the pre-released version.
f.
Copies. Microsoft will inform Authorized Learning Centers if they may make copies of the beta version (in either print and/or CD version) and distribute such copies to Students and/or Trainers. If Microsoft allows such distribution, you will follow any additional terms that Microsoft provides to you for such copies and distribution.
5. ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS. a. Authorized Learning Centers and Trainers: i.
Software.
ii. Virtual Hard Disks. The Licensed Content may contain versions of Microsoft XP, Microsoft Windows Vista, Windows Server 2003, Windows Server 2008, and Windows 2000 Advanced Server and/or other Microsoft products which are provided in Virtual Hard Disks. A. If the Virtual Hard Disks and the labs are launched through the Microsoft Learning Lab Launcher, then these terms apply: Time-Sensitive Software. If the Software is not reset, it will stop running based upon the time indicated on the install of the Virtual Machines (between 30 and 500 days after you install it). You will not receive notice before it stops running. You may not be able to access data used or information saved with the Virtual Machines when it stops running and may be forced to reset these Virtual Machines to their original state. You must remove the Software from the Devices at the end of each Authorized Training Session and reinstall and launch it prior to the beginning of the next Authorized Training Session. B. If the Virtual Hard Disks require a product key to launch, then these terms apply: Microsoft will deactivate the operating system associated with each Virtual Hard Disk. Before installing any Virtual Hard Disks on classroom Devices for use during an Authorized Training Session, you will obtain from Microsoft a product key for the operating system software for the Virtual Hard Disks and will activate such Software with Microsoft using such product key. C. These terms apply to all Virtual Machines and Virtual Hard Disks:
You may only use the Virtual Machines and Virtual Hard Disks if you comply with the terms and conditions of this agreement and the following security requirements: o
You may not install Virtual Machines and Virtual Hard Disks on portable Devices or Devices that are accessible to other networks.
o
You must remove Virtual Machines and Virtual Hard Disks from all classroom Devices at the end of each Authorized Training Session, except those held at Microsoft Certified Partners for Learning Solutions locations.
o
You must remove the differencing drive portions of the Virtual Hard Disks from all classroom Devices at the end of each Authorized Training Session at Microsoft Certified Partners for Learning Solutions locations.
o
You will ensure that the Virtual Machines and Virtual Hard Disks are not copied or downloaded from Devices on which you installed them.
o
You will strictly comply with all Microsoft instructions relating to installation, use, activation and deactivation, and security of Virtual Machines and Virtual Hard Disks.
o
You may not modify the Virtual Machines and Virtual Hard Disks or any contents thereof.
o
You may not reproduce or redistribute the Virtual Machines or Virtual Hard Disks.
ii. Classroom Setup Guide. You will assure any Licensed Content installed for use during an Authorized Training Session will be done in accordance with the classroom set-up guide for the Course. iii. Media Elements and Templates. You may allow Trainers and Students to use images, clip art, animations, sounds, music, shapes, video clips and templates provided with the Licensed Content solely in an Authorized Training Session. If Trainers have their own copy of the Licensed Content, they may use Media Elements for their personal training use. iv. iv Evaluation Software. Any Software that is included in the Student Content designated as “Evaluation Software” may be used by Students solely for their personal training outside of the Authorized Training Session.
b. Trainers Only: i.
Use of PowerPoint Slide Deck Templates. The Trainer Content may include Microsoft PowerPoint slide decks. Trainers may use, copy and modify the PowerPoint slide decks only for providing an Authorized Training Session. If you elect to exercise the foregoing, you will agree or ensure Trainer agrees: (a) that modification of the slide decks will not constitute creation of obscene or scandalous works, as defined by federal law at the time the work is created; and (b) to comply with all other terms and conditions of this agreement.
ii. Use of Instructional Components in Trainer Content. For each Authorized Training Session, Trainers may customize and reproduce, in accordance with the MCT Agreement, those portions of the Licensed Content that are logically associated with instruction of the Authorized Training Session. If you elect to exercise the foregoing rights, you agree or ensure the Trainer agrees: (a) that any of these customizations or reproductions will only be used for providing an Authorized Training Session and (b) to comply with all other terms and conditions of this agreement.
iii. Academic Materials. If the Licensed Content contains Academic Materials, you may copy and use the Academic Materials. You may not make any modifications to the Academic Materials and you may not print any book (either electronic or print version) in its entirety. If you reproduce any Academic Materials, you agree that:
•
The use of the Academic Materials will be only for your personal reference or training use
•
You will not republish or post the Academic Materials on any network computer or broadcast in any media;
•
You will include the Academic Material’s original copyright notice, or a copyright notice to Microsoft’s benefit in the format provided below: Form of Notice: © 2010 Reprinted for personal reference use only with permission by Microsoft Corporation. All rights reserved. Microsoft, Windows, and Windows Server are either registered trademarks or trademarks of Microsoft Corporation in the US and/or other countries. Other product and company names mentioned herein may be the trademarks of their respective owners.
6. INTERNET-BASED SERVICES. Microsoft may provide Internet-based services with the Licensed
Content. It may change 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 of them. You may not use the services to try to gain unauthorized access to any service, data, account or network by any means.
7. SCOPE OF LICENSE. The Licensed Content is licensed, not sold. This agreement only gives you some
rights to use the Licensed Content. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the Licensed Content only as expressly permitted in this agreement. In doing so, you must comply with any 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 Authorized Training Session;
•
allow more classroom Devices to access the server than the number of Students enrolled in and the Trainer delivering the Authorized Training Session if the Licensed Content is installed on a network server;
•
copy or reproduce the Licensed Content to any server or location for further reproduction or distribution;
•
disclose the results of any benchmark tests of the Licensed Content to any third party without Microsoft’s prior written approval;
•
work around any technical limitations in the Licensed Content;
•
reverse engineer, decompile or disassemble the Licensed Content, except and only to the extent that applicable law expressly permits, despite this limitation;
•
make more copies of the Licensed Content than specified in this agreement or allowed by applicable law, despite this limitation;
•
publish the Licensed Content for others to copy;
•
transfer the Licensed Content, in whole or in part, to a third party;
•
access or use any Licensed Content for which you (i) are not providing a Course and/or (ii) have not been authorized by Microsoft to access and use;
•
rent, lease or lend the Licensed Content; or
•
use the Licensed Content for commercial hosting services or general business purposes.
•
Rights to access the server software that may be included with the Licensed Content, including the Virtual Hard Disks does not give you any right to implement Microsoft patents or other Microsoft intellectual property in software or devices that may access the server.
8. EXPORT RESTRICTIONS. The Licensed Content is subject to United States export laws and
regulations. You must comply with all domestic and international export laws and regulations that apply to the Licensed Content. These laws include restrictions on destinations, end users and end use. For additional information, see www.microsoft.com/exporting.
9. NOT FOR RESALE SOFTWARE/LICENSED CONTENT. You may not sell software or Licensed Content marked as “NFR” or “Not for Resale.”
10. ACADEMIC EDITION. You must be a “Qualified Educational User” to use Licensed Content marked as “Academic Edition” or “AE.” If you do not know whether you are a Qualified Educational User, visit www.microsoft.com/education or contact the Microsoft affiliate serving your country.
11. TERMINATION. Without prejudice to any other rights, Microsoft may terminate this agreement if you fail to comply with the terms and conditions of these license terms. In the event your status as an Authorized Learning Center or Trainer a) expires, b) is voluntarily terminated by you, and/or c) is terminated by Microsoft, this agreement shall automatically terminate. Upon any termination of this agreement, you must destroy all copies of the Licensed Content and all of its component parts.
12. ENTIRE AGREEMENT. This agreement, and the terms for supplements, updates, Internet-
based services and support services that you use, are the entire agreement for the Licensed Content and support services.
13. APPLICABLE LAW. a. United States. If you acquired the Licensed Content in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.
b. Outside the United States. If you acquired the Licensed Content in any other country, the laws of that country apply.
14. LEGAL EFFECT. This agreement describes certain legal rights. You may have other rights under the
laws of your country. You may also have rights with respect to the party from whom you acquired the Licensed Content. This agreement does 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. Microsoft gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this agreement cannot change. To the extent permitted under your local laws, Microsoft excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
16. LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES. This limitation applies to •
anything related to the Licensed Content, software, services, content (including code) on third party Internet sites, or third party programs; and
•
claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.
It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages. Please note: As this Licensed Content is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French. Remarque : Ce le contenu sous licence étant distribué au Québec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en français. EXONÉRATION DE GARANTIE. Le contenu sous licence visé par une licence est offert « tel quel ». Toute utilisation de ce contenu sous licence est à votre seule risque et péril. Microsoft n’accorde aucune autre garantie expresse. Vous pouvez bénéficier de droits additionnels en vertu du droit local sur la protection dues consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, d’adéquation à un usage particulier et d’absence de contrefaçon sont exclues. LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES. Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices. Cette limitation concerne: •
tout ce qui est relié au le contenu sous licence , aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et
•
les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, de négligence ou d’une autre faute dans la limite autorisée par la loi en vigueur.
Elle s’applique également, même si Microsoft connaissait ou devrait connaître l’éventualité d’un tel dommage. Si votre pays n’autorise pas l’exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion ci-dessus ne s’appliquera pas à votre égard. EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres droits prévus par les lois 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 permettent pas.
Welcome! Thank you for taking our training! We’ve worked together with our Microsoft Certified Partners for Learning Solutions and our Microsoft IT Academies to bring you a world-class learning experience—whether you’re a professional looking to advance your skills or a student preparing for a career in IT. n
Microsoft Certified Trainers and Instructors—Your instructor is a technical and instructional expert who meets ongoing certification requirements. And, if instructors are delivering training at one of our Certified Partners for Learning Solutions, they are also evaluated throughout the year by students and by Microsoft.
n
Certification Exam Benefits—After training, consider taking a Microsoft Certification exam. Microsoft Certifications validate your skills on Microsoft technologies and can help differentiate you when finding a job or boosting your career. In fact, independent research by IDC concluded that 75% of managers believe certifications are important to team performance1. Ask your instructor about Microsoft Certification exam promotions and discounts that may be available to you.
n Customer Satisfaction Guarantee—Our Certified Partners for Learning Solutions offer a satisfaction guarantee and we hold them accountable for it. At the end of class, please complete an evaluation of today’s experience. We value your feedback!
We wish you a great learning experience and ongoing success in your career!
Sincerely, Microsoft Learning www.microsoft.com/learning
1
IDC, Value of Certification: Team Certification and Organizational Performance, November 2006
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
iii
Acknowledgement Microsoft Learning would like to acknowledge and thank the following for their contribution towards developing this title. Their effort at various stages in the development has ensured that you have a good classroom experience.
Lin Joyner – Content Developer Lin is a subject matter expert, technical writer, and course designer with significant experience of creating training content for Microsoft® Visual Studio® and Microsoft SQL Server®. She has worked with Visual Studio and SQL Server since version 6.0 of both products. Lin has designed and written training courses, labs, and elearning materials about SQL Server, .NET development, and development of the Microsoft Office system, often taking the lead content developer role. Before she joined Content Master, Lin was a professional trainer for five years when she held the MCT and MCSD certifications.
Dominic Betts – Content Developer Dominic currently works for the Distributed Systems Development Team at Content Master as a subject matter expert, technical writer, and course designer specializing in Microsoft technologies such as ASP.NET, F#, and Windows® Azure™. Dominic’s recent projects have included a Visual Studio 2008 Extensibility Training Development Kit (TDK), Windows Mobile® Quickstart and Readiness solutions, and advising the Windows Azure team on training content. Dominic has created and taught courses that cover a range of topics such as ASP.NET, .NET threading, and Enterprise Java. Dominic has also written papers and created technical content for the MSDN® Web site and elsewhere. He was the UK's IT Trainer of the Year in 2003.
iv
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Contents Module 1: Introduction to Data Access Technologies Lesson 1: Data Access Technologies Lesson 2: Data Access Scenarios Lab: Analyzing Data Access Scenarios
1-3 1-14 1-19
Module 2: Building Entity Data Models Lesson 1: Introduction to Entity Data Models Lesson 2: Modifying an Entity Data Model Lesson 3: Customizing an Entity Data Model Lab: Using Entity Data Models
2-3 2-18 2-29 2-43
Module 3: Querying Entity Data Lesson 1: Retrieving Data by Using LINQ to Entities Lesson 2: Retrieving Data by Using Entity SQL Lesson 3: Retrieving Data by Using the EntityClient Provider Lesson 4: Retrieving Data by Using Stored Procedures Lesson 5: Unit Testing Your Data Access Code Lab: Querying Entity Data
3-4 3-19 3-26 3-38 3-43 3-51
Module 4: Creating, Updating, and Deleting Entity Data Lesson 1: Understanding Change Tracking in the Entity Framework Lesson 2: Modifying Data in an Entity Data Model Lab: Creating, Updating, and Deleting Entity Data
4-3 4-12 4-28
Module 5: Handling Multi-User Scenarios by Using Object Services Lesson 1: Handling Concurrency in the Entity Framework Lesson 2: Transactional Support in the Entity Framework Lab: Handling Multi-User Scenarios by Using Object Services
5-3 5-19 5-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
v
Module 6: Building Optimized Solutions by Using Object Services Lesson 1: The Stages of Query Execution Lesson 2: Change Tracking and Object Materialization Lesson 3: Using Compiled Queries Lesson 4: Using Design-Time Generated Entity Framework Views Lesson 5: Monitoring Performance Lesson 6: Performing Asynchronous Data Modifications Lab: Building Optimized Solutions by Using Object Services
6-3 6-9 6-18 6-24 6-29 6-37 6-44
Module 7: Customizing Entities and Building Custom Entity Class Lesson 1: Overriding Generated Classes Lesson 2: Using Templates to Customize Entities Lesson 3: Creating and Using Custom Entity Classes Lab: Customizing Entities and Building Custom Entity Classes
7-3 7-17 7-34 7-47
Module 8: Using POCO Classes with the Entity Framework Lesson 1: Requirements for POCO Classes Lesson 2: POCO Classes and Lazy Loading Lesson 3: POCO Classes and Change Tracking Lesson 4: Extending Entity Types Lab: Using POCO Classes with the Entity Framework
8-3 8-12 8-18 8-23 8-29
Module 9: Building an N-Tier Solution by Using the Entity Framework Lesson 1: Designing an N-Tier Solution Lesson 2: Defining Operations and Implementing Data Transport Structures Lesson 3: Protecting Data and Operations Lab: Building an N-Tier Solution by Using the Entity Framework
9-4 9-12 9-32 9-40
vi
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module 10: Handling Updates in an N-Tier Solution by Using the Entity Framework Lesson 1: Tracking Entities and Persisting Changes Lesson 2: Managing Exceptions in an N-Tier Solution Lab: Handling Updates in an N-Tier Solution by Using the Entity Framework
10-3 10-42 10-54
Module 11: Building Occasionally Connected Solutions Lesson 1: Offline Data Caching by Using XML Lesson 2: Using the Sync Framework Lab: Building Occasionally Connected Solutions
11-3 11-22 11-46
Module 12: Querying Data by Using WCF Data Services Lesson 1: Introducing WCF Data Services Lesson 2: Creating a WCF Data Service Lesson 3: Consuming a WCF Data Service Lesson 4: Protecting Data and Operations in a WCF Data Service Lab: Creating and Using WCF Data Services
12-3 12-16 12-53 12-84 12-99
Module 13: Updating Data by Using WCF Data Services Lesson 1: Creating, Updating, and Deleting Data in a WCF Data Service 13-3 Lesson 2: Preventing Unauthorized Updates and Improving Performance 13-22 Lesson 3: Using WCF Data Services with Nonrelational Data 13-30 Lab: Updating Data by Using WCF Data Services 13-42
Module 14: Using ADO.NET Lesson 1: Retrieving and Modifying Data by Using ADO.NET Commands Lesson 2: Retrieving and Modifying Data by Using DataSets Lesson 3: Managing Transactions and Concurrency in Multi User Scenarios Lab: Using ADO.NET
14-4 14-34 14-58 14-72
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
vii
Module 15: Using LINQ to SQL Lesson 1: Implementing a Logical Data Model by Using LINQ to SQL Lesson 2: Managing Performance and Handling Concurrency Lab: Using LINQ to SQL
15-4 15-51 15-81
Appendix: Lab Answer Keys Module 1 Lab: Analyzing Data Access Scenarios Module 2 Lab: Using Entity Data Models Module 3 Lab: Querying Entity Data Module 4 Lab: Creating, Updating, and Deleting Entity Data Module 5 Lab: Handling Multi-User Scenarios by Using Object Services Module 6 Lab: Building Optimized Solutions by Using Object Services Module 7 Lab: Customizing Entities and Building Custom Entity Classes Module 8 Lab: Using POCO Classes with the Entity Framework Module 9 Lab: Building an N-Tier Solution by Using the Entity Framework Module 10 Lab: Handling Updates in an N-Tier Solution by Using the Entity Framework Module 11 Lab: Building Occasionally Connected Solutions Module 12 Lab: Creating and Using WCF Data Services Module 13 Lab: Updating Data by Using WCF Data Services Module 14 Lab: Using ADO.NET Module 15 Lab: Using LINQ to SQL
L1-1 L2-1 L3-1 L4-1 L5-1 L6-1 L7-1 L8-1 L9-1 L10-1 L11-1 L12-1 L13-1 L14-1 L15-1
About This Course
i
About This Course This section provides you with a brief description of the course, audience, suggested prerequisites, and course objectives.
Course Description This course teaches you how to design and develop data access in your applications. The course discusses how to choose which data access technology is appropriate for your business and application needs and then teaches you how to use the key technologies, including the ADO.NET Entity Framework, LanguageIntegrated Query (LINQ), Windows® Communication Foundation (WCF) Data Services, Microsoft® Sync Framework, and ADO.NET.
Audience This course is intended for professional .NET software developers who use Microsoft Visual Studio® in a team-based, medium-sized to large development environment. Audience members are expected to have experience of implementing data access and data binding within their Web or Windows Client applications and are interested in learning to optimize data access within their applications by using the Entity Framework, LINQ, and ADO.NET. Audience members should be experienced users of Visual Studio 2008 Service Pack 1 (SP1) or newer releases of the Visual Studio product. They should also have some experience of using Visual Studio 2010 for either Windows Client or Web application development.
Student Prerequisites This course requires that you meet the following prerequisites: •
An understanding of the problem-solving techniques that apply to software development, including modern software development models, the software development life cycle, the concepts of event-driven and object-oriented programming, creating use-case diagrams, designing and building a user interface, and developing a structured application.
•
A basic understanding of Web, macro, and Windows scripting techniques and some hands-on experience of writing scripts.
•
A general understanding of the purpose, function, and features of the common language runtime (CLR), the Microsoft .NET Framework class libraries, the common type system, component interoperation, cross-language interoperation, application domains, and runtime hosts that the .NET Framework supports.
ii
About This Course
•
Experience of using Visual Studio 2008 to use variables, operators, and branching and looping statements; create and use classes, methods, and events; identify syntax and logic errors; and access data from a data source.
•
Experience in object-oriented design and development including creating and accessing classes and class properties; creating and accessing methods and overloaded methods; implementing inheritance, base classes, and abstract classes; declaring, raising, and handling events; responding to and throwing exceptions; implementing interfaces and polymorphism; implementing shared and static members; implementing generics; and creating components and class libraries.
•
Experience in n-tier application design and development, including managing a software development process; controlling input at the user interface level in Windows Client and Web applications; debugging, tracing, and profiling .NET applications; monitoring and logging .NET applications; implementing basic testing best practices; performing basic data access tasks by using LINQ; implementing basic security best practices in .NET applications; implementing basic service calls; using .NET configuration files; and deploying .NET Framework applications by using ClickOnce and the Microsoft Installer.
•
Data access experience in Windows Client application development, including connecting to a data source, implementing data binding, and implementing data validation at the user interface layer.
•
Data access experience in Web application development, including connecting to a data source, implementing dynamic data, and implementing data validation at the user interface layer.
Course Objectives After completing this course, students will be able to: •
Evaluate business cases and select an appropriate combination of data access technologies and tools for each case.
•
Use the tools that the Entity Framework provides to map the conceptual model that the business logic of an application uses to the logical data model that the database provides.
•
Query an Entity Data Model (EDM) by using common methods such as LINQ to Entities, Entity SQL, and the classes in the EntityClient namespace.
•
Perform data modification tasks through an EDM by using LINQ to Entities, Entity SQL, and the classes in the EntityClient namespace.
About This Course
iii
•
Describe the optimistic concurrency model in the Entity Framework and manage transactions in Entity Framework applications.
•
Describe the best practices for designing and building a scalable, optimized data access layer by using Object Services.
•
Customize and extend entities with their own business logic and use advanced mappings to shape the data model to their business and application requirements.
•
Reuse existing plain-old CLR object (POCO) business classes in a data access layer that is built by using the Entity Framework.
•
Address the architectural issues that can arise when building an n-tier enterprise application by using the Entity Framework.
•
Build extensible solutions that can update data in an n-tier enterprise application by using the Entity Framework.
•
Access offline data or data that has limited availability in client applications.
•
Design, develop, and consume a simple data service.
•
Use WCF Data Services to update and delete data and handle multi-user concerns.
•
Develop high-performance, scalable ADO.NET applications that can query and update data.
•
Use LINQ to SQL to develop against a logical model that abstracts the lowlevel details of querying ADO.NET tables and result sets.
Course Outline This section provides an outline of the course: Module 1, “Introduction to Data Access Technologies,” introduces the commonly used data access technologies and the scenarios that they are best suited to. Module 2, “Building Entity Data Models,” introduces the concepts of data modeling, and in particular, EDMs. It explains how developers can use EDMs to decouple the conceptual data structure in their applications from the logical data structure in the data store. Module 3, “Querying Entity Data,” explains how to use LINQ to Entities, Entity SQL, the EntityClient provider for the Entity Framework, and stored procedures to
iv
About This Course
retrieve data from an entity model, and describes when each approach should be used. Module 4, “Creating, Updating, and Deleting Entity Data,” introduces the ways that the Entity Framework enables data modifications. It also describes how the Entity Framework implements change tracking. Module 5, “Handling Multi-User Scenarios by Using Object Services,” introduces the concurrency model and describes how the Entity Framework can make use of transactions to ensure data integrity. Module 6, “Building Optimized Solutions by Using Object Services,” describes best practices for designing and building a scalable, optimized data access layer by using Object Services. Module 7, “Customizing Entities and Building Custom Entity Classes,” describes how to customize and extend entities with your own business logic. Module 8, “Using POCO Classes with the Entity Framework,” introduces the ways to define custom entity classes in Entity Framework applications. By default, Visual Studio generates a set of entity classes from the EDM. This module describes how to use an existing set of POCO business classes in the application and how to extend the generated entity classes to add custom business functionality to the entity objects. Module 9, “Building an N-Tier Solution by Using the Entity Framework,” introduces the architectural issues that may be encountered when building an ntier solution and explains how to solve these problems by using the Entity Framework. Module 10, “Handling Updates in an N-Tier Solution by Using the Entity Framework,” describes how to handle data modifications in an n-tier solution and how to manage the exceptions that can occur during the data modification process. Module 11, “Building Occasionally Connected Solutions,” describes how to access offline or occasionally connected data in client applications. It describes how to cache data in local XML files by using LINQ to XML and how to implement an occasionally connected application by using Sync Framework. Module 12, “Querying Data by Using WCF Data Services,” introduces the purpose and features of a WCF Data Service and describes how to create and consume a WCF Data Service. It also discusses how to grant and restrict access to resources that a WCF Data Service exposes. Module 13, “Updating Data by Using WCF Data Services,” describes how to use WCF Data Services to modify data. WCF Data Services use standard Internet
About This Course
v
protocols such as HTTP and the Atom Publishing Protocol to enable update access to data across the Internet or a corporate network. Module 14, “Using ADO.NET,” introduces ADO.NET and explains how to use it to develop scalable, high-performance, data-driven applications. Module 15, “Using LINQ to SQL,” introduces LINQ to SQL and explains how to use it to abstract the low-level details of ADO.NET queries by developing against a logical data model.
vi
About This Course
Course Materials The following materials are included with your kit: •
•
Course Handbook. A succinct classroom learning guide that provides all the critical technical information in a crisp, tightly-focused format, which is just right for an effective in-class learning experience. •
Lessons: Guide you through the learning objectives and provide the key points that are critical to the success of the in-class learning experience.
•
Labs: Provide a real-world, hands-on platform for you to apply the knowledge and skills learned in the module.
•
Module Reviews and Takeaways: Provide improved on-the-job reference material to boost knowledge and skills retention.
•
Lab Answer Keys: Provide step-by-step lab solution guidance at your finger tips when it’s needed.
Course Companion CD. Searchable, easy-to-navigate digital content with integrated premium on-line resources designed to supplement the Course Handbook. •
Lessons: Include detailed information for each topic, expanding on the content in the Course Handbook.
•
Labs: Include complete lab exercise information and answer keys in digital form to use during lab time.
•
Resources: Include well-categorized additional resources that give you immediate access to the most up-to-date premium content on TechNet, MSDN®, and Microsoft Press®.
•
Student Course Files: Include the Allfiles.exe, a self-extracting executable file that contains all the files required for the labs and demonstrations.
Note To access the full course content, insert the Course Companion CD into the CDROM drive, and then in the root directory of the CD, double-click StartCD.exe.
•
Course evaluation. At the end of the course, you will have the opportunity to complete an online evaluation to provide feedback on the course, training facility, and instructor.
About This Course
To provide additional comments or feedback on the course, send e-mail to
[email protected]. To inquire about the Microsoft Certification Program, send e-mail to
[email protected].
vii
viii
About This Course
Virtual Machine Environment This section provides the information for setting up the classroom environment to support the business scenario of the course.
Virtual Machine Configuration In this course, you will use Hyper-V™ to perform the labs. Each classroom computer serves as a host for one virtual machine that will run in Hyper-V. There is one Hyper-V virtual machine for each lab in this course, named 10265A-GEN-DEV-XX. Each virtual machine has an internal computer name of 10265A-GEN-DEV.
Important: At the end of each lab, you must close the virtual machine and must not save any changes. To close a virtual machine without saving the changes, perform the following steps: 1. On the virtual machine, on the Action menu, click Close. 2. In the Close dialog box, in the What do you want the virtual machine to do? list, click Turn off and delete changes, and then click OK.
The following table shows the role of each virtual machine used in this course: Virtual machine
Role
10265A-GEN-DEV-02
The virtual machine to use for Lab 2
10265A-GEN-DEV-03
The virtual machine to use for Lab 3
10265A-GEN-DEV-04
The virtual machine to use for Lab 4
10265A-GEN-DEV-05
The virtual machine to use for Lab 5
10265A-GEN-DEV-06
The virtual machine to use for Lab 6
10265A-GEN-DEV-07
The virtual machine to use for Lab 7
10265A-GEN-DEV-08
The virtual machine to use for Lab 8
10265A-GEN-DEV-09
The virtual machine to use for Lab 9
10265A-GEN-DEV-10
The virtual machine to use for Lab 10
10265A-GEN-DEV-11
The virtual machine to use for Lab 11
About This Course
Virtual machine
ix
Role
10265A-GEN-DEV-12
The virtual machine to use for Lab 12
10265A-GEN-DEV-13
The virtual machine to use for Lab 13
10265A-GEN-DEV-14
The virtual machine to use for Lab 14
10265A-GEN-DEV-15
The virtual machine to use for Lab 15
Software Configuration The following software is installed on each VM: •
Visual Studio 2010 Ultimate
•
Microsoft SQL Server® Management Studio Express
Course Files There are files associated with the labs in this course. The lab files are located in the folder E:\Labfiles\LabXX on the student computers.
Classroom Setup Each classroom computer will have the same virtual machine configured in the same way.
Course Hardware Level To ensure a satisfactory student experience, Microsoft Learning requires a minimum equipment configuration for trainer and student computers in all Microsoft Certified Partner for Learning Solutions (CPLS) classrooms in which Official Microsoft Learning Product courseware are taught. The course requires that you have a computer that meets or exceeds hardware level 6, which prescribes the following: •
Intel Virtualization Technology (Intel VT) or AMD Virtualization (AMD-V) processor.
•
Dual 120-GB hard disks, 7,200 RAM Serial Advanced Technology Attachment (SATA) or better (configured as a stripe array).
•
4 GB of RAM expandable to 8 GB or higher.
x
About This Course
•
DVD drive.
•
Network adapter.
•
Super VGA (SVGA) 17-inch monitor.
•
Microsoft mouse or compatible pointing device.
•
Sound card with amplified speakers.
In addition, the instructor computer must be connected to a projection display device that supports SVGA 1024 × 768, 16-bit colors.
Introduction to Data Access Technologies
1-1
Module 1 Introduction to Data Access Technologies Contents: Lesson 1: Data Access Technologies
1-3
Lesson 2: Data Access Scenarios
1-14
Lab: Analyzing Data Access Scenarios
1-19
1-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
Before you start developing an application, it is important to know which technology is most appropriate to use. In this module, you will be introduced to commonly used data access technologies and the scenarios that they are best suited to.
Objectives After completing this module, you will be able to: •
Describe the key data access technologies that are available to Microsoft® .NET Framework developers.
•
Assign appropriate data access technologies to common data access scenarios.
Introduction to Data Access Technologies
Lesson 1
Data Access Technologies
Many data access technologies are available to today’s developer. This lesson introduces each of the key Microsoft technologies for data access, including the ADO.NET Entity Framework, Language-Integrated Query (LINQ), the Microsoft Sync Framework, Windows® Communication Foundation (WCF) Data Services, and ADO.NET.
Objectives After completing this lesson, you will be able to: •
Describe the ADO.NET Entity Framework.
•
Describe LINQ.
•
Describe the Sync Framework.
•
Describe WCF Data Services.
•
Describe ADO.NET.
1-3
1-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Overview of the ADO.NET Entity Framework
Key Points Data access code has traditionally been tedious to develop. Queries are written as text strings that cannot be type-checked or syntax-checked at compile time, complex joins are required to link data items that belong together from a business point of view, and results are returned as untyped data records. The ADO.NET Entity Framework solves these problems. It enables you to write your queries directly in the programming language of your choice, to write data access applications by programming against a conceptual model of the data instead of against a normalized storage schema, and to access returned data as typed entities.
Entity Data Models Even if you do not explicitly develop one, all applications have a conceptual model that defines the objects within the application and the relationships between them. Database applications also have a physical model that defines how the data is stored in the database and developers traditionally have to write code to navigate between the two.
Introduction to Data Access Technologies
1-5
The Entity Framework aims to eliminate this impedance mismatch and enable developers to code against their conceptual model while it manages the mapping to the physical model for them. It introduces the concept of an Entity Data Model (EDM), which is a conceptual model that is mapped to the physical model of a data store. The model and mappings are stored in XML files that describe the structure of both models and the mappings in between. After you have added an EDM to your project, you can simply manage and work with the conceptual model that you defined, while leaving the physical storage concerns to the database engineers. An EDM also makes it easier to change data sources without requiring code modifications to your application. You can change the connection information of the model and make any changes to the structural part of the EDM. As long as the mappings are updated, your code will still run as expected.
Entity SQL The Entity Framework provides you with a new query language called Entity Structured Query Language (Entity SQL). This is a storage-independent query language that enables you to query and manipulate EDM constructs. The Entity Framework uses storage-specific data providers to translate the generic Entity SQL commands that you write into storage-specific queries that your underlying data source can use.
Entity Client The EntityClient provider is an ADO.NET data provider that supports accessing data stored in an EDM. It enables you to write queries in Entity SQL while using familiar objects from the ADO.NET object model.
Object Services Object Services enables you to work with the common language runtime (CLR) objects that the conceptual model generates. The services that it provides include change tracking, relationship management, state management, Entity SQL query support, identity resolution, and transmission of changes back to the data source. Question: What is the key advantage to using the Entity Framework over other data access technologies?
Additional Reading For more information about the ADO.NET Entity Framework, see the ADO.NET Entity Framework page http://go.microsoft.com/fwlink/?LinkId=196089.
1-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Overview of LINQ
Key Points Traditionally, you can divide the code in data-centric applications into two types: your application logic, which you write in a programming language, and your data manipulation code, which you write in a query language. This architecture means that you need to learn two languages and that you have to write queries as string literals that Microsoft IntelliSense® does not type-check or support. LINQ solves these issues by enabling you to perform set-based data queries in your usual programming language. It supports type-checking and syntax-checking at compile time in addition to IntelliSense because the queries are directly in your code. This support is intended to reduce the time that you spend debugging your code. There are several flavors of LINQ for different types of data source, including: •
LINQ to Entities. You use LINQ to Entities to work with data in an EDM.
•
LINQ to Objects. You use LINQ to Objects to query collections that implement the IEnumerable or IEnumerable(T) interface. It enables you to write
Introduction to Data Access Technologies
1-7
declarative code to manage data that is stored in a collection without using complex loops. •
LINQ to XML. You use LINQ to XML to work with XML data that is stored in memory. It provides the functionality that the Document Object Model (DOM) supplies, alongside support for LINQ query expressions.
•
LINQ to SQL. You use LINQ to SQL to work with data that is stored in a Microsoft SQL Server® database.
Each of these is part of LINQ, so the structure of the queries is common across all, resulting in increased code reuse and knowledge reuse. Question: What advantages over writing queries as string literals does LINQ provide?
Additional Reading For more information about LINQ, see the LINQ Portal page at http://go.microsoft.com/fwlink/?LinkId=196090.
1-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Overview of the Sync Framework
Key Points The Sync Framework provides a synchronization platform that you can use to build offline applications and Occasionally Connected Applications (OCAs) by copying data from one source and transferring it to another. The Sync Framework supports any data from any data source using any protocol. The Sync Framework provides you with a flexible, yet powerful, synchronization system that supports: •
Custom providers. The Sync Framework uses providers to connect to data sources. It ships with providers for ADO.NET-enabled data sources, files and folders, and FeedSync feeds such as Real Simple Syndication (RSS) and Atom feeds. If you need to use other data sources, you can create your own custom provider for any type of data.
•
Conflict handling. The Sync Framework detects the most common conflicts that occur and raises an event that you can handle to resolve the conflict in your chosen way.
Introduction to Data Access Technologies
•
1-9
Filtered subsets of data. You can filter the data that you are synchronizing to minimize the network traffic during synchronization.
You can configure synchronization to copy data from client to server, from server to client, or in both directions. Question: What are the three data source types for which providers ship with the Sync Framework?
Additional Reading For more information about the Sync Framework, see the Microsoft Sync Framework page at http://go.microsoft.com/fwlink/?LinkId=196091.
1-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Overview of WCF Data Services
Key Points WCF Data Services (previously known as ADO.NET Data Services) enable you to create and access data services over the Internet or an intranet. You expose your data as resources that client applications can access by using a URI. These resources are exposed as sets of entities that are related by associations, the same concepts as in an EDM. However, you can expose data from many types of storage, including databases, CLR classes, and late-bound data types. By using URIs for addressing, any client application that supports the standard HTTP verbs, GET, PUT, POST, and DELETE, can use WCF Data Services. You access resources by building a URI that traverses the entities and relationships in the model. For example, the URI in the following code example returns all of the orders that contact 1 has made. http://localhost:12344/AdventureWorks.svc/Contacts(1)/SalesOrderHeader
You can extend the URI to include filters, sorting, and paging of data. You can also extend your services by writing service operations as methods that perform business logic at the server. These methods are then accessible as URIs in a similar
Introduction to Data Access Technologies
1-11
fashion to resources. You can also define interceptors, which are called when you query, insert, update, or delete data and may validate or alter the data, enforce security, or reject the change. Question: Which HTTP verb is used to insert data into the data source?
Additional Reading For more information about WCF Data Services, see the WCF Data Services page at http://go.microsoft.com/fwlink/?LinkId=196092.
1-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Overview of ADO.NET
Key Points ADO.NET is a flexible and lightweight framework that enables you to build data access applications for relational, XML, and application data. It supports writing of client and middle-tier components for Windows-based and Web-based applications. When you use ADO.NET, your data access code is tightly coupled to your data, meaning that changes to the data storage structure require changes to your code. It also requires that you work with data in the logical model, without a conceptual model to simplify your code. ADO.NET is the basis of most of the .NET Framework data access technologies and it still has its own place in data access development today. Examples include situations such as the need to support legacy applications, applications where you do not require the additional functionality of higher-level technologies such as the Entity Framework, and occasions when you are building highly optimized applications. ADO.NET provides you with a consistent set of classes that you can use to access SQL Server, XML, OLE DB, and Open Database Connectivity (ODBC) data. You connect to the data source by using a data provider that translates the ADO.NET
Introduction to Data Access Technologies
1-13
commands into the connection information that your data source requires. The data providers provide key objects that enable you to execute commands, stream data, and load data. In addition to the generic data providers, you can use datasource-specific data providers, such as the .NET Framework Data Provider for SQL Server, which are tuned for their specific data source. Question: How can you ensure the best performance of your ADO.NET code?
Additional Reading For more information about ADO.NET, see the ADO.NET page at http://go.microsoft.com/fwlink/?LinkId=196093.
1-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 2
Data Access Scenarios
Just as there are many data access technologies, there are also many types of application that need to access data. It is important that you understand the requirements of your application so that you can correctly match the appropriate technology to it. This lesson discusses the different types of data access application and the appropriate technology to use for each.
Objectives After completing this lesson, you will be able to: •
Describe the appropriate technologies to use when you are developing different types of enterprise application.
•
Describe the appropriate technologies to use when you are supporting legacy applications.
Introduction to Data Access Technologies
1-15
New Development Projects
Key Points Enterprise applications include applications that are used internally, accessed externally by employees, and accessed externally by partner organizations. Each of these types of application has its own requirements in terms of functionality and security that you must assess before you select the appropriate technology for the application.
Internal Applications Many internal and external applications often use the same data from a data source. If your applications are tightly bound to the structure of the data source, any changes to the structure of the data will require changes to your applications. Therefore, it is recommended that you loosely couple your applications to your data so that changes have minimal impact on your application code. The Entity Framework provides you with the concept of EDMs that enable you to loosely couple your data to your applications. If the structure of the data changes, you can simply update the data store section of the model and the code in your application will continue to function as expected. EDMs also enable you to transform the data into a conceptual form that matches the business requirements
1-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
of your application without worrying about the logical structure of the underlying data source.
Enterprise Applications Accessed Externally by Employees With the increase of employees working from home and visiting customer sites, you will often need to make your internal applications externally accessible by employees who are using different types of hardware. If you just make your data sources accessible from outside the corporate network, you may find that users have to wait for slow or unreliable network connections to download large amounts of data, and that remote users often do not have a network connection that they can use to access the data. These issues often make it favorable for the users to have their own local copy of the data that they can update as and when necessary. LINQ to XML provides read-only access to data that is stored in a local XML file. You can develop an application that caches data locally when it is connected to the central data store and then uses the local information cache when it is disconnected. Alternatively, the Sync Framework supports the development of OCAs that provide read/write access to local data when the application is offline and synchronize with the central data store when the application is online. This ensures that users have constant access to the data without impacting the server performance for internal users.
Enterprise Applications Accessed Externally by Partners Although you could allow partners to use synchronization applications to access and manipulate your corporate data, security issues are unlikely to make this advisable. When you work with partner organizations, you need to ensure that they can only access specific data and that any changes that they make are valid and safe. WCF Data Services enable you to make your corporate data sources accessible over an intranet or the Internet by using standard protocols and addressing. You can use service operations and interceptors to restrict access to appropriate data and validate changes before saving them in the database. Question: Why would you not use the Sync Framework to develop an application that partner organizations will use to access your corporate data?
Introduction to Data Access Technologies
1-17
Additional Reading For more information about developing new applications, see the Microsoft Case Studies: Veracity Solutions page at http://go.microsoft.com/fwlink/?LinkId=194013 and the Microsoft Case Studies: Ian Mercer page at http://go.microsoft.com/fwlink/?LinkId=194014.
1-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Legacy Applications
Key Points Few organizations will rewrite their applications with every release of a new or updated technology. Therefore, it is likely that you will need to support legacy applications, in addition to maintaining them and writing new applications that interact with them and their data sources. ADO.NET enables you to write tightly coupled components and applications that support different versions of the .NET Framework. It is useful when you do not need the functionality that the higher-level technologies, such as the Entity Framework, provide. By using ADO.NET, you can write client-agnostic data tiers that support legacy applications in addition to newer applications. It also provides fast access to the data source. Question: Name one disadvantage of using ADO.NET for data access applications.
Introduction to Data Access Technologies
1-19
Lab: Analyzing Data Access Scenarios
Objectives After completing this lab, you will be able to determine the appropriate technology to use for common data access scenarios.
Introduction In this lab, you will analyze four common data access scenarios and decide which data technology to use for each.
1-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Scenario
You are working as a data expert for Adventure Works Cycles. You have been asked to analyze the database application requirements to determine which data access technologies should be used to meet the requirements of a range of applications and scenarios.
Exercise 1: Identifying Data Access Technologies Scenario In this exercise, you will determine the appropriate technology for each given data access scenario. The main tasks for this exercise are as follows: 1.
Identify the appropriate data access technology for a customer management application.
2.
Identify the appropriate data access technology for an order management application.
Introduction to Data Access Technologies
3.
Identify the appropriate data access technology for a delivery management application.
4.
Identify the appropriate data access technology for a product management application.
1-21
f Task 1: Identify the appropriate data access technology for a customer management application Scenario Adventure Works Cycles has a corporate database that contains customer information. Employees can browse and maintain customer data, but customers only have read access to the data. Employees use a Windows Presentation Foundation (WPF) application to access their required data and the corporate network has no bandwidth issues. The corporate database is several years old and changes are made to the database structure twice a year. •
Given the scenario above, on a piece of paper, write down what you think is the most appropriate data access technology to solve the business problem.
f Task 2: Identify the appropriate data access technology for an order management application Scenario Adventure Works Cycles has a requirement to enable salespeople to view and create orders during offsite meetings and add them to the database at a later time. Therefore, the data access layer needs to copy database content on the remote device to the server. •
Given the scenario above, on a piece of paper, write down what you think is the most appropriate data access technology to solve the business problem.
f Task 3: Identify the appropriate data access technology for a delivery management application Scenario Adventure Works Cycles has agreed to provide an ASP.NET Model-View-Controller (MVC) Web application to delivery companies to query and maintain the delivery status of orders as they ship them. The application has to provide fast and
1-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
responsive access to the database in a potentially low-bandwidth environment. Adventure Works Cycles has to produce a highly robust data access layer for this application in a very compressed time scale. •
Given the scenario above, on a piece of paper, write down what you think is the most appropriate data access technology to solve the business problem.
f Task 4: Identify the appropriate data access technology for a product management application Scenario Adventure Works Cycles previously developed what are now legacy applications that enable employees to browse and maintain a list of products, and enable customers to browse a list of products. The employee application is a Windows Forms application, and the customer application is a Web application. Both applications were built by using the .NET Framework 2.0 or earlier. The underlying database structure is stable and has not been changed since it was first designed. Employees have no bandwidth limitations, although customers may do. 1.
Given the scenario above, on a piece of paper, write down what you think is the most appropriate data access technology to solve the business problem.
2.
Discuss the benefits and drawbacks of each of your solutions with one of the other students.
Introduction to Data Access Technologies
1-23
Lab Review
Review Questions 1.
What is the appropriate technology for the customer management application?
2.
What is the appropriate technology for the order management application?
3.
What is the appropriate technology for the delivery management application?
4.
What is the appropriate technology for the product management application?
1-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
What key advantage does the use of an EDM provide?
2.
What key advantage does synchronization provide over users accessing the data in the central database?
3.
What type of client application can use a WCF Data Service?
Building Entity Data Models
2-1
Module 2 Building Entity Data Models Contents: Lesson 1: Introduction to Entity Data Models
2-3
Lesson 2: Modifying an Entity Data Model
2-18
Lesson 3: Customizing an Entity Data Model
2-29
Lab: Using Entity Data Models
2-43
2-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
Data modeling is a well established part of database design, enabling database administrators and developers to model how data is physically and logically stored. The Entity Framework introduces the concept of an Entity Data Model (EDM) which enables the you to model the data in a meaningful way for use in your application. This module introduces the concepts of data modeling, and in particular, EDMs. It explains how you can use EDMs to decouple the conceptual data structure in your applications from the logical data structure in the data store.
Objectives After completing this module, you will be able to: •
Describe and create an Entity Data Model.
•
Modify an Entity Data Model by using the Entity Designer.
•
Customize a model to meet your business requirements.
Building Entity Data Models
2-3
Lesson 1
Introduction to Entity Data Models
Before you can use an EDM to its full potential, you need to understand how to use the available tools to create an EDM and the structure of the model that they create. This lesson shows you how to create an EDM from an existing database and explains how the model is represented in XML.
Objectives After you have completed this lesson, you will be able to: •
Describe logical and conceptual models.
•
Describe the two key model design strategies.
•
Create models in the Entity Designer.
•
Map a model to a database.
•
Describe the XML behind a model.
•
Describe the issues you should consider when you deploy a model.
2-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Introduction to Data Models
Key Points There are three types of models that are commonly used in data modeling: •
The physical model describes how the data is stored in the database, including details about partitions and indexes. Although this model is very important from a database administrator’s point of view, it is not a convenient way for a developer to work with the data.
•
The logical model builds on the physical model and describes how the data is stored in tables and related by keys. Although this is a useful model for a database administrator or database developer, it is still not the ideal way for an application developer to view the data. When coding against the logical model, you are likely to require complex queries that involve multiple joins.
•
The conceptual model describes the semantics of the business view of the data. It defines entities and relationships in a business sense and is mapped to the logical model to enable easy access to the underlying data. Because the conceptual model describes the items in your application in business terms, you will be able to work with data items in a meaningful way, avoiding the complex queries written against a logical model.
Building Entity Data Models
2-5
The Microsoft® .NET Entity Framework implements the conceptual model as an EDM. The EDM is mapped to the logical model of the database, and the Entity Framework is responsible for translating your business functionality into data source–compliant commands.
Entities vs. Entity Objects vs. Business Objects Entities are things in an application that need to be modeled by data. For example, in an online ordering application, entities may include customers, products, and orders. An entity is simply a description of the item and its properties. Entities are linked by relationships. For example, an order is related to a customer and a product. An entity object is an object returned from an EDM and is based on an entity. Entity objects have properties, but the only default methods they contain are to enable change tracking for that object. You can extend entity objects to contain behaviors required in your application, or you can create business objects from the entity objects and store your custom logic in the business objects. Question: Which model is directly linked to the storage structure in a database?
Additional Reading For more information about data models, see The ADO.NET Entity Framework: Modeling at the Right Level of Abstraction, Modeling Data at the Conceptual Level of Abstraction: The Entity Data Model, and Bringing Data into an EDM Model: Mapping sections of The ADO.NET Entity Framework Overview page at http://go.microsoft.com/fwlink/?LinkID=194015.
2-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Model Design Strategies
Key Points There are three ways to approach the design of the data in your application. You can create a database and then use the Entity Framework tools to generate a model, known as database-first design, you can design your model and then use the tools to reverse engineer your database, known as model-first design, or you can write your code without using a model and let one be generated at runtime, known as code-only design. The following list describes the three different types of design: •
Database-first design. In database-first design, you design and create your database before you design the entities in your application. This approach is often favored by database administrators, but it can limit the flexibility of the application in the long term.
•
Model-first design. In model-first design, you design the entities for the application and then create the database structure around these entities. This approach is often favored by developers, because they can design the application around the business functionality that they require and then build a database structure to support those needs.
Building Entity Data Models
•
2-7
Code-only design. In code-only design, the model is generated when it is required at run time. Because the model is automatically generated, it does not support some of the advanced EDM features that you can manually implement.
However, you will often find that you must develop an application against an existing database, in which case you have no choice but to use a database-first design.
Design Strategies in the Entity Framework 4 The Entity Framework version 4 supports the database-first and model-first design strategies out of the box. When you first add a model to a project, you can choose whether to create an empty model and design the model yourself or generate the model from an existing database. Whichever strategy you choose, you can create or update the database with the structure from your model at any point in the design process. It is recommended that before you start to design a model in any of the available tools, you first plan and sketch the model. Question: In what scenario are you likely to use a database-first design?
2-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Introduction to the Entity Designer
Key Points Microsoft Visual Studio® 2010 provides the ADO.NET Entity Data Model Designer (Entity Designer), which enables you to graphically create and relate entities in a model. It also provides you with the Entity Data Model Wizard for generating models, the Update Model Wizard for updating an existing model from a database, and the Create Database Wizard for generating scripts from a model. The Entity Designer includes the designer pane, which you can use to create entities, relationships, and hierarchies. It also includes the Mapping Details pane, which you can use to view and modify the mappings to the logical model, and the Model Browser pane, which you can use to manage the properties, mappings, and entities in your model.
Example On the slide associated with this topic, you will see a screen shot of a simple model in the Entity Designer. In this model, there are two entities—Contact and SalesOrderHeader—that are related by a one-to-many relationship as shown by the 1:* line linking the two
Building Entity Data Models
2-9
entities. This relationship is also shown in the Navigation Properties list at the bottom of each entity. The other properties listed in the entity are the attributes of the entity and will hold the data for an entity object. Each property has properties of its own that describe it. For example, the ContactID property of the Contact entity has a Type property of Int32, a Nullable property of False, and a Name property of Contact ID. The properties shown on the slide are all scalar properties. You will learn about other types of property later in this module.
EDM Generator In addition to the Entity Designer, you can use the EdmGen.exe command-line tool to create and modify EDMs. The tool is installed in the .NET Framework folder, but you can easily access it from the Visual Studio command prompt. The tool can perform a variety of functions, including validating model XML files and generating models. The following code example shows how to use EdmGen.exe to generate a model from the AdventureWorks database. Edmgen.exe /mode:fullgeneration /c:"Data Source=%datasourceserver%; Initial Catalog=AdventureWorks; Integrated Security=SSPI" /project:AW /entitycontainer:AWEntities /namespace:AWModel /language:CSharp
Question: Name two tools that you can use to generate an entity model.
Additional Reading For more information about the Entity Designer, see the ADO.NET Entity Data Model Designer page at http://go.microsoft.com/fwlink/?LinkID=194016.
2-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Creating a Model from a Database
Key Points •
Create a new class library project.
•
Create an EDM in the project based on two tables in the AdventureWorks database.
•
Review the information in the Mapping Details window, the Model Browser window, and the Properties window.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-02 virtual machine as Student with the password Pa$$w0rd
2.
Open Microsoft® Visual Studio® 2010.
3.
In Visual Studio 2010, create a new Visual C# class library project named CreatingAModelDemo in the E:\Demofiles\Mod02\Demo1\Starter\CreatingAModelDemo folder.
Building Entity Data Models
2-11
4.
Add an ADO.NET Entity Data Model named AWModel to the solution.
5.
Create a model based on the local Microsoft SQL Server® Express AdventureWorks database by using the Contact and SalesOrderHeader tables.
6.
Review the information in the Mapping Details window, the Model Browser window, and the Properties window.
Question: What information is stored in a NavigationProperty property?
2-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Mapping a Model to a Database
Key Points By default, when you create a model from a database, the Entity Designer automatically generates the mappings from the conceptual model to the logical model. These mappings are displayed in the Mapping Details pane, and you can use this pane to view, modify, and delete mappings to the underlying tables. If you update a model from the database, the mappings are again automatically created. If you add a custom entity to the model, you must manually map the entity properties to the tables. The Mapping Details pane also enables you to add conditions to a mapping to limit the data that is returned. Continuing with the Sales example, if you want to view only the orders from one sales territory, you can add a condition to that mapping to select only the appropriate orders from the database. Question: When might you need to manually create the mappings between the conceptual and logical models?
Building Entity Data Models
2-13
The XML Behind the Model
Key Points When you view a model in the Entity Designer, you are viewing an .edmx file. However, that file is defined in an XML file that defines the conceptual model, the logical (or storage) model, and the mapping between them. In the Entity Designer, you simply see a graphical representation of this XML.
f View the XML in Visual Studio 1.
Close the Entity Designer window displaying the model.
2.
In Solution Explorer, right-click the model, and then click Open With.
3.
In the Open With dialog box, click XML Editor, and then click OK.
The XML file is divided into two sections: the <edmx:Runtime> element, which defines the model, and the
element, which defines the Entity Designer content. The second of these two elements should not be edited manually; however, you can edit the contents of the <edmx:Runtime> element to implement functionality that is not available in the Entity Designer.
2-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Structure of the <edmx:Runtime> Element The runtime element is subdivided into three further sections: •
SSDL content. The store schema definition language (SSDL) section of the XML defines the storage model of the entity model. It describes the logical structure of each of the entities in the model and the associations between entities.
•
CSDL content. The conceptual schema definition language (CSDL) section of the XML defines the conceptual model of the entity model. It describes the conceptual structure of the entities and associations in the model.
•
C-S mapping content. The C-S mapping section of the XML defines the mappings between the SSDL and CSDL content. For each entity in the model, the properties are mapped to columns in tables in the logical structure.
Generally, you will not need to work with the XML view of the model; however, there are a few scenarios where you will, including: •
Adding a defining query to emulate a database view in the model itself.
•
Editing an entity key that one of the model wizards has incorrectly inferred.
•
Working with store-generated column values.
•
Mapping a globally unique identifier (GUID) property to a binary column.
•
Defining customer functions in the storage or conceptual model to emulate stored procedures in the model itself.
Warning: Changes you make to the SSDL section of a model may be overwritten if you use the Update Model Wizard to update your model from the database.
Question: What are the two main sections of the XML file behind a model?
Additional Reading For more information about the XML behind an EDM, see the .edmx File Overview (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194017.
Building Entity Data Models
2-15
Demonstration: Reviewing the Model's XML
Key Points •
Open an EDM in the XML Editor.
•
Review the XML definition of the model.
Demonstration Steps 1.
Continue working with the solution files from the previous demonstration.
2.
If AWModel.edmx is open in the Entity Designer, save the file and then close the Entity Designer window.
3.
Open AWModel.edmx in the XML Editor.
4.
Review the XML definition of the model.
Question: Which section of the XML should you not manually edit?
2-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Model Deployment Considerations
Key Points By default, the model and mapping files are embedded as a resource in the application assembly. Although this may be appropriate during the development and testing stages of the application, when you release the application, you should consider whether to change this to store the model and mapping loosely coupled outside the assembly.
Advantages of Storing the Model and Mapping Files Externally During the lifetime of an application, it is possible that the database schema may be modified. If your model and mapping files are part of the application assembly, such changes will require a recompile and redeployment of the application. This may also result in versioning issues for the application. If the model and mapping files are stored externally, you only need to update the model and mapping files and redeploy those.
f Configure the model to store the files externally 1.
In Visual Studio, open the model in the Entity Designer.
Building Entity Data Models
2.
2-17
In the Properties pane, in the Metadata Artifact Processing row, select Copy to Output Directory.
After you have configured the Metadata Artifact Processing property, you will find that when you build the application, three extra files are generated in the output folder: <modelname>.csdl, <modelname>.ssdl, and <modelname>.msl. These files contain the definitions of the model. If your model now changes, you can update and redeploy just these files. If you want to move these files to an alternative location, you can update the paths in the Connection String property to point to the new location. Question: Why should you consider storing your XML externally to the application assembly?
2-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 2
Modifying an Entity Data Model
Even when you create a model from a database, you may need to modify the model to meet your specific requirements. Also, if you want to implement a model-first design, you must add entities and associations to a model. This lesson explains how to modify an EDM by adding entities and associations to the model. It also explains how to generate a script from the model to update the database with changes to the model.
Objectives After you have completed this lesson, you will be able to: •
Add entities to a model.
•
Add associations between entities.
•
Generate a Transact-SQL script from a model to update a database.
Building Entity Data Models
2-19
Adding Entities to a Model
Key Points In addition to defining entities by creating a model from an existing database, you can also manually define them in the Entity Designer. This is useful whether you want to use a model-first design or customize an existing model. When you add entities to a model, you must specify their name, key property name and type, and an entity set name. Optionally, you can derive your entity from an existing entity, in which case you only specify the name of the new entity.
f Add an entity to a model 1.
In Visual Studio, open the model in the Entity Designer.
2.
In the Entity Designer pane, right-click in the white space, point to Add, and then click Entity.
3.
In the Add Entity dialog box, enter the details for your entity, and then click OK.
After adding an entity, you can define the properties for it.
2-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Add a scalar property to an entity 1.
In Visual Studio, open the model in the Entity Designer.
2.
Right-click the entity, point to Add, and then click Scalar Property.
3.
Type the name of the property, and then press ENTER.
4.
In the Properties pane, configure the properties of the scalar property.
In addition to the scalar and navigation properties that you saw in the earlier demonstration, you can also define complex properties, associations, inheritance, and function imports. All of these will be discussed later in this module. Question: How do you create a derived entity?
Building Entity Data Models
2-21
Adding Associations Between Entities
Key Points You add associations to a model to define the relationships between entities. You can define one-to-one, one-to-many, many-to-one, and many-to-many relationships by using the Entity Designer. Each entity in the relationship is known as an end. One association can have only two entities in it; however, each entity can have multiple associations.
f Add an association between entities 1.
In Visual Studio, open the model in the Entity Designer.
2.
Right-click one of the ends of the required association, point to Add, and then click Association.
3.
In the Add Association dialog box, configure the appropriate entities, multiplicities, and navigation properties, and then click OK.
2-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Navigation Properties Navigation properties provide quick access to the ends of an association. They are created when you add an association and deleted when you delete an association. You can view the association information in the Properties pane for a navigation property. You can also use the Properties pane to rename or change the multiplicity of that end of the association. Question: What is the purpose of navigation properties?
Building Entity Data Models
Demonstration: Adding Entities and Associations
Key Points •
Add an entity to an existing EDM.
•
Add properties to an entity.
•
Add an association between two entities.
Demonstration Steps 1.
Continue working with the solution files from the previous demonstration.
2.
Open AWModel.edmx in the Entity Designer.
3.
Add an entity named ProductInfo to the model, with an entity set name of ProductInfos and a key property name of ProductID.
4.
Add a scalar property named ProductName to the entity.
5.
Add a scalar property named UnitPrice to the entity.
6.
Add a scalar property named SupplierID to the entity.
2-23
2-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
7.
Add an entity named Suppliers to the model, with an entity set name of Suppliers and a key property name of SupplierID.
8.
Add a many-to-many association between the ProductInfo and Suppliers entities.
9.
Review the navigation properties that have been created.
Question: How can you change the data type of a scalar property?
Building Entity Data Models
2-25
Generating Transact-SQL Scripts from a Model
Key Points When you add entities to an existing model, you must later map them to database objects. It is likely that you will create entities that do not have matching database tables; therefore, the Entity Framework provides the Generate Database Wizard, which you can use to generate a database from a model. The wizard inspects your model, the entities in it, the properties of those entities, and the associations between them, and then generates a Transact-SQL script to create database objects that will map to your model. The model has a Database Schema Name property that defines the schema name under which all of the objects will be created. The default setting for this property is dbo, so it is important to configure this to a more meaningful schema name. When you use a model-first design, you can use the Generate Database Wizard to generate a script for your entire database. If you are just modifying an existing model that already has mappings to a database, you can customize the script to only create the new items in your model.
2-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The wizard only generates the script and does not execute it. This is to ensure that if you only want to add items to an existing database, you do not overwrite the existing tables and thus the data that they contain. You can use Microsoft SQL Server® Management Studio or the Visual Studio Transact-SQL Editor to execute the script. Note: The Generate Database Wizard only supports SQL Server 2005 and SQL Server 2008 databases.
Question: You have used the Generate Database Wizard, but you cannot see any corresponding changes in your database. What have you forgotten to do?
Building Entity Data Models
Demonstration: Generating Transact-SQL Scripts
Key Points •
Modify the Database Schema Name property of a model.
•
Run the Generate Database Wizard.
•
Modify a generated script.
•
Execute a generated script.
Demonstration Steps 1.
Continue working with the solution files from the previous demonstration.
2.
Oepn AWModel.edmx in the Entity Designer.
3.
Change the Database Schema Name property of the model to Products.
4.
Run the Generate Database Wizard.
2-27
2-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
5.
In the Transact-SQL Editor window, review the Transact-SQL script that has been created. Make particular note of the Products.SuppliersProductInfo table.
6.
In the Transact-SQL Editor window, delete the sections of the script that relate to existing objects in the database.
7.
Execute the Transact-SQL script against the local SQL Server Express database.
8.
Open SQL Server Management Studio, and then review the new tables in the AdventureWorks database.
9.
Close SQL Server Management Studio.
10. Save and close the solution and then close Visual Studio. Question: How does the Generate Database Wizard handle many-to-many associations in the entity model?
Building Entity Data Models
2-29
Lesson 3
Customizing an Entity Data Model
Entity models created from a database do not always match your application design. They often use data from more than one table, inherit from other entities, or require access to stored procedures. They also combine properties into complex types. This lesson explains how to customize an EDM to support these requirements. It also explains how to use entity splitting to map an entity to multiple tables, how to use inheritance to customize your model, how to access stored procedures from your model, and how to create complex types in a model.
Objectives After you have completed this lesson, you will be able to: •
Use entity splitting.
•
Implement an inheritance hierarchy.
2-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
Add a stored procedure to a model.
•
Use complex types.
Building Entity Data Models
2-31
Using Entity Splitting
Key Points Developers often find themselves using the data from multiple tables as one item in an application. For example, a database might store employee data in two tables: departmental and location details in one table and personal information in another. For security purposes, it is important to keep these as separate tables in the database; however, the joins involved in merging them in applications is tedious to code. Entity splitting enables you to map one entity to more than one table, which removes the need to include joins of this type in your code. For entity splitting to work, both practically and logically, the two (or more) tables must have the same primary key. This ensures that no data can be wrongly read, updated, or deleted when you work with the single entity.
f Implement entity splitting 1.
Create the two entities (Entity1 and Entity2) in your model, mapped to the two database tables.
2.
Cut the unique properties from Entity1 and paste them into Entity2.
2-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
3.
In the Mapping Details pane, map the new properties in Entity2 to the tables in the database.
4.
Delete Entity1 from the model.
Question: Describe an example where you could use entity splitting in one of your applications.
Additional Reading For more information about entity splitting, see the How to: Define a Model with a Single Entity Mapped to Two Tables page at http://go.microsoft.com/fwlink/?LinkID=194018.
Building Entity Data Models
Demonstration: Using Entity Splitting
Key Points •
Add an ADO.NET EDM to an existing solution.
•
Create a model based on existing tables in a database.
•
Copy and paste properties from one entity to another entity.
•
Map properties of an entity to a database table.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-02 virtual machine as Student with the password Pa$$w0rd.
2.
Open Visual Studio.
3.
In Visual Studio, open the starter project in the E:\Demofiles\Mod02\Demo5\Starter folder.
4.
Add an ADO.NET EDM named EntitySplitting to the solution.
2-33
2-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
5.
Create a model based on the Customer and Individual tables in the local SQL Server Express AdventureWorks database.
6.
Review the model, noting that both of the tables have the same primary key (CustomerID), but that the other properties are all different.
7.
Copy and paste the ContactID and Demographics properties from the Individual entity to the Customer entity.
8. Map the new properties of the Customer entity to the Individual table . 9.
Delete the Individual entity.
10. Save and close the solution and then close Visual Studio.
Question: What is the defining factor for whether two tables can be used for entity splitting?
Building Entity Data Models
2-35
Implementing an Inheritance Hierarchy
Key Points The Entity Framework and the Entity Designer support entity inheritance, which enables you to derive an entity type from another entity type in the model. They support both table-per-type and table-per-hierarchy inheritance.
Table-per-Type Table-per-type inheritance uses separate tables in the database for each type. For example, a Contact table may contain personal information such as name and title, and a Customer table may inherit from the Contact table and add information such as Billing Address, Shipping Address, and Credit Card Details.
Table-per-Hierarchy Table-per-hierarchy inheritance uses conditional mapping to define rows into the different entity types. In the example shown on the slide associated with this topic, the CurrentProduct entity is derived from the Product entity. In the Mapping Details pane, a condition restricts the data in the CurrentProduct entity to only products that do not have
2-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
an entry in the DiscontinuedDate property; that is, they have not been discontinued. The next step in the application design may be to derive another entity from the Product entity for the discontinued products.
f Implement an inheritance hierarchy 1.
Add the base type to your entity model.
2.
Create new entities, specifying that their Base type property should be the base entity.
3.
Move properties specific to each subtype from the base type to the subtype.
3.
Add conditions to the subtypes' specific properties.
4.
Set the Abstract property of the base class to True.
Question: Describe an example where you could use entity inheritance in one of your applications.
Additional Reading For more information about table-per-type inheritance, see the How to: Define a Model with Table-per-Type Inheritance (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194019.
Building Entity Data Models
2-37
Adding a Stored Procedure to a Model
Key Points For security, encapsulation, and performance reasons, stored procedures are a recommended way of interacting with data in a database. Therefore, it is logical that the Entity Framework should expose them to an entity model to enable you to use the functionality and advantages of stored procedures. Function imports enable you to invoke stored procedures directly from your EDM. When you have the stored procedure included in the model, you can add a function import to the model to enable access to the stored procedure. To create the function import, you must specify the stored procedure to call and the return type of the stored procedure. Stored procedures can return collections of scalar types, complex types, entity types, or have no return value. When returning entity types, the column structure of the returned data must exactly match the column structure of the entity type. The function import is not shown in the Entity Designer, but with the Entity Designer open, you can view the function and the stored procedure details in the Model Browser pane.
2-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Add a stored procedure to a model 1.
Use the Update Wizard to add the stored procedure to the model.
2.
Add a function import to the model, specifying a name for the import, the stored procedure name it should call, and the return type of the procedure.
Question: Why would you want to access a stored procedure from an EDM?
Building Entity Data Models
Demonstration: Adding a Stored Procedure
Key Points •
Execute a Transact-SQL script to create a stored procedure in the database.
•
Add a stored procedure to a model.
•
Add a function import to a model.
•
Review the runtime content of a function import in the XML Editor.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-02 virtual machine as Student with the password Pa$$w0rd.
2.
Open Visual Studio.
3.
In Visual Studio, open the starter project in the E:\Demofiles\Mod02\Demo6\Starter\UsingSPDemo folder.
4.
Execute the CreateSP.sql Transact-SQL script.
2-39
2-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
5.
Open AWModel in the Entity Designer.
6.
Add the uspCountOrders stored procedure to the model.
7.
Add a function import to the model. Name the function import CountOrders and map it to the uspCountOrders stored procedure.
8.
View the function import in the Model Browser window.
9.
Save and then close the model.
10. Open the model in the XML Editor. 11. Review the runtime content, and then close the XML Editor. 12. Save and close the solution and then close Visual Studio.
Question: How can you query what a stored procedure returns when importing it as a function?
Building Entity Data Models
2-41
Using Complex Types
Key Points You may find that in one entity model, you have a set of related properties that are used in more than one entity. For example, in a model with Customers, Employees, and Directors entities, each may have a HouseNumber, Street, Town, City, and ZipCode property. This scenario is easier to manage by making a complex type that contains these properties and then referencing this type in each of the entities.
f Create a complex type 1.
Select the related properties in one of the entities.
2.
Right-click the selection, and then click Refactor into New Complex Type.
3.
Type a name for the type, and then press ENTER.
4.
In the entity, right-click ComplexProperty, and then click Rename.
5.
Type a meaningful name, and then press ENTER.
2-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The complex type is added to the model, and the related properties in the entity you were working on have been replaced with a ComplexProperty property of the complex type.
f Use a complex type 1.
In another entity with the same related properties, delete the related properties.
2.
Right-click the entity header, point to Add, and then click Complex Property.
3.
Type a name for the property, and then press ENTER.
4.
In the Properties pane, set the Type property of the complex type to the complex type you want to use.
Question: Describe an example where you could use complex types in one of your applications.
Building Entity Data Models
Lab: Using Entity Data Models
Objectives After completing this lab, you will be able to: •
Generate an EDM.
•
Add entities and associations to an EDM.
•
Use the Generate Database Wizard.
•
Map entities to multiple tables.
•
Implement an inheritance hierarchy.
•
Use stored procedures in an EDM.
•
Create a complex type.
2-43
2-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Introduction In this lab, you will generate an EDM, add entities and associations to it, generate new tables in the database for the new entities, and implement an inheritance hierarchy. You will also implement entity splitting, access stored procedures, and create a complex type.
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-02 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
Building Entity Data Models
2-45
Lab Scenario
You have been asked to modify the corporate data model to implement a new customer rewards program. Customers will be awarded points when they purchase items from Adventure Works, and they can choose to exchange them for air miles, supermarket vouchers, or discounts on future Adventure Works purchases. You must keep track of the rewards offered and the reward claims made by customers. Two or more customers may combine their points to claim a better reward. The current points held by a customer have already been added to the Contact table. You have been asked to update the database to store current and inactive customers in separate tables, but you must ensure that this does not impact on the model or the application. You must also add a stored procedure to the model to track the number of orders a customer has placed and refactor some of the properties in entities in the model.
2-46
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Exercise 1: Generating an EDM from AdventureWorks Scenario In this exercise, you will create a new EDM from the AdventureWorks database, creating one entity per table. The tables you will use are Contact, SalesTerritory, SalesOrderHeaders, and StoreContact. You will browse the logical and conceptual models and the mappings in the EDM Designer. Finally, you will review the XML for the EDM. The main tasks for this exercise are as follows: 1.
Prepare the AdventureWorks database for the lab.
2.
Open the starter project.
3.
Create the AdventureWorks Entity Data Model.
4.
Review the AdventureWorks model.
5.
Modify the SalesTerritory entity by using XML.
f Task 1: Prepare the AdventureWorks database for the lab 1.
Log on to the 10265A-GEN-DEV-02 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Labfiles folder, run AWReset.bat.
f Task 2: Open the starter project 1.
Open Visual Studio 2010.
2.
Open the existing solution, DAL.sln in the E:\Labfiles\Lab02\VB\Ex1\Starter\DAL or E:\Labfiles\Lab02\CS\Ex1\Starter\DAL folder.
f Task 3: Create the AdventureWorks Entity Data Model •
Add a new ADO.NET EDM to the DAL project. Generate the EDM from the AdventureWorks SQL Server database and create entities for the Contact, SalesOrderHeader, SalesTerritory, and StoreContact tables.
Building Entity Data Models
2-47
f Task 4: Review the AdventureWorks model 1.
Review the four entities shown in the Entity Designer pane and the associations between them.
2.
Open the Mapping Details pane and review the mappings for each entity in the model.
3.
Close the Entity Designer pane.
f Task 5: Modify the SalesTerritory entity by using XML 1.
Open the AdventureWorksEDM EDM in the XML Editor.
2.
Locate the CSDL section of the model.
3.
Locate the SalesTerritory entity in the CSDL section of the model.
4.
Find the Name property, and then change its Name attribute to TerritoryName.
5.
Save the model and close the XML Editor window.
6.
Open AdventureWorksEDM.edmx in the Entity Designer pane, verify that the change has been made, and then change the property back to Name.
7.
Save and close the solution.
Exercise 2: Adding Entities and Associations Scenario In this exercise, you will modify the EDM by adding new entities to it to support the new customer rewards program. You will create a new entity named Reward. This entity will provide access to information about the current rewards that Adventure Works offers. The Reward entity will contain the following properties: RewardID, RewardType (for example, AM for air miles, SM for supermarket vouchers, and AW for Adventure Works), RewardName (for example, 100 air miles or $10 off at Adventure Works), NumberOfAirMilesRequired, PointsPerAirMile, Destination, MoneyBackPerPoint, NumberOfPointsRequired, and Product. You will then create a second new entity named RewardsClaimed. This entity will provide access to the customer claims for rewards in the program. It will have the following properties: ClaimID and PointsUsed. The PointsUsed property must be
2-48
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
tracked because Adventure Works permits families to combine points; therefore, the points used by a customer are not necessarily the price of the reward. Next, you will add a 1:n association between the Reward and RewardsClaimed entities, changing the name of the RewardRewardID property to RewardID. You will also add a 1:n association between the Contact and RewardsClaimed entities, changing the name of the ContactContactID property to ContactID. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Add the Reward entity.
3.
Add the RewardsClaimed entity.
4.
Add the Reward entity to the RewardsClaimed association.
5.
Add the Contact entity to the RewardsClaimed association.
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex2\Starter\DAL or E:\Labfiles\Lab02\CS\Ex2\Starter\DAL folder.
f Task 2: Add the Reward entity 1.
Open the AdventureWorksEDM model in the Entity Designer pane.
2.
Add a new entity named Reward with a Key Property name of RewardID and an EntitySet name of Reward to the model.
3.
Add the scalar properties described in the following table to the entity. Property name
Type
Scale
Nullable Default Value
RewardType
String
not available
True
AW
RewardName
String
not available
False
(None)
NumberOfAirMilesRequired
Int32
not available
True
(None)
PointsPerAirMile
Int32
not available
True
(None)
Destination
String
not available
True
(None)
Building Entity Data Models
Property name
Type
Scale
2-49
Nullable Default Value
MoneyBackPerPoint
Decimal
2
True
(None)
NumberOfPointsRequired
Int32
not available
True
(None)
Product
String
not available
True
(None)
f Task 3: Add the RewardsClaimed entity 1.
Add a new entity named RewardsClaimed with a Key Property name of ClaimID and an Entity Set name of RewardsClaimed to the model.
2.
Add the scalar property described in the following table to the entity. Property name PointsUsed
Type Int32
f Task 4: Add the Reward entity to the RewardsClaimed association 1.
Add a one-to-many association between the Reward and RewardsClaimed entities.
2.
In the RewardsClaimed entity, rename the new RewardRewardID property to RewardID.
3.
In the Reward entity, rename the RewardsClaimeds navigation property to RewardsClaimed.
f Task 5: Add the Contact entity to the RewardsClaimed association 1.
Add a one-to-many association between the Contact and RewardsClaimed entities.
2.
Rename the new ContactContactID property in the RewardsClaimed entity to ContactID.
3.
Rename the Navigation Property in the Contact entity to RewardsClaimed.
4.
Save and close the solution.
2-50
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Exercise 3: Using the Generate Database Wizard Scenario In this exercise, you will use the Generate Database Wizard to generate a TransactSQL script for the model. You will then remove the Transact-SQL code that creates the Customers, SalesTerritory, SalesOrderHeaders, and StoreContact tables and their keys from the script to ensure that it does not overwrite the existing data in the database. You will then execute the Transact-SQL script to create the new tables in the database and review the mappings generated in your model. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Modify the Database Schema Name property.
3.
Generate Transact-SQL script of the model.
4.
Modify the generated script.
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex3\Starter\DAL or E:\Labfiles\Lab02\CS\Ex3\Starter\DAL folder.
f Task 2: Modify the Database Schema Name property 1.
Open the AdventureWorksEDM model in the Entity Designer.
Note: The Error List shows that the Reward and RewardsClaimed entities are not mapped. This is because you created the entities in the last exercise and have not yet mapped them to any database objects. You will resolve this error later in this exercise.
2.
Change the Database Schema Name property to Sales.
f Task 3: Generate Transact-SQL script of the model •
Run the Generate Database Wizard to script the AdventureWorksEDM model.
Building Entity Data Models
2-51
f Task 4: Modify the generated script 1.
Remove the Dropping existing FOREIGN KEY constraints section from the script.
2.
Remove the Dropping existing tables section from the script.
3.
Remove the code that creates the following existing tables in the database: Contacts, SalesOrderHeaders, SalesTerritories, and StoreContacts.
4.
Remove the code that creates the existing primary keys on the following tables in the database: Contacts, SalesOrderHeaders, SalesTerritories, and StoreContacts.
5.
Remove the code that creates the existing foreign keys on the following tables in the database: SalesOrderHeaders and StoreContacts.
6.
Modify the code that creates the foreign key on [ContactID] in the RewardsClaimed table to change the schema for the Contact table to Person and to singularize the table name to Contact.
7.
Validate and then execute the script against the 10265A-GEN-DEV \SQLExpress database engine.
8.
Build the solution.
Note: The errors in the Error List have been cleared and the solution builds successfully because the Reward and RewardsClaimed entities are now mapped to the tables that you have just created.
9.
Save and close the solution.
Exercise 4: Mapping Entities to Multiple Tables Scenario In this exercise, you will use the Update Wizard to add the InactiveStoreContact table to the EDM. You will then modify the StoreContact entity to contain information from both the StoreContact and InactiveStoreContact tables. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Create the InactiveStoreContact table.
2-52
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
3.
Update the model from the database.
4.
Map the StoreContact entity to the InactiveStoreContact table.
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex4\Starter\DAL or E:\Labfiles\Lab02\CS\Ex4\Starter\DAL folder.
f Task 2: Create the InactiveStoreContact table 1.
Open the InactiveStoreContact.sql script from the E:\Labfiles\Lab02\VB\Ex4\Starter or E:\Labfiles\Lab02\CS\Ex4\Starter folder.
2.
Validate and then execute the script against the 10265A-GEN-DEV \SQLExpress database engine.
f Task 3: Update the model from the database 1.
Open the AdventureWorksEDM model in the Entity Designer.
2.
Update the model from the database to add the InactiveStoreContacts table to the model.
Note: Due to an issue with the prerelease version of the Entity Designer, the mappings for the original table have been lost. To resolve this issue, it is necessary to delete the model from the project, re-create the model, and re-create any default values.
3.
Delete the model from the project.
4.
Add a new ADO.NET EDM named AdventureWorksEDM.edmx to the DAL project. Generate the data model from the AdventureWorks database, and create entities for the Contact, InactiveStoreContact, Reward, RewardsClaimed, SalesOrderHeader, SalesTerritory, and StoreContact tables.
5.
Change the Database Schema Name property to Sales.
6.
Rename the Navigation Property in the Reward entity to RewardsClaimed.
7.
Rename the Navigation Property in the Contact entity to RewardsClaimed.
Building Entity Data Models
2-53
f Task 4: Map the StoreContact entity to the InactiveStoreContact table 1.
Open the Mapping Details pane, and then map the StoreContact entity to the InactiveStoreContact table.
2.
In the Entity Designer pane, delete the InactiveStoreContact entity.
3.
Build the solution.
4.
Save and close the solution.
Exercise 5: Implementing an Inheritance Hierarchy Scenario In this exercise, you will create three new entities: AirMilesReward, SupermarketReward, and AdventureWorksReward. You will move the NumberOfAirMilesRequired, PointsPerAirMile, and Destination properties to the AirMilesReward entity, the MoneyBackPerPoint property to the SupermarketReward entity, and the NumberOfPointsRequired and Product properties to the AdventureWorksReward entity. You will add a condition to each of the new entities so that they only include data when the RewardType property is the appropriate type. For example, for the AirMilesReward, the condition will be When RewardType = AM. Finally, you will review the entity types generated by the EDM to implement this hierarchy. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Add the AirMilesReward entity.
3.
Add the SupermarketReward entity.
4.
Add the AdventureWorksReward entity.
5.
Map the new entities to the Reward table.
6.
Add conditions to the mappings.
7.
Assign properties to the new entities.
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex5\Starter\ DAL or E:\Labfiles\Lab02\CS\Ex5\Starter\DAL folder.
2-54
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 2: Add the AirMilesReward entity 1.
Open AdventureWorksEDM in the Entity Designer pane.
2.
Add a new entity named AirMilesReward based on the Reward entity.
f Task 3: Add the SupermarketReward entity •
Add a new entity named SupermarketReward based on the Reward entity.
f Task 4: Add the AdventureWorksReward entity •
Add a new entity named AdventureWorksReward based on the Reward entity.
f Task 5: Map the new entities to the Reward table 1.
Open the Mapping Details pane, and then map the AirMilesReward entity to the Reward table.
2.
In the Mapping Details pane, map the SupermarketReward entity to the Reward table.
3.
In the Mapping Details pane, map the AdventureWorksReward entity to the Reward table.
f Task 6: Add conditions to the mappings 1.
Add a condition to the AirMilesReward mapping to specify that this entity should contain rewards only if the RewardType field is AM.
2.
Add a condition to the SupermarketReward mapping to specify that this entity should contain rewards only if the RewardType field is SM.
3.
Add a condition to the AdventureWorksReward mapping to specify that this entity should contain rewards only if the RewardType field is AW.
4
Remove the RewardType property from the Reward entity.
5.
Make the Reward entity abstract.
Building Entity Data Models
2-55
f Task 7: Assign properties to the new entities 1.
Move the NumberOfAirMilesRequired property to the AirMilesReward entity.
2.
Move the PointsPerAirMile property to the AirMilesReward entity.
3.
Move the Destination property to the AirMilesReward entity.
4.
Move the MoneyBackPerPoint property to the SupermarketReward entity.
5.
Move the NumberOfPointsRequired property to the AdventureWorksReward entity.
6.
Move the Product property to the AdventureWorksReward entity.
7.
Map the AirMilesReward properties to the appropriate columns in the Reward table.
8.
Map the SupermarketReward properties to the appropriate columns in the Reward table.
9.
Map the AdventureWorksReward properties to the appropriate columns in the Reward table.
10. Build the solution. 11. Save and close the solution.
Exercise 6: Using Stored Procedures Scenario In this exercise, you will add the uspCountOrders stored procedure to the EDM and add a new function import to map to the stored procedure. This stored procedure will return a count of the number of orders that a given contact has made. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Add the uspCountOrders stored procedure to the model.
3.
Add a function import to the model.
2-56
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex6\Starter or E:\Labfiles\Lab02\CS\Ex6\Starter folder.
f Task 2: Add the uspCountOrders stored procedure to the model 1.
Open AdventureWorksEDM in the Entity Designer pane.
2.
Run the Update Wizard to add the uspCountOrders stored procedure to the model.
Note: Four errors will appear in the Error List because you have updated the model from the database and the mappings for the inheritance hierarchy that you created in Exercise 5 have been lost. These errors will not impact this exercise, so you can ignore the errors and continue with the next task.
f Task 3: Add a function import to the model 1.
Add a function import named CountOrders to the model. Map it to the uspCountOrders stored procedure and configure it to return a collection of scalar Int32 data types.
2.
Build the solution.
3.
Save and close the solution.
Exercise 7: Creating a Complex Type Scenario In this exercise, you will refactor the Name, CountryRegionCode, and Group properties in the SalesTerritory entity into a new complex type named LocationType. You will then create a complex property of this type in the SalesTerritory entity, named Location. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Create the complex type.
Building Entity Data Models
2-57
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab02\VB\Ex7\Starter or E:\Labfiles\Lab02\CS\Ex7\Starter folder.
f Task 2: Create the complex type 1.
Open AdventureWorksEDM in the Entity Designer pane.
2.
In the SalesTerritory entity, create a complex type named LocationType from the Name, CountryRegionCode, and Group properties.
3.
Change the name of the new complex property in the SalesTerritory entity to Location.
4.
Build the solution.
5.
Save and close the solution, and then close Visual Studio.
2-58
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Review
Review Questions 1.
What information does the CSDL section of the model store?
2.
Name three types of association that the Entity Designer supports.
3.
What does the Database Schema Name property of a model control?
4.
Why would you map one entity to more than one table?
5.
How do you configure an entity to inherit from another entity?
6.
Name three types of collection that a stored procedure can return.
7.
Why might you want to create a complex type?
Building Entity Data Models
2-59
Module Review and Takeaways
Review Questions 1.
What key advantage does the use of a conceptual model provide?
2.
Why might you want to modify an EDM that you have created from a database?
3.
Which Entity Framework feature enables you to derive entity types from other entity types?
Best Practices Related to Designing an EDM Supplement or modify the following best practices for your own work situations: •
Use a conceptual model to disconnect your application from the database engine and database structure.
•
Before you start to design a model in any of the available tools, you must first plan and sketch the model.
•
Do not edit the element of the XML file.
2-60
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Best Practices Related to Deploying an EDM Supplement or modify the following best practices for your own work situations: •
When you deploy an application that contains an EDM, store the model and mapping files externally to the assembly.
Best Practices Related to Generating Transact-SQL Scripts Supplement or modify the following best practices for your own work situations: •
Ensure that you fully review a script before you execute it against the database so that you do not accidently recreate existing objects.
Querying Entity Data
3-1
Module 3 Querying Entity Data Contents: Lesson 1: Retrieving Data by Using LINQ to Entities
3-4
Lesson 2: Retrieving Data by Using Entity SQL
3-19
Lesson 3: Retrieving Data by Using the EntityClient Provider
3-26
Lesson 4: Retrieving Data by Using Stored Procedures
3-38
Lesson 5: Unit Testing Your Data Access Code
3-43
Lab: Querying Entity Data
3-52
3-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
The Entity Framework enables you to retrieve data from a database. You can run queries against the conceptual model, and these queries can return data as objects. This means that you do not need a detailed knowledge of the structure of the underlying database, and you can work with ordinary Microsoft® .NET Framework objects that represent the entities in your model. The Entity Framework enables you to use several different technologies to define and run these queries. You can choose the style of query that is most suitable for the specific task that you need to perform. This module explains how to use Language-Integrated Query (LINQ) to Entities, Entity Structured Query Language (Entity SQL), the EntityClient provider for the Entity Framework, and stored procedures to retrieve data from your entity model, and describes when you should use each approach.
Querying Entity Data
Objectives After completing this module, you will be able to: •
Retrieve data by using LINQ to Entities.
•
Retrieve data by using Entity SQL.
•
Retrieve data by using the EntityClient provider.
•
Retrieve data by using stored procedures in the entity model.
•
Create unit tests for your data access code.
3-3
3-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 1
Retrieving Data by Using LINQ to Entities
LINQ to Entities enables you to create powerful, strongly typed queries to run against the conceptual data model that is defined in an Entity Data Model (EDM). LINQ expressions and the LINQ standard query operators enable you to write strongly typed, composable queries directly from the development environment in a syntax that is similar to Transact-SQL. The queries are expressed in the programming language itself and not as string literals embedded in the application code, so the compiler can catch most common errors. This minimizes the potential for run-time errors and results in a more robust application. You write your queries to run against the entities and relationships that are defined in the conceptual model. When an application uses the EDM, the Entity Framework automatically handles the mapping between the conceptual data model and the underlying data source. This enables you to change the back-end data source without requiring changes to the client application, because the Entity Framework handles most database-specific features.
Querying Entity Data
Objectives After completing this lesson, you will be able to: •
Describe the role of the ObjectContext class.
•
Create a LINQ to Entities query by using the ObjectQuery class.
•
Perform a query against the EDM and understand how LINQ to Entities materializes objects.
3-5
3-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Connecting to the Conceptual Model
Key Points The conceptual model defines the entities with which your application can interact, in the form of .NET Framework objects. The primary class for interacting with these objects is the ObjectContext class. The key features of the ObjectContext class are: •
It handles the underlying connection to the database.
•
It holds the model’s descriptive metadata.
•
It tracks changes that are made to objects by using the ObjectStateManager class.
Note: You can find the connection string that the ObjectContext object uses to connect to the database in the app.config file.
Querying Entity Data
3-7
Note: For security reasons, you should consider encrypting the connection information, especially if passwords are stored in the configuration file. Note: The Entity Framework will automatically take advantage of connection pooling if it is available.
When you use the tools in Microsoft Visual Studio® to create an EDM, Visual Studio automatically generates several classes from the model that enable your application to interact with the database. One of these classes derives from the ObjectContext class, and the EntityContainer name property in the EDM determines the name of this class. To perform queries that run against the entities that are defined in the conceptual model, you first need to obtain a reference to the ObjectContext object. In the following code example, the name of the derived class that Visual Studio generates is AdventureWorksEntities. This derived class enables you to write code to perform queries against entities such as Contacts and Rewards that are defined in the Adventure Works EDM. [Visual Basic] Using entities As New AdventureWorksEntities() … End Using
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { … }
3-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Note: You should always make sure that the ObjectContext object is disposed of when you have finished with it.
Question: What will be the impact of adding a new entity type to the conceptual model on the custom ObjectContext class?
Additional Reading For more information about connection strings in the Entity Framework, see the Connection Strings (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194020. For more information about connection strings in ADO.NET, see the Connection Strings (ADO.NET) page at http://go.microsoft.com/fwlink/?LinkID=194021.
Querying Entity Data
3-9
Defining the Query
Key Points To define a LINQ to Entities query, you first need to construct an ObjectQuery instance from the ObjectContext object. The ObjectQuery generic class in the System.Data.Objects namespace represents a query that returns a collection of zero or more typed entities of the specified type. The following code example shows how to create an ObjectQuery object that you can use to query the StoreContacts entity set. After you execute the query, this entity set will contain StoreContacts entities that are retrieved from the database. You can use these StoreContacts entities to display data in a client application, or to manipulate the data as part of a business process in your application. [Visual Basic] Using entities As New AdventureWorksEntities() Dim contacts As ObjectQuery(Of StoreContact) = entities.StoreContacts … End Using
3-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { ObjectQuery<StoreContact> contacts = entities.StoreContacts; … }
You can then construct a LINQ query that defines the data that you want to return based on this ObjectQuery object. You can use either query expression syntax or method-based syntax for your LINQ queries. Your queries can use the full range of LINQ syntax to construct queries that can: •
Filter data.
•
Order data.
•
Perform grouping and aggregate data.
•
Use join operators.
•
Navigate relationships.
•
Page through data by using Skip and Join.
The following code example shows how to define a query that returns all of the contacts stored in the Adventure Works database by using LINQ expression syntax.
[Visual Basic] Using entities As New AdventureWorksEntities() Dim contacts As ObjectQuery(Of StoreContact) = entities.StoreContacts Dim query As IQueryable(Of StoreContact) = From c In contacts Select c … End Using
Querying Entity Data
3-11
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { ObjectQuery<StoreContact> contacts = entities.StoreContacts; IQueryable<StoreContact> query = from c in contacts select c; … }
You can also pass parameters to LINQ queries. The following code example returns all of the StoreContacts entities in the Adventure Works database with a contact type of 5. [Visual Basic] Using entities As New AdventureWorksEntities() Dim typeID As Integer = 5 Dim contacts As ObjectQuery(Of StoreContact) = entities.StoreContacts Dim query As IQueryable(Of StoreContact) = From c In contacts Where c.ContactTypeID = 5 Select c … End Using
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { int typeID = 5; ObjectQuery<StoreContact> contacts = entities.StoreContacts; IQueryable query = from c in contacts where c.ContactTypeID = 5 select c; … }
The following code example shows how you can also return data as primitive types instead of as entity objects. This is useful if you do not need access to all of the data that is held in the entity object, for example, to populate a list in the user interface.
3-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] Using entities As New AdventureWorksEntities() Dim typeID As Integer = 5 Dim contacts As ObjectQuery(Of StoreContact) = entities.StoreContacts Dim query As IQueryable(Of StoreContact) = From c In contacts Where c.ContactTypeID = 5 Select c.ContactID … End Using
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { int typeID = 5; ObjectQuery<StoreContact> contacts = entities.StoreContacts; IQueryable query = from c in contacts where c.ContactTypeID = 5 select c.ContactID; … }
Note: To follow best practice, you should adopt the habit of creating unit tests for your data access code. This topic is described later in this module.
Question: How do you obtain a typed ObjectQuery instance?
Additional Reading For more examples that show how to use LINQ to Entities, see the LINQ to Entities page at http://go.microsoft.com/fwlink/?LinkId=196094. For more information about the methods that LINQ to Entities supports, see the Supported and Unsupported LINQ Methods (LINQ to Entities) page at http://go.microsoft.com/fwlink/?LinkID=194023. For more information about the behavior of expressions in LINQ to Entities, see the Expressions in LINQ to Entities Queries at http://go.microsoft.com/fwlink/?LinkID=194024.
Querying Entity Data
For an example that shows how to page through data, see the How to: Page Through Query Results (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194025.
3-13
3-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Executing the Query
Key Points After you have defined your query, you need to execute the query and use the results. Materialization is the term that is used to refer to the process of returning the query results to your application as entity objects.
Materializing Objects The following code example shows how you can materialize entity objects by running the LINQ query and returning the results in a collection. You can also iterate through the returned entities individually by using a for…each construct. You can return the list of Contacts to a client application for display in the user interface, or to be consumed by a business process.
[Visual Basic] Using entities = New AdventureWorksEntities() Dim contacts As ObjectQuery(Of Contact) = entities.Contacts contacts.MergeOption = MergeOption.NoTracking
Querying Entity Data
3-15
Dim query As IQueryable(Of Contact) = From c In contacts Select c Dim results As List(Of Contact) = query.ToList() … End Using
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { ObjectQuery contacts = entities.Contacts; contacts.MergeOption = MergeOption.NoTracking; IQueryable query = from c in contacts select c; List results = query.ToList(); … }
Note: The ObjectContext object raises the ObjectMaterialized event whenever an entity object is created from data in the database as part of a query or load operation.
The following table describes how the different MergeOption values affect the way in which data is loaded into the ObjectContext object. MergeOption
Description
AppendOnly
Objects that already exist in the object context are not loaded from the data source. This is the default behavior for queries.
OverwriteChanges
Objects are always loaded from the data source. Any property changes that are made to objects in the object context are overwritten by the data source values.
PreserveChanges
Objects are always loaded from the data source. However, any property changes that are made to objects in the object context are preserved.
NoTracking
Objects are maintained in a Detached state and are not tracked in the ObjectStateManager object.
3-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
You can access all of the properties of the entity objects that you defined in the conceptual model. This gives you access to the column data from the underlying database. The following code example shows how you can access the last name of a contact. [Visual Basic] Dim contact As Contact = results(0) Dim lastName As String = contact.LastName
[Visual C#] Contact contact = results[0]; string lastName = contact.LastName;
You can also navigate to other entities by using the navigation properties that are defined in the entity model. The Entity Framework will only materialize entities that are retrieved in this way when they are first accessed programmatically; this behavior is known as lazy loading. The following code example shows how you can access all of the sales order header information for a contact if you need to use the sales order data in your client application. [Visual Basic] Dim contact As Contact = results(0) Dim orders As EntityCollection(Of SalesOrderHeader) = contact.SalesOrderHeaders
[Visual C#] Contact contact = results[0]; EntityCollection<SalesOrderHeader> orders = contact.SalesOrderHeaders;
If you build your entity model by using the Visual Studio tools, the Entity Framework will automatically enable lazy loading. Otherwise, you have to configure the ObjectContext object to perform lazy loading, as the following code example shows. [Visual Basic] entities.ContextOptions.LazyLoadingEnabled = True
Querying Entity Data
3-17
[Visual C#] entities.ContextOptions.LazyLoadingEnabled = true;
Note: You may want to wrap the contents of the entity object in a custom business object before passing the data to another tier in your application.
Entity Keys Every entity object has an EntityKey property that contains a unique identifying value for that entity. Typically, the EDM maps the EntityKey property to the primary key column in the underlying database. After the Entity Framework has materialized an object, the ObjectContext object caches the entity object for the lifetime of the ObjectContext object . Therefore, you can subsequently access the entity object without the need to requery the database. To troubleshoot a problem, or to better understand how the Entity Framework is processing a query, you can ask the Entity Framework about the commands that it sends to the underlying database. The following code example shows how you can view the commands that the Entity Framework sends to the underlying database. You could include calls to ToTraceString as a part of any logging feature that you add to your data access layer code. [Visual Basic] Console.WriteLine(DirectCast(query, ObjectQuery(Of Contact)).ToTraceString())
[Visual C#] Console.WriteLine(((ObjectQuery)query).ToTraceString());
Question: Define the term materialization.
Additional Reading For more information about how to enable lazy loading, see the How to: Use Lazy Loading to Load Related Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194026.
3-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
For more information about how to explicitly load related objects, see the How to: Explicitly Load Related Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194027. For more information about deferred and immediate execution, see the Query Execution page at http://go.microsoft.com/fwlink/?LinkID=194028.
Querying Entity Data
3-19
Lesson 2
Retrieving Data by Using Entity SQL
Entity SQL is an alternative to using LINQ to Entities. It enables you to use an SQL-like language to query your conceptual model. Conceptual models represent data as entities and relationships, and Entity SQL enables you to query those entities and relationships by using a syntax that is similar to traditional SQL, and to return entity objects. Entity SQL is useful when you need to construct queries dynamically at run time; when you want to store the query as a part of the model definition; or if you are already an expert in SQL-based query languages.
Objectives After completing this lesson, you will be able to: •
Understand the syntax of Entity SQL.
•
Describe how to use parameters and execute queries by using Entity SQL.
3-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Entity SQL Syntax
Key Points Entity SQL is a database-independent dialect of SQL that works directly with your conceptual model. It directly supports EDM concepts such as inheritance and relationships, and returns data as entity objects to your application. The Entity Framework converts Entity SQL into database-specific queries to run against the underlying database. Entity SQL queries are defined by using strings in your application code. The following code example shows a simple query that returns all of the Contacts entities in the Adventure Works EDM. SELECT VALUE c from AdventureWorksEntities.Contacts as c
When you execute this query, it will return a collection of Contact entity objects. AdventureWorksEntities.Contacts refers to the Contacts entity set that is defined in the AdventureWorksEntities EDM. As you can see, the syntax is very similar to traditional SQL.
Querying Entity Data
3-21
The following code example shows how you can sort the results, in this example, by LastName. SELECT VALUE c from AdventureWorksEntities.Contacts as c Order By c.LastName
You can use aggregate functions, for example, Average, Sum, Min, Max, and Count. The following code example shows how you can calculate an average of the TotalDue values for each contact. SELECT contactID, AVG(order.TotalDue) FROM AdventureWorksEntities.SalesOrderHeaders AS order GROUP BY order.Contact.ContactID as contactID
Your Entity SQL queries can use join operators to link data when no navigation property is defined in the entity model. Entity SQL supports cross joins, inner joins, and outer joins just like traditional SQL.
Specifying Parameters Your Entity SQL queries can also contain placeholders for parameter values. The placeholder name always begins with an at sign (@). The following code example shows a parameter placeholder called @lastName. You must provide a suitable value for this placeholder at run time. SELECT VALUE c from AdventureWorksEntities.Contacts as c WHERE c.LastName=@lastName
Querying Data with Table-Per-Type Inheritance The Adventure Works EDM uses table-per-type inheritance to model the different types of rewards that customers can claim, such as Airmiles rewards, supermarket rewards, and Adventure Works rewards. The following code example shows how Entity SQL syntax supports this type of model. The query only returns rewards of the AdventureWorksRewards subtype. SELECT VALUE r FROM OfType(AdventureWorksEntities.Rewards, AdventureWorksModel.AdventureWorksReward) AS r
You can page through large collections of entities by using the SKIP and LIMIT clauses in your Entity SQL query. Question: List some of the advantages and disadvantages of using Entity SQL instead of LINQ for Entities.
3-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Additional Reading For more information about Entity SQL, see the Entity SQL Language page at http://go.microsoft.com/fwlink/?LinkID=194029.
Querying Entity Data
3-23
Using Entity SQL
Key Points The following code example shows how you can execute queries that are defined by using Entity SQL. They run in the same way as LINQ to Entities queries, by using an ObjectQuery object. This example shows how to retrieve all of the Contact entities from the Adventure Works database. Notice that, in this case, you must instantiate a new ObjectQuery object, passing the query string and the ObjectContext object to the constructor. [Visual Basic] Using entities = New AdventureWorksEntities() Dim queryString As String = “SELECT VALUE c from AdventureWorksEntities.Contacts as c" Dim query As ObjectQuery(Of Contact) = New ObjectQuery(Of Contact)(queryString, entities) query.MergeOption = MergeOption.NoTracking Dim results As List(Of Contact) = query.ToList() … End Using
3-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { string queryString = @"SELECT VALUE c from AdventureWorksEntities.Contacts as c"; ObjectQuery query = new ObjectQuery(queryString, entities); query.MergeOption = MergeOption.NoTracking; List results = query.ToList(); … }
Note: Any syntax errors in your Entity SQL are likely to cause an EntitySQLException exception at run time.
If you are building an Entity SQL query dynamically, you need to take special care to avoid any EntitySQLException exceptions. In this case, you should consider using the query builder methods of ObjectQuery instead of constructing an Entity SQL query string at run time.
Using Parameters If your Entity SQL code contains parameter placeholders, you must provide values of the correct type before you execute the query. The following code example shows how you can return contact entities based on the contactID value to your client application. [Visual Basic] Using entities = New AdventureWorksEntities() Dim contactID As Integer = 2019 Dim queryString As String = "SELECT VALUE r from AdventureWorksEntities.RewardsClaimed" & "as r WHERE r.ContactID=@contactID" Dim query As ObjectQuery(Of Contact) = New ObjectQuery(Of Contact)(queryString, entities) query.Parameters.Add(New ObjectParameter("contactID", contactID)) query.MergeOption = MergeOption.NoTracking Dim results As List(Of Contact) = query.ToList() … End Using
Querying Entity Data
3-25
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { int contactID = 2019; string queryString = @"SELECT VALUE r from AdventureWorksEntities.RewardsClaimed as r WHERE r.ContactID=@contactID"; ObjectQuery query = new ObjectQuery(queryString, entities); query.Parameters.Add(new ObjectParameter("contactID", contactID)); query.MergeOption = MergeOption.NoTracking; List results = query.ToList(); … }
Question: Why should you use the ObjectParameter class to supply a parameter instead of performing string manipulation on the query string?
Additional Reading For more information about query plan caching, see the Query Plan Caching (Entity SQL) page at http://go.microsoft.com/fwlink/?LinkID=194030.
3-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 3
Retrieving Data by Using the EntityClient Provider
LINQ to Entities and Entity SQL return entity objects from queries that are executed against the conceptual model. In subsequent modules, you will see how you can use these entity objects to perform data modifications. The EntityClient provider for the Entity Framework enables you to return read-only data as rowsets. This may provide performance benefits at the expense of reduced functionality.
Objectives After completing this lesson, you will be able to: •
Describe the components of the EntityClient provider for the Entity Framework.
•
Describe how to connect to the EDM by using an EntityConnection object.
•
Show how to create and execute a query by using an EntityCommand object.
•
Access the query results by using an EntityDataReader object.
Querying Entity Data
3-27
Understanding the EntityClient Provider for the Entity Framework
Key Points The EntityClient provider enables you to return read-only entity data as rowsets instead of as entity objects. It runs against the EDM, and enables detailed control over the connections and commands that you use to retrieve data. The EntityClient provider classes are defined in the System.Data.Entity namespace. You should consider using the EntityClient provider if you need to access data as rowsets rather than entity objects, or if query performance is critical to your application. You use the EntityConnection class to connect to the EDM by using a connection string. You use the EntityCommand class to define queries in Entity SQL to retrieve data. You use the EntityDataReader class to process the results from running a query. Note: If you are familiar with using providers such as the ADO.NET provider in traditional ADO.NET, you will find that the EntityClient provider operates in a similar fashion.
3-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Question: When should you consider using the EntityClient provider?
Querying Entity Data
3-29
Connecting to the Model by Using an EntityConnection Object
Key Points When you use LINQ to Entities and Entity SQL, the ObjectContext class is responsible for opening and closing connections to the underlying database. However, when you use the EntityClient provider, it becomes your responsibility to manage the connection. You can then use this connection with EntityCommand objects to query the database. The following code example shows one way to establish a connection to the Adventure Works database in preparation for executing an EntityCommand query. [Visual Basic] Using connection = New EntityConnection("name=AdventureWorksEntities") ' Open the connection connection.Open() … connection.Close() End Using
3-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] using (EntityConnection connection = new EntityConnection("name=AdventureWorksEntities")) { // Open the connection connection.Open(); … connection.Close(); }
The connection string in the app.config file specifies both the model to use with its metadata and mapping information, and the connection details to the underlying database. The following code example shows an example connection string.
Question: How does working with the EntityConnection class differ from working with the ObjectContext class in relation to opening connections to the database?
Additional Reading For more information about building connection strings, see the Connection Strings (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID= 194031.
Querying Entity Data
3-31
Creating Queries by Using an EntityCommand Object
Key Points You use an EntityCommand object to execute an Entity SQL command against the entity model. The Entity SQL command can be query text or the name of a stored procedure that you have imported into the entity model. The parameters to the EntityCommand constructor specify the Entity SQL command and the connection to use. The following code example shows how you can define a query to return a list of Rewards Claimed records for a specific contact. It demonstrates how to provide the contactID parameter to the EntityCommand objects by using an EntityParameter object. [Visual Basic] Using connection = New EntityConnection("name=AdventureWorksEntities") connection.Open() Dim queryString As String = "SELECT VALUE r from " & "AdventureWorksEntities.RewardsClaimed as r WHERE " & "r.ContactID=@contactID"
3-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using cmd = New EntityCommand(queryString, connection) Dim param As EntityParameter = New EntityParameter() param.ParameterName = "contactID" param.Value = contactID cmd.Parameters.Add(param) … End Using connection.Close() End Using
[Visual C#] using (EntityConnection connection = new EntityConnection("name=AdventureWorksEntities")) { connection.Open(); string queryString = "SELECT VALUE r from AdventureWorksEntities.RewardsClaimed as r WHERE r.ContactID=@contactID"; using (EntityCommand cmd = new EntityCommand(queryString, connection)) { EntityParameter param = new EntityParameter(); param.ParameterName = "contactID"; param.Value = contactID; cmd.Parameters.Add(param); … } connection.Close(); }
You can also create an EntityCommand object from the EntityConnection object, as the following code example shows. [Visual Basic] Dim cmd As EntityCommand = connection.CreateCommand()
[Visual C#] EntityCommand cmd = connection.CreateCommand();
Querying Entity Data
Question: What are the parameters to the EntityCommand constructor?
Additional Reading For further information about EntityCommand objects and the EntityClient provider, see the EntityClient Provider for the Entity Framework page at http://go.microsoft.com/fwlink/?LinkID=194032.
3-33
3-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Processing Query Results by Using an EntityDataReader Object
Key Points The EntityCommand object returns data as primitive types rather than entity objects. If you are familiar with using Command objects in traditional ADO.NET, you will find that EntityCommand objects operate in a similar fashion. You can execute the command in one of three ways as described in the following table. EntityCommand methods
Description
ExecuteReader()
Returns multiple rows and columns.
ExecuteScalar()
Returns the first row/column entry.
ExecuteNonQuery()
Executes a command with no return value.
When you use the ExecuteReader method, you must handle the data that the query returns in an EntityDataReader object. The EntityDataReader object
Querying Entity Data
3-35
returns a single row at a time, and enables you to access the individual fields and their metadata. The important methods and properties of this object include: •
The Read method, which returns the next row in the EntityDataReader object. You must call this method before trying to access any data.
•
The various Get methods, which return metadata and typed data from the current row.
•
The GetBytes method, which reads a stream of bytes from the specified column.
•
The HasRows Boolean property, which indicates whether the EntityDataReader object has one or more rows.
•
The FieldCount property, which tells you how many columns there are in the current row.
The following code example shows the use of an EntityDataReader object to process the list of Rewards Claimed records returned by a query against the Adventure Works database. The CommandBehavior.SequentialAccess parameter of the ExecuteReader method is the default behavior and indicates that the data will be read in a forward-only mode, providing the best performance. [Visual Basic] Dim claim As New RewardsClaimed() Using connection = New EntityConnection("name=AdventureWorksEntities") connection.Open() Dim queryString As String = "SELECT VALUE r from " & "AdventureWorksEntities.RewardsClaimed as r WHERE " & "r.ContactID=@contactID" Using cmd = New EntityCommand(queryString, connection) Dim param = New EntityParameter() param.ParameterName = "contactID" param.Value = contactID cmd.Parameters.Add(param) Using reader As EntityDataReader = cmd.ExecuteReader(CommandBehavior.SequentialAccess) While reader.Read() Dim r As IExtendedDataRecord = reader
3-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
claim.ClaimID = reader.GetInt32(0) claim.RewardID = reader.GetInt32(1) claim.PointsUsed = reader.GetInt32(2) claim.ContactID = reader.GetInt32(3) End While End Using End Using connection.Close() End Using
[Visual C#] RewardsClaimed claim = new RewardsClaimed(); using (EntityConnection connection = new EntityConnection("name=AdventureWorksEntities")) { connection.Open(); string queryString = "SELECT VALUE r from AdventureWorksEntities.RewardsClaimed as r WHERE r.ContactID=@contactID"; using (EntityCommand cmd = new EntityCommand(queryString, connection)) { EntityParameter param = new EntityParameter(); param.ParameterName = "contactID"; param.Value = contactID; cmd.Parameters.Add(param); using (EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { while (reader.Read()) { IExtendedDataRecord r = reader; claim.ClaimID = reader.GetInt32(0); claim.RewardID = reader.GetInt32 (1); claim.PointsUsed = reader.GetInt32(2); claim.ContactID = reader.GetInt32 (3); } } } connection.Close(); }
Querying Entity Data
3-37
Question: Why does using the EntityDataReader class give good performance?
Additional Reading For further information about the Execute methods of the EntityCommand object, see the EntityCommand Class page at http://go.microsoft.com/fwlink/?LinkID=194033. For further information about the EntityDataReader class, see the EntityDataReader Class page at http://go.microsoft.com/fwlink/?LinkID=194034. For further information about EntityCommand objects and the EntityClient provider, see the EntityClient Provider for the Entity Framework page at http://go.microsoft.com/fwlink/?LinkID=194035.
3-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 4
Retrieving Data by Using Stored Procedures
You can use stored procedures to aid security, provide predictability, and encapsulate logic with data inside a database. An EDM can reference stored procedures by using function imports. You can invoke these stored procedures by using the ObjectContext object for the EDM.
Objectives After completing this lesson, you will be able to: •
Describe how to invoke stored procedures in the EDM.
•
Describe how to handle stored procedures with output parameters.
Querying Entity Data
3-39
Invoking Stored Procedures
Key Points You can use stored procedures as part of the mapping in your entity model to support retrieving (and updating) entities. You do not need to do anything special to use these types of stored procedure in your application code. You can continue to query entities by using LINQ to Entities or Entity SQL. If you import stored procedures with input or output parameters into the entity model as function imports, you can call these stored procedures directly from your application code as methods of the ObjectContext class. Note: To create the function import, you must specify the stored procedure to call and the return type of the stored procedure. The function import is not shown in the ADO.NET Entity Data Model Designer (Entity Designer), but with the Entity Designer open, you can view the function and the stored procedure details in the Model Browser window.
The Adventure Works EDM might contain an imported stored procedure called CountAirmilesClaims that returns the number of Airmiles reward claims that a
3-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
contact has placed. It takes a single input parameter that is the contactID, and returns an integer value that is the number of claims that are recorded for the contact. The following code example shows how to invoke this stored procedure. [Visual Basic] Using entities = New AdventureWorksEntities() Return DirectCast( entities.CountAirmilesClaims(contactID).First(), Integer) End Using
[Visual C#] using (AdventureWorksEntities entities = new AdventureWorksEntities()) { return (int)entities.CountAirmilesClaims(contactID).First(); }
Question: What are the two ways in which you can use stored procedures in your entity model?
Querying Entity Data
3-41
Invoking Stored Procedures with Output Parameters
Key Points Stored procedures can have both input and output parameters. To handle output parameters, you use the ObjectParameter class. The following code example shows a stored procedure with one input parameter and one output parameter. CREATE PROCEDURE [dbo].[GetRewardName] @ID int, @RewardName nvarchar(50) OUTPUT AS SELECT @RewardName = Name FROM Reward WHERE RewardID = @ID
If you import the stored procedure into the EDM, you can use the following code example to invoke the stored procedure and retrieve the reward name for the reward with a reward ID of 1.
3-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] Using context = New AdventureWorksEntities() Dim name As New ObjectParameter("RewardName", GetType(String)) context.GetRewardName(1, name) Console.WriteLine(name.Value) End Using
[Visual C#] using (AdventureWorksEntities context = new AdventureWorksEntities ()) { ObjectParameter name = new ObjectParameter("RewardName", typeof(String)); context.GetRewardName(1, name); Console.WriteLine(name.Value); }
Question: Which class do you need to use to handle an output parameter for a stored procedure that is embedded in your EDM?
Querying Entity Data
3-43
Lesson 5
Unit Testing Your Data Access Code
In many development environments, it is standard practice to create unit tests for your code. Data access code that uses the Entity Framework is no exception. Organizing your data access code appropriately can streamline the way in which you create your unit tests. Centralizing all of your data access code in a data access layer component is one approach to take. Visual Studio has many built-in tools to help you create and manage your suite of unit tests. You must also establish a procedure for ensuring that the database is in a known state before you run any tests because many tests will involve comparing the data in the database with known values.
Objectives After completing this lesson, you will be able to: •
Describe an approach for unit testing your entity model.
•
Create a unit test to run against your entity model.
3-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Establishing a Unit-Testing Environment
Key Points It is important to follow a well-documented and systematic process to ensure that your testing is accurate and repeatable. The following steps outline the tasks that you can perform to establish a unit-testing environment for your data access code: 1.
Develop a set of scripts, or backup and restore operations, which you can use to return the database to a known state before you run any unit tests that will be referencing data in the database.
2.
Organize your code so that all of your data access code is found in a single class or component, typically, a data access layer that can be tested in isolation.
3.
Add a unit test project to your Visual Studio solution. This project is where you will write your unit test code and manage your unit tests.
4.
In the unit test project, you should write methods that create suitable dummy data that you can use if you need to check values in the database.
5.
In the unit test project, write your unit tests to verify that your data access code functions correctly.
Querying Entity Data
6.
3-45
Establish procedures to ensure that the unit tests are run on a regular basis, or as a part of your standard build. Visual Studio also enables you to run unit tests on an informal basis; this is particularly useful to help you check that any refactoring that you perform will not break your application.
Question: Will all of your unit tests for your data access layer need to touch the database?
Additional Reading For more information about the unit-testing tools in Visual Studio, see the Verifying Code by Using Unit Tests page at http://go.microsoft.com/fwlink/?LinkID=194036.
3-46
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Creating Unit Tests
Key Points When you create a unit test project in Visual Studio, it creates the framework that is required to create, run, and manage your unit tests. You can add new skeleton tests for methods by right-clicking in a code file and then clicking Create Unit Test. Before you create your unit test, you may need to define some test data that you will use as a comparison with the data that you retrieve from the database. The following code example shows a unit test method that creates two sample RewardsClaimed objects that you can use in your unit tests. [Visual Basic] Private Function GetLocalRewardsClaimedList() As List(Of RewardsClaimed) Dim rewards As New List(Of RewardsClaimed)() Dim claim1 As New RewardsClaimed With { .ClaimID = 2,
Querying Entity Data
.PointsUsed = 20000, .RewardID = 2, .ContactID = 2 } rewards.Add(claim1) Dim claim2 As New RewardsClaimed With { .ClaimID = 11, .PointsUsed = 25000, .RewardID = 21, .ContactID = 2 } rewards.Add(claim2) Return rewards End Function
[Visual C#] private List GetLocalRewardsClaimedList() { List rewards = new List(); RewardsClaimed claim1 = new RewardsClaimed { ClaimID = 2, PointsUsed = 20000, RewardID = 2, ContactID = 2 }; rewards.Add(claim1); RewardsClaimed claim2 = new RewardsClaimed { ClaimID = 11, PointsUsed = 25000, RewardID = 21, ContactID = 2 }; rewards.Add(claim2); return rewards; }
3-47
3-48
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Note: This code example uses instances of your entity classes. If your data access layer uses data transfer objects, you should create instances of the data transfer objects instead.
You can then fill in the automatically generated skeleton unit test methods with code to perform your unit test logic. The following code example compares two records that are retrieved from the EDM by using a method in the data access layer against the sample data that is created in the GetLocalRewardsClaimedList method. [Visual Basic] Public Sub GetRewardsClaimedListTest() Dim dal As New DataAccessLayer() Dim contactID As Integer = 2 Dim expected As List(Of RewardsClaimed) = Me.GetLocalRewardsClaimedList() Dim actual As List(Of RewardsClaimed) = dal.GetRewardsClaimedList(contactID) For i As Integer = 0 To expected.Count - 1 Assert.AreEqual(expected(i).PointsUsed, actual(i).PointsUsed) Next i dal.Dispose() End Sub
[Visual C#] [TestMethod()] public void GetRewardsClaimedListTest() { DataAccessLayer dal = new DataAccessLayer(); int contactID = 2; List expected = this.GetLocalRewardsClaimedList(); List actual = dal.GetRewardsClaimedList(contactID); for (int i = 0; i < expected.Count; i++)
Querying Entity Data
3-49
{ Assert.AreEqual(expected[i].PointsUsed, actual[i].PointsUsed); } dal.Dispose(); }
You can run this unit test by using any of the facilities in Visual Studio for running unit tests. Note: Notice that the test creates a new instance of the data access layer at the beginning, and disposes of it at the end. This avoids any potential interaction between individual tests. Note: This example unit test does not modify any data in the database. If you are creating a unit test to test data modification functionality, you should reset the data at the end of the test. This will avoid any potential problems if you change the order of the tests or if you add new tests.
Question: Why should you create a new instance of your data access layer component for each test?
3-50
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Adding a Unit Test Project
Key Points •
Add a test project to a solution.
•
Add a unit test for a method.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-03 virtual machine as Student with the password Pa$$w0rd
2.
Open Microsoft® Visual Studio® 2010.
3.
In Visual Studio, open the solution named UnitTestDemo in the E:\Demofiles\Mod03\Demo1\Starter folder.
4.
Add a Test project named DALTest to the solution.
5.
Open the DataAccessLayer code file in the DAL project.
6.
Add a unit test for the GetContactList method.
Querying Entity Data
7.
Review the GetContactListTest method.
8.
Save and close the solution.
Question: How can you identify a unit test method in your unit test project?
3-51
3-52
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab: Querying Entity Data
Objectives After completing this lab, you will be able to: •
Query an entity model by using LINQ to Entities.
•
Filter data by using LINQ to Entities.
•
Query an entity model by using Entity SQL.
•
Query an entity model by using the EntityClient provider for the Entity Framework.
•
Query an entity model by using a stored procedure.
Introduction In this lab, you will use several different techniques to execute queries against your entity model. In addition, you will learn how to create unit tests for your data access code.
Querying Entity Data
3-53
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-03 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
3-54
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Scenario
Adventure Works implements an entity model to support its customer reward program. You have been asked to implement a data access layer to provide the functionality that the customer rewards client application requires. In this first phase, you only need to implement the functionality that is necessary to retrieve data from the model. You must add functionality to retrieve contact entities, reward entities, rewards claimed data, and the number of orders that a contact has placed. You must include unit tests for all of your methods, and verify that the client application displays the retrieved data correctly.
Exercise 1: Retrieving All Contact Entities Scenario In this exercise, you will create a query that retrieves all of the contacts from the Adventure Works database. You will do this by using LINQ to Entities to query the entity model. The main tasks for this exercise are as follows:
Querying Entity Data
1.
Prepare the Adventure Works database for the lab.
2.
Open the starter project for this exercise.
3.
Add code to retrieve all of the contacts.
4.
Add a unit test to verify your code.
5.
Build and test the application.
3-55
f Task 1: Prepare the Adventure Works database for the lab 1.
Log on to the 10265A-GEN-DEV-03 virtual machine as Student with the password Pa$$w0rd.
2.
Run AWReset.bat in the E:\Labfiles folder.
f Task 2: Open the starter project for this exercise 1.
Open Visual Studio 2010.
2.
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex1\Starter or E:\Labfiles\Lab03\VB\Ex1\Starter folder.
f Task 3: Add code to retrieve all of the contacts 1.
In Visual Studio, review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Retrieve all contacts item in the task list. This task is located in the first GetContactList method.
3.
Delete the existing code in the GetContactList method.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null. If it is, instantiate it as a new instance of the AdventureWorksEntities context object.
Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all data access operations by using this context object.
b.
Create and define a LINQ to Entities query to select all Contact entities.
c.
Execute the query with MergeOption set to NoTracking.
3-56
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
d. Return the results. 5.
Save the DataAccessLayer code file.
f Task 4: Add a unit test to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex1 - Create a unit test for GetContactList item in the task list. This task is located in the GetContactListTest method.
3.
Delete the existing code in the GetContactListTest method.
4.
Write a unit test to compare the first 10 contacts returned by your query with the 10 contacts returned by the GetLocalCustomerListFirstTen method. Be sure to release all resources at the end of the test.
5.
Save the DataAccessLayerTest code file.
f Task 5: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that the application functions as expected.
4.
Close the application.
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed, including GetContactListTest.
7.
Close the solution.
Exercise 2: Retrieving Contact Entities by Using a Filter Scenario In this exercise, you will create a query that retrieves all of the contacts from the Adventure Works database that have a specified last name. You will do this by using a LINQ to Entities query that takes a parameter that specifies the name to match against.
Querying Entity Data
3-57
The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Add code to retrieve contacts by last name.
3.
Add a unit test to verify your code.
4.
Build and test the application.
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex2\Starter or E:\Labfiles\Lab03\VB\Ex2\Starter folder.
f Task 2: Add code to retrieve contacts by last name 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - Retrieve contacts by last name item in the task list. This task is located in the second GetContactList method.
3.
Delete the existing code in the GetContactList method.
4.
Add code to the method that performs the following tasks: a.
Instantiate the entities context object if it is currently null.
b.
Create and define a LINQ to Entities query to retrieve Contact entities by last name.
c.
Execute the query with MergeOption set to NoTracking.
d. Return the results. 5.
Save the DataAccessLayer code file.
f Task 3: Add a unit test to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex2 - Create a unit test for GetContactList by last name item in the task list. This task is located in the GetAContactListTest method.
3.
Delete the existing code in the GetAContactListTest method.
3-58
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
4.
Write a unit test to compare the first contacts returned by your query, using “Adina” as the last name parameter, with the contacts returned by the GetLocalCustomerList method. Be sure to release all resources at the end of the test.
5.
Save the DataAccessLayerTest code file.
f Task 4: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
In the AdventureWorks Rewards window, in the Name box, type Ward and then click Search. Verify that the application functions as expected and loads the correct contacts into the data grid.
4.
Close the application.
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed, including GetAContactListTest.
7.
Close the solution.
Exercise 3: Retrieving RewardsClaimed Entities Scenario In this exercise, you will create a query that retrieves from the Adventure Works database all of the rewards that an individual contact has claimed. You will do this by using an Entity SQL query that takes a parameter that specifies the contact ID of the contact to match against. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Add code to retrieve rewards claimed by contact ID.
3.
Add a unit test to verify your code.
4.
Build and test the application.
Querying Entity Data
3-59
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex2\Starter or E:\Labfiles\Lab03\VB\Ex3\Starter folder.
f Task 2: Add code to retrieve rewards claimed by contact ID 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex3 - Retrieve rewards claimed by contact ID item in the task list. This task is located in the GetRewardsClaimedList method.
3.
Delete the existing code in the GetRewardsClaimedList method.
4.
Add code that performs the following tasks: a.
Instantiate the entities context object if it is currently null.
b.
Create and define an Entity SQL query to retrieve RewardsClaimed entities by contact ID.
c.
Execute the query with MergeOption set to NoTracking.
d. Return the results. 5.
Save the DataAccessLayer code file.
f Task 3: Add a unit test to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex3 - Create a unit test for GetRewardsClaimedList item in the task list. This task is located in the GetRewardsClaimedListTest method.
3.
Delete the existing code in the GetRewardsClaimedListTest method.
4.
Write a unit test to compare the RewardsClaimed entities returned by your query, using a contactID of 2 as the parameter value, with the RewardsClaimed entities returned by the GetLocalRewardsClaimedList method. Be sure to release all resources at the end of the test.
5.
Save the DataAccessLayerTest code file.
3-60
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 4: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid. Then, click a contact in the data grid to display rewards claimed by that contact in the second data grid.
4.
Close the application.
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed, including GetRewardsClaimedListTest.
7.
Close the solution.
Exercise 4: Querying the Rewards Family of Entities Scenario In this exercise, you will create a query that retrieves details of AdventureWorksReward rewards only. You will do this by using an Entity SQL query that takes a parameter that specifies the reward ID to match against, and retrieves rewards of the specified type. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Add code to retrieve reward details by reward ID.
3.
Add a unit test to verify your code.
4.
Build and test the application.
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex4\Starter or E:\Labfiles\Lab03\VB\Ex4\Starter folder.
f Task 2: Add code to retrieve reward details by reward ID 1.
Review the task list.
Querying Entity Data
3-61
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex4 - Get a reward by reward ID item in the task list. This task is located in the GetReward method.
3.
Delete the existing code in the GetReward method.
4.
Use the EntityConnection, EntityCommand, and EntityDataReader classes to connect to the entity model, retrieve the details of a reward by rewardID, and then return the result as a Reward entity. Set the MergeOption of the query to NoTracking.
5.
Save the DataAccessLayer code file.
f Task 3: Add a unit test to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex4 - Create a unit test for GetReward item in the task list. This task is located in the GetRewardTest method.
3.
Delete the existing code in the GetRewardTest method.
4.
Write a unit test to compare the Reward entity returned by your query, using a rewardID of 21 as the parameter value, with the Reward entity returned by the GetLocalRewardData method. Be sure to release all resources at the end of the test.
5.
Save the DataAccessLayerTest code file.
f Task 4: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid. Click a contact in the data grid to display reward claims in the second data grid. Then, click a rewards claim in the second data grid to display the reward details on the form.
4.
Close the application.
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed, including GetRewardTest.
3-62
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
7.
Close the solution.
Exercise 5: Executing a Stored Procedure Scenario In this exercise, you will invoke a stored procedure that returns the number of orders that a contact has placed. You will do this by calling a method that wraps an imported function in the entity model. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Add code to retrieve the number of orders that a contact has placed.
3.
Add a unit test to verify your code.
4.
Build and test the application.
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab03\CS\Ex5\Starter or E:\Labfiles\Lab03\VB\Ex5\Starter folder.
f Task 2: Add code to retrieve the number of orders that a contact has placed 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex5 - Call the CountOrders stored procedure item in the task list. This task is located in the CountOrders method.
3.
Delete the existing code in the CountOrders method.
4.
Add code that performs the following tasks:
5.
a.
Instantiate the entities context object if it is currently null.
b.
Invoke the CountOrders method on the context object.
c.
Return the results.
Save the DataAccessLayer code file.
Querying Entity Data
3-63
f Task 3: Add a unit test to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex5 - Create a unit test for CountOrders item in the task list. This task is located in the CountOrdersTest method.
3.
Delete the existing code in the CountOrdersTest method.
4.
Write a unit test to verify that the contact with a contact ID of 2 has placed four orders.
5.
Save the DataAccessLayerTest code file.
f Task 4: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
In the AdventureWorks Rewards window, click All Customers to load contacts into the data grid. Then, click a contact in the data grid to display the number of orders placed by that contact on the form.
4.
Close the application.
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed, including CountOrdersTest.
7.
Close Visual Studio.
3-64
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Review
Review Questions 1.
Which class do you use to define a query that returns entity objects?
2.
What type does the EntityClient provider use to return data from a query?
3.
Which query operator enables you to work with entities that are organized in a table-per-type hierarchy?
4.
Where can you find an imported function in the EDM?
Querying Entity Data
3-65
Module Review and Takeaways
Review Questions 1.
What are the two query technologies that enable you to retrieve entity objects from your model?
2.
What structure does the EntityClient provider for the Entity Framework use to return data?
3.
What is the role of the ObjectContext object?
Best Practices Related to Querying Your Entity Model with the Entity Framework Supplement or modify the following best practices for your own work situations: •
To enable as much compile-time checking as possible, you should consider using LINQ to Entities.
3-66
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
Entity SQL is useful when you need to construct queries dynamically at run time, when you want to store the query as a part of the model definition, or if you are already an expert in SQL-based query languages.
•
If performance is critical, and you only need to retrieve read-only data as rowsets, consider using the EntityClient provider.
•
Use stored procedures that are embedded in your EDM to enforce security, provide predictability, and encapsulate logic on data inside the database.
•
Organize your data access code into a single data access layer class or component.
•
Create unit tests for all of the public methods in your data access layer.
Creating, Updating, and Deleting Entity Data
4-1
Module 4 Creating, Updating, and Deleting Entity Data Contents: Lesson 1: Understanding Change Tracking in the Entity Framework
4-3
Lesson 2: Modifying Data in an Entity Data Model
4-12
Lab: Creating, Updating, and Deleting Entity Data
4-28
4-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
Most data-aware solutions will need to provide update functionality to their users. The Entity Framework provides a change tracking mechanism to track the changes to the data in the model and you must write code to explicitly persist the changes to the database. This module introduces you to the ways in which the Entity Framework enables you to modify data in your database. You apply changes to the entities managed by the ObjectContext class. The ObjectContext class is responsible for tracking all changes to entities and then persisting these changes to the database on request.
Objectives After completing this module, you will be able to: •
Describe how the Entity Framework implements change tracking.
•
Describe how to modify data in the entity model, and persist the changes to the database.
Creating, Updating, and Deleting Entity Data
4-3
Lesson 1
Understanding Change Tracking in the Entity Framework
In this lesson, you will learn how the ObjectContext class performs data modification tasks for your application. The ObjectContext class, or the derived version created from your Entity Data Model (EDM), handles data modifications in two stages. In the first stage, your code makes changes to the entities and entity sets managed by the ObjectContext class. The ObjectStateManager class in the ObjectContext class tracks all of these modifications; in the second stage, it persists these changes to the underlying database. The Entity Framework generates all of the required database commands, so your code operates only on common language runtime (CLR) objects.
Objectives After completing this lesson, you will be able to: •
Describe the role of the ObjectContext and ObjectStateManager classes in the data modification process.
4-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
Describe the differences between detached and attached entity objects.
•
Describe how the ObjectStateManager class tracks changes to entity objects.
Creating, Updating, and Deleting Entity Data
4-5
The ObjectContext and ObjectStateManager Classes
Key Points An ObjectContext object contains entity objects that represent data in your underlying database. If you want to modify data, you first make a change to the contents of the ObjectContext object and then ask the ObjectContext object to save the changes to the underlying database.
Populating the ObjectContext Object There are two ways that you can load data into your ObjectContext object. First, you can run an Entity Structured Query Language (Entity SQL) or LanguageIntegrated Query (LINQ) to Entities query that retrieves data from the database. Note: If you plan to make changes to this data, it is important that you do not use the MergeOption.NoTracking value for the MergeOption property of the ObjectQuery object when you execute the query.
4-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The second way that you can load data into the ObjectContext object is by key. The following code example shows how you can load a contact entity if you know the EntityKey value of the contact that you want to modify. [Visual Basic] Dim contact As Object = Nothing Dim key As EntityKey = New EntityKey( _ "AdventureWorksEntities.Contacts", "contactID", contactID) ' Ensure that the contact to modify is loaded. If entities.TryGetObjectByKey(key, contact) Then ' Use the returned contact entity … End If
[Visual C#] object contact = null; EntityKey key = new EntityKey("AdventureWorksEntities.Contacts", "contactID", contactID); // Ensure that the contact to modify is loaded. if (entities.TryGetObjectByKey(key, out contact)) { // Use the returned contact entity … }
If the contact entity is not already in the ObjectContext object, the TryGetObjectByKey method loads the entity from the database and returns true if it succeeds.
Modifying Data in the ObjectContext Object To create a new entity, you instantiate a new object of the entity type, set the properties of the entity object, and add it to the ObjectContext object. To update an existing entity, you get a reference to the entity object and modify the properties of the entity object. To delete an existing entity, you get a reference to the entity object and mark it for deletion.
Creating, Updating, and Deleting Entity Data
4-7
The ObjectContext class uses the ObjectStateManager class to track all of these changes. The ObjectStateManager class marks newly created objects as new, and it marks deleted objects as deleted. In the case of updated objects, the ObjectStateManager class marks these objects as changed and stores both the original and changed versions of the object.
Persisting Modifications to the Database When you call the SaveChanges method of the ObjectContext class, the Entity Framework generates and executes the commands to save the changes to the database in the context of a single transaction. By default, if the SaveChanges method succeeds, the ObjectContext class resets all of the tracking information maintained by the ObjectStateManager object. You can override this default behavior by calling the overloaded version of the SaveChanges method with a SaveOptions.None parameter. Question: Describe two ways in which you can load an entity into an ObjectContext object for modification.
4-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Attaching and Detaching Objects
Key Points Entity objects can be in an attached or detached state. The ObjectContext class directly manages attached entity objects and can track changes to them, and persist these changes to the database. The ObjectContext class does not manage detached objects; for example, a newly instantiated entity object is detached and cannot be saved to the database until you add it to an ObjectContext object. The Entity Framework automatically attaches entity objects returned by a query, unless you ran the query with the MergeOption property set to MergeOption.NoTracking. In this case, all of the returned entities will be detached. In the AdventureWorks application, when you retrieve entities from the EDM, you set the MergeOption property to MergeOption.NoTracking. Using these detached objects helps you conserve resources in the application, because the user will only browse many of these entities in the user interface. You can attach objects to the ObjectContext object by using the Attach or AttachTo methods of the ObjectContext class. You can detach objects from the ObjectContext object by using the Detach method of the ObjectContext class.
Creating, Updating, and Deleting Entity Data
4-9
Question: Why do detached entity objects consume fewer resources than attached entity objects?
Additional Reading For more information about store-generated column values, see the How to: Work with Store Generated Column Values (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194037.
4-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Change Tracking and Identity Resolution
Key Points The ObjectContext class uses the ObjectStateManager class to maintain a cache of attached objects and track changes to these objects. The ObjectContext class uses EntityKey objects to ensure that it holds only a single copy of any entity in the cache. When you run a query, the ObjectContext class loads only the missing entities into the cache. You can control this behavior by using the MergeOption property of a query. The following table shows how the different values of the MergeOption property affect the way that entities are loaded into the cache. MergeOption
Description
AppendOnly
This is the default behavior. Only entities that do not already exist in the cache are loaded from the database.
OverwriteChanges
All entities are loaded from the database, and any entity property changes in the cache are overwritten with values from the database.
Creating, Updating, and Deleting Entity Data
MergeOption
4-11
Description
PreserveChanges
All entities are loaded from the database, but any entity property changes in the cache are preserved.
NoTracking
Entities are materialized in a detached state and are not tracked by the ObjectStateManager class.
The ObjectContext class uses the ObjectStateManager class to track all of the changes you make to attached entities. The ObjectStateManager class maintains a collection of ObjectStateEntry objects. Each ObjectStateEntry object has a pair of properties to hold the current and original property values of the entity, a property that holds the key of the entity, and a State property that records whether the entity has been added, deleted, or modified. When you call the SaveChanges method in the ObjectContext class, the Entity Framework uses the collection of ObjectStateEntry objects to perform the following actions: •
For newly created entities, the Entity Framework runs a command to insert the record into the database and changes the value of the State property of the ObjectStateEntry object from Added to Unchanged.
•
For updated entities, the Entity Framework runs a command to update the record in the database, copies the contents of the CurrentValues property of the ObjectStateEntry object to the OriginalValues property, and changes the value of the State property of the ObjectStateEntry object from Modified to Unchanged.
•
For deleted entities, the Entity Framework runs a command to delete the record in the database and removes the entity from the context.
Question: What is the default behavior of queries if a merge option is not specified?
Additional Reading For more information about the SaveChanges method, see the ObjectContext.SaveChanges Method page at http://go.microsoft.com/fwlink/?LinkID=194038.
4-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 2
Modifying Data in an Entity Data Model
In this lesson, you will learn how to perform create, update, and delete operations on information in your database by using the Entity Framework. You create new entities by instantiating new entity objects and adding them to the ObjectContext object. The ObjectStateManager class tracks this change and inserts a new record into the database when you call the SaveChanges method. You update existing entities by modifying the properties of an attached entity object. The ObjectStateManager class tracks this change and updates the original record in the database when you call the SaveChanges method. You delete entities by marking them as deleted in the ObjectContext class. The ObjectStateManager class tracks this change and deletes the original record from the database when you call the SaveChanges method. An Adventure Works client application can use this functionality to enable users to modify data in the database for all of the entity types defined in the AdventureWorks EDM.
Creating, Updating, and Deleting Entity Data
Objectives After completing this lesson, you will be able to: •
Create a new entity object and persist the entity to the database.
•
Update an entity object and persist the changes to the database.
•
Delete an entity object and persist the change to the database.
•
Use stored procedures to persist changes to entities to the database.
4-13
4-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Creating and Saving a New Entity
Key Points You can create a new entity object in two ways. The first way is to instantiate a new entity object and then use an Add method to add the entity to the ObjectContext object. The following code example shows how to use the AddObject method of the ObjectContext class to create a new Contact entity in the AdventureWorks EDM. [Visual Basic] ' Instantiate a new Contact entity. Dim contact As New Contact() ' Populate the properties of the Contact entity. … ' Add the entity to the ObjectContext object. ' This also attaches the entity. entities.AddObject("Contact", contact)
Creating, Updating, and Deleting Entity Data
4-15
[Visual C#] // Instantiate a new Contact entity. Contact contact = new Contact(); // Populate the properties of the Contact entity. … // Add the entity to the ObjectContext object. // This also attaches the entity. entities.AddObject("Contact", contact);
The Entity Framework also generates a custom Add method. The following code example shows the AddToContacts method in the AdventureWorks EDM. [Visual Basic] ' Instantiate a new Contact entity. Dim contact As New Contact() ' Populate the properties of the Contact entity. … ' Add the entity to the ObjectContext object. ' This also attaches the entity. entities.AddToContacts(contact)
[Visual C#] // Instantiate a new Contact entity. Contact contact = new Contact(); // Populate the properties of the Contact entity. … // Add the entity to the ObjectContext object. // This also attaches the entity. entities.AddToContacts(contact);
The second way to create a new entity is by using the custom Create method generated by the Entity Framework. The following code example shows the CreateContacts method in the AdventureWorks EDM. [Visual Basic] ' Instantiate a new Contact entity. Dim contact As Contact = contact.CreateContact(0, True, _ "Ronald", "Adina", 0, "xyz", "abc", _ Guid.NewGuid(), DateTime.Now, 1000)
4-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
' Add the entity to the ObjectContext object. ' This also attaches the entity. entities.AddToContacts(contact)
[Visual C#] // Instantiate a new Contact entity. Contact contact = Contact.CreateContact(0, true, "Ronald", "Adina", 0, "xyz", "abc", Guid.NewGuid(), DateTime.Now, 1000); // Add the entity to the ObjectContext object. // This also attaches the entity. entities.AddToContacts(contact);
This generated method has a parameter for every property that cannot be null. If you create the entity by using the constructor, you must ensure that you provide a value for every property that cannot be null, as specified in your EDM. Note: If the key is an identity key, the key value created by the database will be applied after the SaveChanges method has been called. Otherwise, you must provide a unique key value.
To save any entities you have created and added to the ObjectContext object, you call the SaveChanges method of the ObjectContext class. This method will generate the necessary database insert statements to persist the entity. You must handle any exceptions thrown by the SaveChanges method. For example, if two objects have the same user-specified key value, an InvalidOperationException exception occurs when the SaveChanges method is called. If this occurs, you should assign unique key values and retry the operation. This method can also throw an OptimisticConcurrencyException exception. Note: To reload an entity or a collection of entities from the database after calling the SaveChanges method, you can call the Refresh method of the ObjectContext class.
Question: What types of error should your code handle when you save a new entity to the database?
Creating, Updating, and Deleting Entity Data
4-17
Updating and Saving an Entity
Key Points You can update an existing entity object in two ways. The first way is to modify the properties of an attached entity object. The following code example shows how to set the Name property of a Reward entity in the AdventureWorks EDM. [Visual Basic] Reward.Name = "Bonus Points"
[Visual C#] Reward.Name = "Bonus Points";
The second way is to copy the properties of a detached object to an attached object. The following code example shows how to copy the properties of the detached StoreContact entity object to the attached storeContactToModify object. [Visual Basic] ' Get the key of the StoreContact entity you are modifying.
4-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Dim key As EntityKey = entities.CreateEntityKey("StoreContacts", Contact) Dim storeContactToModify As Object = Nothing ' Ensure that the StoreContact entity to modify is loaded. If entities.TryGetObjectByKey(key, storeContactToModify) Then ' Copy all the changes over. entities.ApplyCurrentValues(key.EntitySetName, Contact) End If
[Visual C#] // Get the key of the StoreContact entity you are modifying. EntityKey key = entities.CreateEntityKey("StoreContacts", contact); object storeContactToModify; // Ensure that the StoreContact entity to modify is loaded. if (entities.TryGetObjectByKey(key, out storeContactToModify)) { // Copy all the changes over. entities.ApplyCurrentValues(key.EntitySetName, contact); }
To save any entities you have modified in the ObjectContext object, you call the SaveChanges method of the ObjectContext class. This method will generate the necessary database update statements to persist the entity. You must handle any exceptions thrown by the SaveChanges method. For example, if the change results in a referential integrity violation in the database, the Entity Framework will throw an UpdateException exception. In the AdventureWorks EDM, you may get a referential integrity violation when you save a StoreContact entity if another user has updated the related Contact record. Remember that all of the changes made by a single call to the SaveChanges method occur in the scope of a transaction. Note: To reload an entity or a collection of entities from the database after calling the SaveChanges method, you can call the Refresh method of the ObjectContext class.
Creating, Updating, and Deleting Entity Data
4-19
Question: What type of exception does the Entity Framework throw if your change results in a referential integrity violation?
Additional Reading For more information about executing business logic during property changes, see the How to: Execute Business Logic During Scalar Property Changes (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194039.
4-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Deleting an Entity from an Entity Set
Key Points You use the DeleteObject method of either the ObjectContext class or the entity set to mark an entity for deletion. The following code example demonstrates how to delete a StoreContact entity from the AdventureWorks EDM. [Visual Basic] Dim storeContactToDelete As Object = Nothing Dim key As EntityKey = _ New EntityKey("AdventureWorksEntities.StoreContacts", _ "contactID", contactID) ' Make sure that the entity to modify is loaded If entities.TryGetObjectByKey(key, storeContactToDelete) Then ' Delete the object entities.DeleteObject(storeContactToDelete) End If
Creating, Updating, and Deleting Entity Data
4-21
[Visual C#] object storeContactToDelete = null; EntityKey key = new EntityKey("AdventureWorksEntities.StoreContacts", "contactID", contactID); // Make sure that the entity to modify is loaded if (entities.TryGetObjectByKey(key, out storeContactToDelete)) { // Delete the object entities.DeleteObject(storeContactToDelete); }
If the EDM defines a relationship to another dependent entity, and if the relationship is marked with in the conceptual model, the Entity Framework will automatically delete any dependent entities. You can use this to delete the RewardsClaimed entities related to a Contact entity in the AdventureWorks EDM. To delete from the database any entities marked for deletion in the ObjectContext object, you call the SaveChanges method of the ObjectContext class. This method will generate and execute the necessary database delete statements. You must handle any exceptions thrown by the SaveChanges method. For example, deleting a record can cause a referential integrity violation in the database, which will result in an UpdateException exception. Remember that all of the changes made by a single call to the SaveChanges method occur within the scope of a transaction. Question: How can you automate cascading delete behavior?
Additional Reading For more information about cascading deletes in the Entity Framework, see the OnDelete Element (CSDL) page at http://go.microsoft.com/fwlink/?LinkID=194040.
4-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using Stored Procedures to Persist Changes to the Database
Key Points The Entity Framework enables you to specify stored procedures to use when you modify entity data. You add these stored procedures to your entity model, and the stored procedures replace the methods generated by the Entity Framework. Stored procedures are called implicitly, so no changes are required to the data model defined in the conceptual schema or to your application code.
Inserting Data by Using Stored Procedures The following code example shows how the insert stored procedure for the RewardsClaim table is defined in your EDM. <ModificationFunctionMapping> <ScalarProperty Name="ContactID" ParameterName="contactID" /> <ScalarProperty Name="RewardID" ParameterName="rewardID" />
Creating, Updating, and Deleting Entity Data
4-23
<ScalarProperty Name="PointsUsed" ParameterName="pointsUsed" /> <ScalarProperty Name="ClaimID" ParameterName="claimID" />
The following code example shows how to invoke this stored procedure from your application code when you add a new claim in the AdventureWorks EDM. [Visual Basic] ' Add RewardsClaimed entity to the entity set. entities.AddToRewardsClaimed(claim) ' Save all the changes to the database. entities.SaveChanges()
[Visual C#] // Add RewardsClaimed entity to the entity set. entities.AddToRewardsClaimed(claim); // Save all the changes to the database. entities.SaveChanges();
Updating Data by Using Stored Procedures The following code example shows how the update stored procedure for the RewardsClaim table is defined in your EDM. <ModificationFunctionMapping> <ScalarProperty Name="ContactID" ParameterName="contactID" Version="Current" /> <ScalarProperty Name="RewardID" ParameterName="rewardID" Version="Current" /> <ScalarProperty Name="PointsUsed" ParameterName="pointsUsed" Version="Current" /> <ScalarProperty Name="ClaimID" ParameterName="claimID" Version="Current" />
The following code example shows how to invoke this stored procedure from your application code when you have modified the points claimed for a RewardsClaimed entity.
4-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] rewardsClaimed.PointsUsed = 500 ' Save the changes to the database entities.SaveChanges()
[Visual C#] rewardsClaimed.PointsUsed = 500; // Save the changes to the database entities.SaveChanges();
Deleting Data by Using Stored Procedures The following code example shows how the delete stored procedure for the RewardsClaim table is defined in the AdventureWorks EDM. <ModificationFunctionMapping> <ScalarProperty Name="ClaimID" ParameterName="claimID" />
The following code example shows how to invoke this stored procedure from your application code. [Visual Basic] ' Delete the object entities.DeleteObject(rewardClaimToDelete) ' Save the changes to the database entities.SaveChanges()
[Visual C#] // Delete the object entities.DeleteObject(rewardClaimToDelete); // Save the changes to the database entities.SaveChanges();
Creating, Updating, and Deleting Entity Data
4-25
Question: What are the advantages of using stored procedures to persist changes in entities to the database?
4-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Adding Stored Procedures to the Model
Key Points •
Import a stored procedure from the AdventureWorks database.
•
Map the insert function of the RewardsClaimed entity to the stored procedure.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-04 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Demofiles folder, run Demo.bat to create the stored procedure for this demonstration.
3.
Start Microsoft Visual Studio® 2010.
4.
Open the ImportSPDemo solution.
5.
Open the AdventureWorksEDM model in the Entity Designer.
Creating, Updating, and Deleting Entity Data
4-27
6.
Run the Update Wizard to add the uspInsertRewardsClaim stored procedure to the model.
7.
Map the insert function of the RewardsClaimed entity to the uspInsertRewardsClaim stored procedure.
8.
Map the stored procedure parameters to the appropriate entity properties.
9.
Save and close the solution.
10. Close Visual Studio.
Question: What three functions are available for mapping to a stored procedure?
4-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab: Creating, Updating, and Deleting Entity Data
Objectives After completing this lab, you will be able to: •
Create, update, and delete entity data by using the default behavior of the EDM.
•
Create, update, and delete entity data by using stored procedures embedded in the EDM.
Introduction In this lab, you will create, modify, and delete entity objects in the ObjectContext object. You will then persist these changes to the database. You will also use stored procedures embedded in the EDM to perform the database modifications. Note that data validation will be covered in a later lab.
Creating, Updating, and Deleting Entity Data
4-29
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-04 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
4-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Scenario
Adventure Works implements an entity model to support its customer reward program. You have been asked to extend the data access layer to provide the functionality required by the customer rewards client application. In this second phase, you must implement the functionality necessary to support creating, updating, and deleting contacts, rewards, and claims.
Exercise 1: Maintaining Contact and Reward Data Scenario In this exercise, you will update the data access layer to support creating, updating, and deleting contact and reward entities. You will use the default behavior of the EDM, leaving the EDM to generate the required database commands. You must also ensure that you delete any related claims when you delete a contact. The main tasks for this exercise are as follows: 1.
Prepare the AdventureWorks database for the lab.
2.
Open the starter project.
Creating, Updating, and Deleting Entity Data
3.
Add code to add a new contact.
4.
Add code to update a contact.
5.
Add code to delete a contact.
6.
Add unit tests to verify your code.
7.
Add code to add a new reward.
8.
Add code to update a reward.
9.
Add code to delete a reward.
4-31
10. Add unit tests to verify your code. 11. Build and test the application.
f Task 1: Prepare the AdventureWorks database for the lab 1.
Log on to the 10265A-GEN-DEV-04 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Labfiles folder, run AWReset.bat.
f Task 2: Open the starter project 1.
Open Microsoft® Visual Studio® 2010.
2.
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\CS\Ex1\Starter or E:\Labfiles\Lab04\VB\Ex1\Starter folder.
f Task 3: Add code to add a new contact 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Add a new Contact in the task list. This task is located in the AddContact method.
3.
In the AddContact method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
4-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all data access operations by using this context object.
b.
Encrypt the password in the contact passed as a parameter by calling the EncryptPassword method.
c.
Set the ModifiedDate property of the contact to the current date and time.
d. Assign a globally unique identifier (GUID) to the rowguid property of the contact.
5.
e.
Add the contact to the Contacts entityset.
f.
Save the changes to the database and return the new ContactID value.
g.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 4: Add code to update a contact 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Modify an existing Contact in the task list. This task is located in the UpdateContact method.
3.
In the UpdateContact method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Get the EntityKey property of the detached contact passed to the method. This detached contact contains the modified properties.
c.
Use the TryGetObjectByKey method to load the correct contact into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a parameter. e.
Save the changes to the database and return true.
f.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Creating, Updating, and Deleting Entity Data
5.
4-33
Save the DataAccessLayer file.
f Task 5: Add code to delete a contact 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Delete an existing Contact in the task list. This task is located in the DeleteContact method.
3.
In the DeleteContact method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Use the contactID variable passed as a parameter to create the EntityKey object of the contact to delete.
c.
Use the TryGetObjectByKey method to load the correct contact into the context.
d. Delete all of the related RewardsClaimed entities belonging to the contact from the context
5.
e.
Delete the contact from the context.
f.
Save all of the changes to the database and return true.
g.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 6: Add unit tests to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Add a test for AddContact in the task list. This task is located in the AddContactTest method.
3.
In the AddContactTest method, delete the existing code.
4.
Add a unit test to verify the behavior of the AddContact method. Use the CreateTestContact method to create a contact to add to the database, and use
4-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
the GetContactById method to retrieve the contact from the database. Ensure that you remove the contact and release any resources at the end of the test. 5.
Locate the ModifyContactTest method by double-clicking the comment TODO: Ex1 - Add a test for ModifyContact in the task list. This task is located in the ModifyContactTest method.
6.
In the ModifyContactTest method, delete the existing code.
7.
Add a unit test to verify the behavior of the ModifyContact method. Use the CreateTestContact method to create a contact to add to the database, which you can then modify, and use the GetContactById method to retrieve the contact from the database. Ensure that you remove the contact and release any resources at the end of the test.
8.
Locate the DeleteContactTest method by double-clicking the comment TODO: Ex1 - Add a test for DeleteContact in the task list. This task is located in the DeleteContactTest method.
9.
In the DeleteContactTest method, delete the existing code.
10. Add a unit test to verify the behavior of the DeleteContact method. Use the CreateTestContact method to create a contact to add to the database, which you can then delete, and use the GetContactById method to try to retrieve the contact from the database. Ensure that you release any resources at the end of the test. 11. Save the DataAccessLayerTest file.
f Task 7: Add code to add a new reward 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Add a new Reward in the task list. This task is located in the AddReward method.
3.
In the AddReward method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all data access operations by using this context object.
Creating, Updating, and Deleting Entity Data
4-35
b.
Get the next available RewardID value by calling the GetNextRewardID method.
c.
Add the reward to the Rewards entity set.
d. Save the changes to the database and return the RewardID value. e. 5.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 8: Add code to update a reward 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Modify an existing Reward in the task list. This task is located in the UpdateReward method.
3.
In the UpdateReward method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Get the EntityKey property of the detached contact passed to the method. This detached contact contains the modified properties.
c.
Use the TryGetObjectByKey method to load the correct contact into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a parameter.
5.
e.
Save the changes to the database and return true.
f.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 9: Add code to delete a reward 1.
Review the task list.
4-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Delete an existing Reward in the task list. This task is located in the DeleteReward method.
3.
In the DeleteReward method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Use the rewardID value passed as a parameter to create the EntityKey object of the reward to delete.
c.
Use the TryGetObjectByKey method to load the correct reward into the context.
d. Delete the reward from the context.
5.
e.
Save the change to the database and return true.
f.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 10: Add unit tests to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Add a test for AddReward in the task list. This task is located in the AddRewardTest method.
3.
In the AddRewardTest method, delete the existing code.
4.
Add a unit test to verify the behavior of the AddReward method. Use the CreateAdventureWorksRewardData and CreateSupermarketRewardData methods to create rewards to add to the database, and use the GetRewardById method to retrieve the rewards from the database. Ensure that you remove the rewards and release any resources at the end of the test.
5.
Locate the ModifyRewardTest method by double-clicking the comment TODO: Ex1 - Add a test for ModifyReward in the task list. This task is located in the ModifyRewardTest method.
6.
In the ModifyRewardTest method, delete the existing code.
Creating, Updating, and Deleting Entity Data
4-37
7.
Add a unit test to verify the behavior of the ModifyReward method. Use the CreateSupermarketRewardData method to create a reward to add to the database, which you can then modify, and use the GetRewardById method to retrieve the reward from the database. Ensure that you remove the reward and release any resources at the end of the test.
8.
Locate the DeleteRewardTest method by double-clicking the comment TODO: Ex1 - Add a test for DeleteReward in the task list. This task is located in the DeleteRewardTest method.
9.
In the DeleteRewardTest method, delete the existing code.
10. Add a unit test to verify the behavior of the DeleteReward method. Use the CreateAdventureWorksRewardData method to create a reward to add to the database, which you can then delete. Ensure that you release any resources at the end of the test. 11. Save the DataAccessLayerTest file.
f Task 11: Build and test the application 1.
Build the solution and correct any errors.
2.
Run all of the tests in the solution.
3.
Verify that all of the tests succeed.
4.
Start the application in Debug mode.
5.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add and modify Adventure Works reward data.
6.
Close the application.
7.
Close the solution.
Exercise 2: Maintaining RewardsClaim Data Scenario In this exercise, you will update the data access layer to support creating, updating, and deleting claim entities. You will import stored procedures into your EDM that will be used to perform the changes to the database. You must also ensure that you
4-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
adjust the number of points a contact has when you make a change to the claims associated with the contact. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Add data modification stored procedures to your model.
3.
Add code to add a new RewardsClaim record.
4.
Add code to update a RewardsClaim record.
5.
Add code to delete a RewardsClaim record.
6.
Add unit tests to verify your code.
7.
Build and test the application.
f Task 1: Open the starter project •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab04\CS\Ex2\Starter or E:\Labfiles\Lab04\VB\Ex2\Starter folder
f Task 2: Add data modification stored procedures to your model 1.
Open the AdventureWorksEDM model in the Entity Designer.
2.
Run the Update Wizard to add the uspInsertRewardsClaim, uspUpdateRewardsClaim, and uspDeleteRewardsClaim stored procedures to the model.
3.
Map the RewardsClaimed entity to the stored procedures in the following table. Function
4.
Stored Procedure
Insert
uspInsertRewardsClaim
Update
uspUpdateRewardsClaim
Delete
uspDeleteRewardsClaim
Map the stored procedure parameters to the entity properties as shown in the following table.
Creating, Updating, and Deleting Entity Data
Stored Procedure
5.
Parameter
4-39
Property
uspInsertRewardsClaim
claimID : int
ClaimID : Int32
uspInsertRewardsClaim
pointsUsed : int
PointsUsed : Int32
uspInsertRewardsClaim
rewardID : int
RewardID : Int32
uspInsertRewardsClaim
contactID : int
ContactID : Int32
uspUpdateRewardsClaim
claimID : int
ClaimID : Int32
uspUpdateRewardsClaim
pointsUsed : int
PointsUsed : Int32
uspUpdateRewardsClaim
rewardID : int
RewardID : Int32
uspUpdateRewardsClaim
contactID : int
ContactID : Int32
uspDeleteRewardsClaim
claimID : int
ClaimID : Int32
Save the AdventureWorksEDM model.
f Task 3: Add code to add a new RewardsClaim record 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 Add a new RewardsClaimed entity in the task list. This task is located in the CreateRewardsClaim method.
3.
In the CreateRewardsClaim method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all data access operations by using this context object.
b.
Set the ClaimID value by calling the GetNextClaimID method.
c.
Add the claim to the RewardsClaimed entity set.
4-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
d. Decrement the points for the associated contact by the points used for the claim.
5.
e.
Save all of the changes to the database and return the new ClaimID value.
f.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 4: Add code to update a RewardsClaim record 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 Update a RewardsClaimed entity in the task list. This task is located in the UpdateRewardsClaim method.
3.
In the UpdateRewardsClaim method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Get the EntityKey property of the detached claim passed to the method. This detached claim contains the modified properties.
c.
Use the TryGetObjectByKey method to load the correct claim into the context.
d. Use the ApplyCurrentValues method to copy data from the detached contact passed as a parameter.
5.
e.
Adjust the points for the associated contact by the difference between the old and the new points for the claim.
f.
Save all of the changes to the database and return true.
g.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 5: Add code to delete a RewardsClaim record 1.
Review the task list.
Creating, Updating, and Deleting Entity Data
4-41
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 Delete a RewardsClaimed entity in the task list. This task is located in the DeleteRewardsClaim method.
3.
In the DeleteRewardsClaim method, delete the existing code.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null, and if it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Use the claimID value passed as a parameter to create the EntityKey object of the claim to delete.
c.
Use the TryGetObjectByKey method to load the correct claim into the context.
d. Give the points of the claim back to the associated contact.
5.
e.
Delete the claim from the context.
f.
Save all of the changes to the database and return true.
g.
Handle any InvalidOperationException or UpdateException exceptions by throwing a new DALException exception.
Save the DataAccessLayer file.
f Task 6: Add unit tests to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex2 - Add a test for CreateRewardsClaim in the task list. This task is located in the CreateRewardsClaimTest method.
3.
In the CreateRewardsClaimTest method, delete the existing code.
4.
Add a unit test to verify the behavior of the CreateRewardsClaim method. Use the CreateLocalClaim method to create a claim to add to the database, and use the GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you remove the rewards and release any resources at the end of the test.
5.
Locate the UpdateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a test for UpdateRewardsClaim in the task list. This task is located in the UpdateRewardsClaimTest method.
6.
In the UpdateRewardsClaimTest method, delete the existing code.
4-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
7.
Add a unit test to verify the behavior of the UpdateRewardsClaim method. Use the CreateLocalClaim method to create a claim to add to the database, which you can then modify, and use the GetRewardsClaimedByID method to retrieve the claim from the database. Ensure that you remove the claim and release any resources at the end of the test.
8.
Locate the DeleteRewardsClaimTest method by double-clicking the comment TODO: Ex2 - Add a test for DeleteRewardsClaim in the task list. This task is located in the DeleteRewardsClaimTest method.
9.
In the DeleteRewardsClaimTest method, delete the existing code.
10. Add a unit test to verify the behavior of the DeleteRewardsClaim method. Use the CreateLocalClaim method to create a claim to add to the database, which you can then delete. Ensure that you release any resources at the end of the test. 11. Save the DataAccessLayerTest file.
f Task 7: Build and test the application 1.
Build the solution and correct any errors.
2.
Run all of the tests in the solution.
3.
Verify that all of the tests succeed, including the GetContactListTest test.
4.
Start the application in Debug mode.
5.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that you can add, modify, and delete claims and that the points for the contact are adjusted correctly.
6.
Close the application.
7.
Close the solution, and then close Visual Studio.
Creating, Updating, and Deleting Entity Data
4-43
Lab Review
Review Questions 1.
Which method do you use to load entity by key into the ObjectContext object?
2.
Must you modify your code to work with stored procedures in the EDM?
3.
Which exceptions should you handle in your data access update code?
4.
Does the SaveChanges method support saving changes to multiple entities at the same time?
5.
If your entity has an identity column for its primary key in the database, how do you ensure that the new key value is returned when you call the SaveChanges method?
4-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
What are the differences between detached and attached entity objects?
2.
How do you persist changes to the database after modifying entity objects in the ObjectContext object?
3.
What are the two main tasks you must perform if you want your EDM to use stored procedures for data modifications?
Best Practices Related to Modifying Entities with the Entity Framework Supplement or modify the following best practices for your own work situations: •
When you query data that may be modified, be clear about whether the entities returned from the query will be attached or detached.
•
Any data modification should handle UpdateException and InvalidOperationException exceptions.
Creating, Updating, and Deleting Entity Data
4-45
•
For performance reasons, you should consider batching changes together where possible.
•
You should consider using stored procedures to modify your data whenever possible.
•
You should create unit tests for all of the public methods in your data access layer.
Handling Multi-User Scenarios by Using Object Services
5-1
Module 5 Handling Multi-User Scenarios by Using Object Services Contents: Lesson 1: Handling Concurrency in the Entity Framework
5-3
Lesson 2: Transactional Support in the Entity Framework
5-19
Lab: Handling Multi-User Scenarios by Using Object Services
5-32
5-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
Data applications that support multiple users can face concurrency issues when different users access the data simultaneously. The Entity Framework provides a concurrency model and transactional capabilities to help you overcome these issues. This module introduces the concurrency model and describes how the Entity Framework can make use of transactions to ensure data integrity.
Objectives After completing this module, you will be able to: •
Describe the optimistic concurrency model that the Entity Framework uses.
•
Manage transactions in applications that use the Entity Framework.
Handling Multi-User Scenarios by Using Object Services
5-3
Lesson 1
Handling Concurrency in the Entity Framework
When two or more users modify the same data simultaneously, concurrency conflicts occur. You must handle these conflicts and allow the application and users to recover gracefully. This lesson explains how the Entity Framework detects concurrency conflicts. It also describes how the Entity Framework implements an optimistic concurrency model and explains how you can implement conflict resolution in your application code.
Objectives After completing this lesson, you will be able to: •
Describe the problem associated with concurrent access to data.
•
Describe how the Entity Framework detects concurrency conflicts in a database.
•
Configure your Entity Data Model (EDM) to detect concurrency conflicts.
5-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
Handle concurrency conflicts in your application code.
•
Create unit tests to verify your concurrency-resolution logic.
Handling Multi-User Scenarios by Using Object Services
5-5
Concurrent Data Access
Key Points Any multi-user application that works with stored data must have a strategy to deal with concurrent access to its data. The Entity Framework has a clear set of rules that it follows to detect possible conflicts. You can summarize the process that the Entity Framework uses to modify data as follows: 1.
Data that may be modified by a user is loaded into the cache maintained by the ObjectContext object, either by running a query or by loading records by key.
2.
Your application code can then modify data in the cache. The ObjectStateManager object tracks these changes for the ObjectContext object.
3.
The changes made to data in the cache are persisted to the database when your application code calls the SaveChanges method on the ObjectContext object. It is at this point that the Entity Framework may encounter a concurrency conflict, because during the time that an entry was in the cache, another user may have modified it.
5-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
For example, User1 loads the contact record for the contact called Ward into the cache. At the same time, User2 also loads the record for the contact called Ward into the cache for his or her application. At this point, User1 changes the value in the CurrentPoints property of the contact from 1,000 to 800 and calls the SaveChanges method, which writes the record back to the database with the new value in the CurrentPoints property of 800. However, in User2's cache, the value in the CurrentPoints property is still 1,000. When User2 takes away 100 points, leaving 900, and then calls the SaveChanges method, this will overwrite the change made by User1. The Entity Framework must have some way of detecting that another user has modified the record.
Handling Multi-User Scenarios by Using Object Services
5-7
How the Entity Framework Detects Concurrency Conflicts
Key Points The Entity Framework uses an optimistic concurrency model, which means that it does not hold any locks in the database. When the Entity Framework saves changes to the database, it checks to see whether anyone has modified the record since it retrieved the record and placed it in the cache. If the Entity Framework detects a change, it throws an OptimisticConcurrencyException exception. By default, the Entity Framework saves changes without checking for concurrency conflicts. There are two ways that you can make the Entity Framework check for concurrency conflicts when it saves changes: 1.
In the EDM, you can set the ConcurrencyMode property of an entity property to Fixed. This tells the Entity Framework to check whether the value of this field has changed whenever it tries to save or delete a record. For example, if the StoreContact entity has a ModifiedDate column that is updated with the current date and time whenever the record is saved, you should set the ConcurrencyMode property value to Fixed for this entity property.
2.
If the EDM uses a stored procedure to make a change to the database, the Entity Framework will throw an OptimisticConcurrencyException exception
5-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
if the stored procedure reports that it changed zero rows. You must design your stored procedures so that they check whether another process has modified the record in the database since the ObjectContext object originally retrieved the record. The following code example shows an example stored procedure that will enable concurrency checking when you save a StoreContact entity to the database. You can see that the stored procedure has two ModifiedDate parameters: one contains the original date and the other the changed date. The logic in the stored procedure checks to see whether the ModifiedDate field of the record in the database has changed since the ObjectContext object originally retrieved it by comparing the value of the record's ModifiedDate field in the database with the value of the modifiedDateOriginal parameter. CREATE PROCEDURE [Sales].[uspUpdateStoreContact] @customerID [int], @contactID [int], @contactTypeID [int], @modifiedDateOriginal [datetime], @modifiedDateChanged [datetime] AS BEGIN UPDATE [Sales].[StoreContact] SET [CustomerID] = @customerID ,[ContactID] = @contactID ,[ContactTypeID] = @contactTypeID ,[ModifiedDate] = GETDATE() WHERE [CustomerID] = @customerID and [ContactID] = @contactID and [ModifiedDate] = @modifiedDateOriginal
Question: What advantages do stored procedures offer over the use of the ConcurrencyMode property as a mechanism for detecting concurrency conflicts?
Additional Reading For more information about stored procedure support in the Entity Framework, see the Stored Procedure Support (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194041. For more information about the RowsAffectedParameter parameter, see the RowsAffectedParameter (EntityTypeMapping) page at http://go.microsoft.com/fwlink/?LinkID=194042.
Handling Multi-User Scenarios by Using Object Services
5-9
Demonstration: Setting Concurrency Options in the Model
Key Points •
Configure concurrency options in the AdventureWorks EDM.
•
Set the ConcurrencyMode property of the ModifiedDate entity property of the SalesTerritory entity.
•
Use a stored procedure to detect concurrency issues on the StoreContact entity.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-05 virtual machine as Student with the password Pa$$w0rd
2.
In the E:\Demofiles folder, run Demo.bat to create the stored procedure for this demonstration.
3.
Open Microsoft® Visual Studio® 2010.
5-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
4.
In Visual Studio 2010, open the ConcurrencyDemo.sln solution in the E:\Demofiles\Mod05\Demo1\Starter folder.
5.
Open the AdventureWorksEDM model in the Entity Designer.
6.
Set the Concurrency Mode property to Fixed for the ModifiedDate entity property of the SalesTerritory entity.
7.
Use the Model Browser to update the model from the database and import the uspUpdateStoreContact stored procedure.
8.
Map the update function of the StoreContact entity to the uspUpdateStoreContact stored procedure.
9.
Save AdventureWorksEDM.edmx.
Question: What column types are suitable candidates for setting the ConcurrencyMode property to Fixed?
Handling Multi-User Scenarios by Using Object Services
5-11
Handling Optimistic Concurrency Exceptions
Key Points The Entity Framework uses an optimistic concurrency model to detect concurrency conflicts when the ObjectContext object attempts to save modified data back to the database. If the Entity Framework detects a concurrency conflict, it throws an OptimisticConcurrencyException exception for you to trap and handle in your code. This topic describes a strategy you can use to handle this exception. When the Entity Framework detects a concurrency conflict, you must decide how to resolve it. There are three basic approaches, and which one you choose depends on the specific requirements of your application. The three approaches are as follows: 1.
Discard the changes.
2.
Accept the changes, and overwrite the data in the database.
3.
Notify the user that the Entity Framework cannot save the change because someone else has modified the data, and ask the user whether to overwrite the data in the database.
5-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The ObjectContext class has a Refresh method that supports implementation of any of these approaches. You can use the Refresh method to put the cache in the ObjectContext object into a suitable state before retrying the SaveChanges method. The Refresh method refreshes the data in the cache for either a single entity object or a collection of entity objects. You control how the Refresh method refreshes the cache by using the RefreshMode parameter. The following table shows the possible values of the RefreshMode parameter. RefreshMode name
Description
ClientWins
Property changes made to objects in the cache are not replaced with values from the data source. On the next call to the SaveChanges method, these changes are sent to the data source.
StoreWins
Property changes made to objects in the cache are replaced with values from the data source.
The following code example shows how to handle a concurrency conflict detected by the Entity Framework when it attempts to save a SalesOrderHeader record. In this example, the user's change overwrites the current values in the database. [Visual Basic] Using context As New AdventureWorksEntities() Try Dim orders As ObjectQuery(Of SalesOrderHeader) = context.SalesOrderHeaders.Where( "it.CreditCardApprovalCode IS NULL").Top("100") ' Reset the order status to 4 = Rejected. order.Status = 4 ' Try to save changes, which may cause a conflict. context.SaveChanges()
Catch ex As OptimisticConcurrencyException ' Resolve the concurrency conflict by refreshing the
Handling Multi-User Scenarios by Using Object Services
5-13
' ObjectContext object before re-saving changes. context.Refresh(RefreshMode.ClientWins, orders) ' Save changes. context.SaveChanges() End Try End Using
[Visual C#] using (AdventureWorksEntities context = new AdventureWorksEntities()) { try { ObjectQuery<SalesOrderHeader> orders = context.SalesOrderHeaders.Where( "it.CreditCardApprovalCode IS NULL").Top("100"); // Reset the order status to 4 = Rejected. order.Status = 4; // Try to save changes, which may cause a conflict. context.SaveChanges(); } catch (OptimisticConcurrencyException) { // Resolve the concurrency conflict by refreshing the // ObjectContext object before re-saving changes. context.Refresh(RefreshMode.ClientWins, orders); // Save changes. context.SaveChanges(); } }
This code example shows only how to handle concurrency conflicts. How to perform additional data validation when you save changes to the database will be explained in a later module.
5-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Question: How is it possible to implement a hybrid resolution to a concurrency conflict? For example, you save a SalesOrderHeader entity, and the Entity Framework detects a conflict. You want to use all of the property values from the database except for the value of the Status property, which you do want to change. How do you achieve this?
Handling Multi-User Scenarios by Using Object Services
5-15
Creating Unit Tests to Verify Concurrency Behavior
Key Points Testing how your application handles concurrency conflicts can be challenging, especially because the effects of concurrency conflicts can sometimes be quite difficult to identify. You should create unit tests to verify that your application behaves correctly in as many different concurrency scenarios as possible. You should try to identify all of the scenarios and their expected outcomes before you write your tests. For example, in the AdventureWorks EDM, it is possible that a user may try to save changes to a contact that another user has already modified. The following code example shows how the UpdateContact method handles an OptimisticConcurrencyException exception by refreshing the context with data from the database.
5-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] Catch ex As OptimisticConcurrencyException ' The contact may have been modified, so ' get the latest version from the database. entities.Refresh(RefreshMode.StoreWins, contactToModify) ' Try to save all of the changes again. entities.SaveChanges() ' Ensure that the correct datetime is in the context. entities.Refresh(RefreshMode.StoreWins, contactToModify) Return True End Try
[Visual C#] catch (OptimisticConcurrencyException) { // The contact may have been modified, so // get the latest version from the database. entities.Refresh(RefreshMode.StoreWins, contactToModify); // Try to save all of the changes again. entities.SaveChanges(); // Ensure that the correct datetime is in the context. entities.Refresh(RefreshMode.StoreWins, contactToModify); return true; }
The following code example shows a strategy for testing this behavior. It uses two instances of the data access layer component to simulate two different users. The first user creates a new contact, the second user modifies the contact, and when the first user also tries to modify the contact, the Entity Framework detects a concurrency conflict.
Handling Multi-User Scenarios by Using Object Services
5-17
[Visual Basic] _ Public Sub UpdateContactConcurrencyTest() ' Create two instances of the DataAccessLayer ' to represent two users. Dim user1 As New DataAccessLayer() Dim user2 As New DataAccessLayer() ' User1 creates a contact—this loads the contact ' into the context. Dim contactID As Integer = user1.AddContact(CreateTestContact()) ' User2 modifies the contact. Dim user2ModifiedContact As Contact = CreateTestContact() user2ModifiedContact.ContactID = contactID user2ModifiedContact.CurrentPoints = 2000 user2.UpdateContact(user2ModifiedContact) ' User1 updates the contact—getting a conflict. Dim user1ModifiedContact As Contact = CreateTestContact() user1ModifiedContact.CurrentPoints = 3000 user1ModifiedContact.ContactID = contactID user1.UpdateContact(user1ModifiedContact) ' Get the contact from the database. Dim actualContact As Contact = GetContactById(contactID) ' Perform the test. Assert.AreEqual(2000, actualContact.CurrentPoints)
' Tidy up—delete the contact. user1.DeleteContact(contactID) user1.Dispose() user2.Dispose() End Sub
5-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] [TestMethod()] public void UpdateContactConcurrencyTest() { // Create two instances of the DataAccessLayer // to represent two users. DataAccessLayer user1 = new DataAccessLayer(); DataAccessLayer user2 = new DataAccessLayer(); // User1 creates a contact—this loads the contact // into the context. int contactID = user1.AddContact(CreateTestContact()); // User2 modifies the contact. Contact user2ModifiedContact = CreateTestContact(); user2ModifiedContact.ContactID = contactID; user2ModifiedContact.CurrentPoints = 2000; user2.UpdateContact(user2ModifiedContact); // User1 updates the contact—getting a conflict. Contact user1ModifiedContact = CreateTestContact(); user1ModifiedContact.CurrentPoints = 3000; user1ModifiedContact.ContactID = contactID; user1.UpdateContact(user1ModifiedContact); // Get the contact from the database. Contact actualContact = GetContactById(contactID); // Perform the test. Assert.AreEqual(2000, actualContact.CurrentPoints);
// Tidy up—delete the contact. user1.DeleteContact(contactID); user1.Dispose(); user2.Dispose(); }
Question: What other types of testing can you carry out to verify the behavior of your concurrency conflict-resolution code?
Handling Multi-User Scenarios by Using Object Services
5-19
Lesson 2
Transactional Support in the Entity Framework
When an application needs to update more than one table in a database as one unit of work, it is imperative that either all of the tables are updated or none are updated. The Entity Framework provides transactions that enable you to ensure that this occurs. This lesson explains how you can use transactions with the Entity Framework to help ensure the integrity of your data. The Entity Framework can make use of transactions when it saves data modifications to the database.
Objectives After completing this lesson, you will be able to: •
Describe how the Entity Framework supports the use of transactions.
•
Manage transactions in your application code.
•
Retry transactions when they fail.
5-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Transactions and the Entity Framework
Key Points Databases use transactions to ensure the consistency of the data stored in the database. A transaction is a unit of work that consists of one or more database commands that must complete as a unit. The following table describes three types of transaction. Transaction type
Description
Implicit
You do not need to write any code to use an implicit transaction. For example, an UPDATE statement in a database may be automatically executed in an implicit transaction to guarantee the integrity of the database.
Explicit
You mark the beginning and end of the transaction in your code, and you control when a transaction should be rolled back. You use explicit transactions to implement specific data integrity rules for your application. You can write explicit transactions in stored procedures or in your application code.
Handling Multi-User Scenarios by Using Object Services
Transaction type Distributed
5-21
Description A distributed transaction is similar to an explicit transaction, except that the transaction spans two or more data sources. These data sources may include databases, message queues, or some other component that understands commit and rollback semantics. Although you code the transaction in your application code, a separate coordinator component manages the transaction across the various data sources.
The Entity Framework can make use of transactions when the ObjectContext object interacts with the underlying database. Note: The data sources that are used in a distributed transaction do not have to be databases. For example, Message Queuing (also known as MSMQ) queues can participate in a transaction. Note: The Entity Framework uses transactions only during operations against the data source. Changes made to entity objects in the context are not transacted, and any changes are visible outside the context.
Question: How many operations should you include in a transaction?
Additional Reading For more information about the atomic, consistent, isolated, and durable (ACID) properties of transactions, see the ACID properties page at http://go.microsoft.com/fwlink/?LinkID=194043.
5-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Managing Transactions in the Entity Framework
Key Points When you call the SaveChanges method on your ObjectContext object, the Entity Framework creates a new transaction for the operations it performs against the database. If your context contains two new contact entities, three updated claims, and one claim marked for deletion, all six operations will be part of an implicit transaction against the database. If any of the operations fail, the Entity Framework will roll back the transaction. The Entity Framework does not make any changes to the entity objects in the context until the transaction has committed; this ensures that the state of the entity objects in the context is consistent with the state of the data in the database. If your application logic requires an explicit transaction, you should use the TransactionScope class. In the AdventureWorks EDM, you do not know the contactID property of a new contact until you save the Contact entity to the database. This is because the Contact table uses an identity column for the contactID column. Your application may need to create both a Contact entity and a Store Contact entity at the same time, and you want to ensure that you do not create a Store Contact record without its matching Contact record. The following code example shows how you can use an explicit transaction to achieve this.
Handling Multi-User Scenarios by Using Object Services
5-23
Notice that if an error occurs, the TransactionScope object handles the rollback for you, and the calls to the SaveChanges method automatically participate in the transaction defined by the TransactionScope object.
[Visual Basic] Public Function CreateNewStoreContact(ByVal contact As Contact, ByVal storecontact As StoreContact) As Boolean ' Check you have an ObjectContext object. If entities Is Nothing Then entities = New AdventureWorksEntities() Using scope As New TransactionScope() Try ' Add the contact. entities.AddToContacts(contact) entities.SaveChanges() ' Now you know the ContactID. storecontact.ContactID = contact.ContactID entities.AddToStoreContacts(storecontact) entities.SaveChanges() scope.Complete() Return True Catch ex As Exception Console.WriteLine("Transaction Rolled Back {0}", ex.Message) Return False End Try End Using End Function
5-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] public bool CreateNewStoreContact(Contact contact, StoreContact storecontact) { // Check you have an ObjectContext object. if (entities == null) entities = new AdventureWorksEntities(); using (TransactionScope scope = new TransactionScope()) { try { // Add the contact. entities.AddToContacts(contact); entities.SaveChanges(); // Now you know the ContactID. storecontact.ContactID = contact.ContactID; entities.AddToStoreContacts(storecontact); entities.SaveChanges(); scope.Complete(); return true; } catch (Exception ex) { Console.WriteLine("Transaction Rolled Back {0}", ex.Message); return false; } } }
You can extend this approach to implement a distributed transaction. In the following code example, the transaction is now a distributed transaction that includes placing a message in a message queue.
Handling Multi-User Scenarios by Using Object Services
5-25
[Visual Basic] Public Function CreateNewStoreContact(ByVal contact As Contact, ByVal storecontact As StoreContact) As Boolean ' Check you have an ObjectContext object. If entities Is Nothing Then entities = New AdventureWorksEntities() Using scope As New TransactionScope() Try ' Add the contact. entities.AddToContacts(contact) entities.SaveChanges() ' Now you know the contact ID. storecontact.ContactID = contact.ContactID entities.AddToStoreContacts(storecontact) entities.SaveChanges() If Not MessageQueue.Exists("NotifyQueue") Then MessageQueue.Create("NotifyQueue") End If Using q As New MessageQueue("NotifyQueue") Dim msg As System.Messaging.Message = New System.Messaging.Message(String.Format( "" & "<storecontact customerid='{1}' />" & "", contact.ContactID, storecontact.CustomerID)) q.Send(msg) End Using scope.Complete() Return True Catch ex As Exception Console.WriteLine("Transaction Rolled Back {0}", ex.Message) Return False End Try End Function
5-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] public bool CreateNewStoreContact(Contact contact, StoreContact storecontact) { // Check you have an ObjectContext object. if (entities == null) entities = new AdventureWorksEntities(); using (TransactionScope scope = new TransactionScope()) { try { // Add the contact. entities.AddToContacts(contact); entities.SaveChanges(); // Now you know the contact ID. storecontact.ContactID = contact.ContactID; entities.AddToStoreContacts(storecontact); entities.SaveChanges(); if (!MessageQueue.Exists("NotifyQueue")) { MessageQueue.Create("NotifyQueue"); } using (MessageQueue q = new MessageQueue("NotifyQueue")) { System.Messaging.Message msg = new System.Messaging.Message(String.Format( "" + "<storecontact customerid='{1}' />" + "", contact.ContactID, storecontact.CustomerID)); q.Send(msg); } scope.Complete(); return true; } catch (Exception ex) { Console.WriteLine("Transaction Rolled Back {0}", ex.Message); return false; } } }
Handling Multi-User Scenarios by Using Object Services
5-27
Question: Can you nest TransactionScope objects?
Additional Reading For more information about writing transaction applications, see the Implementing an Implicit Transaction using Transaction Scope page at http://go.microsoft.com/fwlink/?LinkID=194044. For more information about distributed transactions, see the Transaction Management Escalation page at http://go.microsoft.com/fwlink/?LinkID=194045.
5-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Retrying Failed Transactions
Key Points You can implement retry logic if an operation fails inside a transaction. The following code example shows how to retry the save operation. Notice that you call the SaveChanges method with the SaveOptions.None parameter. This means that the context is not updated and you can retry the save. The call to the AcceptAllChanges method updates the context after the transaction has completed successfully. [Visual Basic] Public Function CreateNewStoreContact(ByVal contact As Contact, ByVal storecontact As StoreContact) As Boolean ' Check you have an ObjectContext object. If entities Is Nothing Then entities = New AdventureWorksEntities() Dim success As Boolean = False For i As Integer = 0 To 2 Using scope = New TransactionScope()
Handling Multi-User Scenarios by Using Object Services
Try ' Add the contact. entities.AddToContacts(contact) ' Do not accept the changes in the context. ' in case you have to retry entities.SaveChanges(SaveOptions.None) ' Now you know the contact ID. storecontact.ContactID = contact.ContactID entities.AddToStoreContacts(storecontact) ' Do not accept the changes in the context, ' in case you have to retry. entities.SaveChanges(SaveOptions.None) scope.Complete() success = True break() Catch ex As Exception Console.WriteLine("Retrying transaction") End Try End Using Next i If (success) Then entities.AcceptAllChanges() Return True Else Console.WriteLine("Transaction Failed") Return False End If End Function
5-29
5-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] public bool CreateNewStoreContact(Contact contact, StoreContact storecontact) { // Check you have an ObjectContext object. if (entities == null) entities = new AdventureWorksEntities(); bool success = false; for (int i = 0; i <3; i++) { using (TransactionScope scope = new TransactionScope()) { try { // Add the contact. entities.AddToContacts(contact); // Do not accept the changes in the context. // in case you have to retry entities.SaveChanges(SaveOptions.None); // Now you know the contact ID. storecontact.ContactID = contact.ContactID; entities.AddToStoreContacts(storecontact); // Do not accept the changes in the context, // in case you have to retry. entities.SaveChanges(SaveOptions.None); scope.Complete(); success = true; break; } catch (Exception) { Console.WriteLine("Retrying transaction"); } } } if (success) { entities.AcceptAllChanges(); return true; }
Handling Multi-User Scenarios by Using Object Services
5-31
else { Console.WriteLine("Transaction Failed"); return false; } }
Question: Does the ApplyChanges method make any changes to the database?
5-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab: Handling Multi-User Scenarios by Using Object Services
Objectives After completing this lab, you will be able to: •
Manage concurrency in a multi-user application that uses the Entity Framework.
•
Create and manage transactions in an application that uses the Entity Framework.
Introduction In this lab, you will update your EDM to define how the Entity Framework detects concurrency conflicts. You will then add code to your data access layer that resolves any concurrency conflicts. You will also define a transaction that guarantees the integrity of your data when several updates take place together.
Handling Multi-User Scenarios by Using Object Services
5-33
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-05 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
5-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Scenario
Adventure Works implements an EDM to support its customer reward program. You have been asked to modify the data access layer to ensure that rewards claim data is correctly saved to the database in circumstances where multiple users edit the same records simultaneously. You have also been asked to save copies of all updated and inserted rewards claim data to an archive table to provide an audit trail of the data modifications performed by users.
Exercise 1: Handling Concurrency of Rewards Claimed Data Scenario Users have reported that sometimes a contact's reward points do not update correctly when they modify claims. You have been asked to implement concurrency checking in the data access layer to prevent these errors. You should create unit tests to verify your solution. The main tasks for this exercise are as follows: 1.
Prepare the AdventureWorks database for the lab.
Handling Multi-User Scenarios by Using Object Services
5-35
2.
Open the starter project for this exercise.
3.
Set the concurrency behavior of the Contact and RewardsClaimed entities.
4.
Add code to set the ModifiedDate property of the contact.
5.
Add code to handle OptimisticConcurrencyException exceptions in the CreateRewardsClaim method.
6.
Add code to handle OptimisticConcurrencyException exceptions in the UpdateRewardsClaim method.
7.
Add code to handle OptimisticConcurrencyException exceptions in the DeleteRewardsClaim method.
8.
Add unit tests to verify your code.
9.
Build and test the application.
f Task 1: Prepare the AdventureWorks database for the lab 1.
Log on to the 10265A-GEN-DEV-05 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Labfiles folder, run AWReset.bat.
f Task 2: Open the starter project for this exercise 1.
Open Visual Studio 2010.
2.
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex1\Starter or E:\Labfiles\Lab05\CS\Ex1\Starter folder.
f Task 3: Set the concurrency behavior of the Contact and RewardsClaimed entities 1.
Open the AdventureWorksEDM model in the Entity Designer.
2.
In the AdventureWorksEDM model, set the Concurrency Mode property of the ModifiedDate property of the Contact entity to Fixed.
3.
In the AdventureWorksEDM model, set the Concurrency Mode property of the TimeStamp property of the RewardsClaimed entity to Fixed.
4.
Save the AdventureWorks EDM model.
5-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 4: Add code to set the ModifiedDate property of the contact 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Update the contact's Modified Date property item in the task list. This task is located in the UpdateContact method.
3.
Immediately after the comment, add code that sets the ModifiedDate property of the contact being saved to the current date and time.
4.
Save the DataAccessLayer file.
f Task 5: Add code to handle OptimisticConcurrencyException exceptions in the CreateRewardsClaim method 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Handle the OptimisticConcurrencyException in CreateRewardsClaim item in the task list. This task is located in the CreateRewardsClaim method.
3.
Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException exception. In the catch block, add code that performs the following tasks: a.
Refresh the contact with the current values from the database.
b.
Deduct the points used for the claim from the contact.
c.
Set the ModifiedDate property of the contact to the current date and time.
d. Save the changes to the database.
4.
e.
Refresh the contact and the claim with the current values from the database.
f.
Return the new claimID value.
Save the DataAccessLayer file.
f Task 6: Add code to handle OptimisticConcurrencyException exceptions in the UpdateRewardsClaim method 1.
Review the task list.
Handling Multi-User Scenarios by Using Object Services
5-37
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Handle the OptimisticConcurrencyException in UpdateRewardsClaim item in the task list. This task is located in the UpdateRewardsClaim method.
3.
Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException exception. In the catch block, add code that performs the following tasks: a.
Refresh the contact with the current values from the database.
b.
Refresh the claim, keeping any changes made in the context.
c.
Deduct the points used for the original claim from the contact, and add the points used for the modified claim to the contact.
d. Set the ModifiedDate property of the contact to the current date and time.
4.
e.
Save the changes to the database.
f.
Refresh the contact and the claim with the current values from the database.
g.
Return true.
Save the DataAccessLayer file.
f Task 7: Add code to handle OptimisticConcurrencyException exceptions in the DeleteRewardsClaim method 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex1 Handle the OptimisticConcurrencyException in DeleteRewardsClaim item in the task list. This task is located in the DeleteRewardsClaim method.
3.
Immediately after the comment, add a catch block to handle an OptimisticConcurrencyException exception. In the catch block, add code that performs the following tasks: a.
Refresh the contact with the current values from the database.
b.
Refresh the claim, keeping any changes made in the context.
c.
Add the points used for the deleted claim to the contact.
d. Set the ModifiedDate property of the contact to the current date and time. e.
Save the changes to the database.
5-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
4.
f.
Refresh the contact with the current values from the database.
g.
Return true.
Save the DataAccessLayer file.
f Task 8: Add unit tests to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex1 - Create a test to verify that CreateRewardsClaim handles concurrency issues item in the task list. This task is located in the CreateRewardsClaimConcurrencyTest method.
3.
Using the comments in the CreateRewardsClaimConcurrencyTest method for guidance, write code that verifies the behavior of the CreateRewardsClaim method when two users modify the same contact while they are adding new claims to the database.
4.
Locate the UpdateRewardsClaimConcurrencyTest method by doubleclicking the comment TODO: Ex1 - Create a test to verify that UpdateRewardsClaim handles concurrency issues item in the task list. This task is located in the UpdateRewardsClaimConcurrencyTest method.
5.
Using the comments in the UpdateRewardsClaimConcurrencyTest method for guidance, write code that verifies the behavior of the UpdateRewardsClaim method when two users modify the same contact while they are updating claims to the database.
6.
Locate the DeleteRewardsClaimConcurrencyTest method by double-clicking the comment TODO: Ex1 - Create a test to verify that DeleteRewardsClaim handles concurrency issues item in the task list. This task is located in the DeleteRewardsClaimConcurrencyTest method.
7.
Using the comments in the DeleteRewardsClaimConcurrencyTest method for guidance, write code that verifies the behavior of the DeleteRewardsClaim method when two users modify the same contact while they are inserting and deleting claims in the database.
8.
Save the DataAccessLayerTest file.
f Task 9: Build and test the application 1.
Build the solution and correct any errors.
Handling Multi-User Scenarios by Using Object Services
2.
Run all of the tests in the solution.
3.
Verify that all of the tests succeed, including the CreateRewardsClaimConcurrencyTest, UpdateRewardsClaimConcurrencyTest, and DeleteRewardsClaimConcurrencyTest tests.
4.
Close the solution.
5-39
Exercise 2: Updating the RewardsClaimed and ArchivedRewardsClaimed Information by Using a Transaction Scenario You have been asked to modify the data access layer to save a copy of every new and updated reward claimed to an archive table in a separate database as an audit trail. You must ensure that you create the archived record only if the change to the reward claimed record succeeds. You should create a unit test to verify that your transaction works correctly. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Create the AdventureWorksArchivedEDM Entity Data Model.
3.
Modify the CreateRewardsClaim method to save a copy of the claim to the archive table as part of a transaction.
4.
Modify the UpdateRewardsClaim method to save a copy of the claim to the archive table as part of a transaction.
5.
Modify the unit tests to verify your code.
6.
Build and test the application.
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab05\VB\Ex2\Starter or E:\Labfiles\Lab05\CS\Ex2\Starter folder.
5-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 2: Create the AdventureWorksArchivedEDM Entity Data Model 1.
Add a new ADO.NET Entity Data Model to the DAL project. Generate the data model from the AdventureWorks Microsoft SQL Server® database and create entities for the ArchivedRewardsClaimed table.
2.
Copy the AdventureWorksArchivedEntities connection string from the App.Config file in the DAL project to the App.Config file in the DALTest project.
f Task 3: Modify the CreateRewardsClaim method to save a copy of the claim to the archive table as part of a transaction 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 In CreateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an ArchivedRewardsClaim item in the task list. This task is located in the CreateRewardsClaim method.
3.
Place the call to the SaveChanges method inside a new TransactionScope code block. In the TransactionScope code block, after the call to the SaveChanges method, add code to perform the following tasks: a.
Create a new AdventureWorksArchivedEntities context called archivedEntities.
b.
In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a copy of the data in the RewardsClaimed entity.
c.
Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.
d. Save all of the changes in the archivedEntities context. e. 4.
At the end of the TransactionScope code block, call the Complete method of the TransactionScope object.
Add code to the CreateRewardsClaim method to handle the TransactionAbortedException exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 - In CreateRewardsClaim, handle TransactionAbortedException item in the task list.
Handling Multi-User Scenarios by Using Object Services
5-41
5.
Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh the contact and the claim from the database, and throw a new DALException exception to report the error.
6.
Save the DataAccessLayer file.
f Task 4: Modify the UpdateRewardsClaim method to save a copy of the claim to the archive table as part of a transaction 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the comment TODO: Ex2 In UpdateRewardsClaim, wrap SaveChanges in a distributed transaction that creates an ArchivedRewardsClaim item in the task list. This task is located in the UpdateRewardsClaim method.
3.
Place the call to the SaveChanges method inside a new TransactionScope code block. In the TransactionScope code block, after the call to the SaveChanges method, add code to perform the following tasks: a.
Create a new AdventureWorksArchivedEntities context called archivedEntities.
b.
In the archivedEntities context, create a new ArchivedRewardsClaimed entity that contains a copy of the data in the RewardsClaimed entity.
c.
Add the new ArchivedRewardsClaimed entity to the ArchivedRewardsClaimed entity set.
d. Save all of the changes in the archivedEntities context. e.
At the end of the TransactionScope code block, call the Complete method of the TransactionScope object.
4.
Add code to the UpdateRewardsClaim method to handle the TransactionAbortedException exception. To locate the place where you must add this code, double-click the comment TODO: Ex2 - In UpdateRewardsClaim, handle TransactionAbortedException item in the task list.
5.
Add a catch block that traps TransactionAbortedException exceptions. In the catch block, refresh the contact and the claim from the database, and throw a new DALException exception to report the error.
6.
Save the DataAccessLayer file.
5-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 5: Modify the unit tests to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest file by double-clicking the comment TODO: Ex2 - Count the archived claims before the insert item in the task list. This task is located in the CreateRewardsClaimTest method.
3.
Immediately after the comment, add code to count the number of archived claims by calling the CountArchivedRewardsClaimed method.
4.
Navigate to the next comment by double-clicking the comment TODO: Ex2 Count the archived claims after the insert and test item in the task list. This task is located in the CreateRewardsClaimTest method.
5.
Immediately after the comment, add code to count the number of archived claims by calling the CountArchivedRewardsClaimed method, and verify that the number of archived rewards has increased by one.
6.
Navigate to the next comment by double-clicking the comment TODO: Ex2 Count the archived claims before the update item in the task list. This task is located in the UpdateRewardsClaimTest method.
7.
Immediately after the comment, add code to count the number of archived claims by calling the CountArchivedRewardsClaimed method.
8.
Navigate to the next comment by double-clicking the comment TODO: Ex2 Count the archived claims after the update and test item in the task list. This task is located in the UpdateRewardsClaimTest method.
9.
Immediately after the comment, add code to count the number of archived claims by calling the CountArchivedRewardsClaimed method, and verify that the number of archived rewards has increased by one.
10. Save the DataAccessLayerTest file.
f Task 6: Build and test the application 1.
Build the solution and correct any errors.
2.
Run all of the tests in the solution.
3.
Verify that all of the tests succeed, including the CreateRewardsClaimTest and UpdateRewardsClaimTest tests.
4.
Close the solution, and then close Visual Studio.
Handling Multi-User Scenarios by Using Object Services
5-43
Lab Review
Review Questions 1.
What do you need to ensure for any entity property that you decide to use for concurrency checking?
2.
What should you do in the handler for an OptimisticConcurrencyException exception?
3.
What are the two options available to you when you refresh an entry in the cache?
4.
How do you indicate that a transaction controlled by using a TransactionScope object has completed?
5.
How do you initiate a distributed transaction?
5-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
What are two ways that the Entity Framework can detect concurrency conflicts when you modify data in the database?
2.
Why does the Entity Framework use an optimistic instead of a pessimistic concurrency model?
3.
Outline a strategy to create unit tests that verify your concurrency-resolution logic.
4.
If you are using a TransactionScope object to define a transaction, how can you force a transaction to roll back?
Handling Multi-User Scenarios by Using Object Services
5-45
Best Practices Related to Handling Multi-User Scenarios by Using Object Services Supplement or modify the following best practices for your own work situations: •
Use stored procedures in your EDM to perform data modifications and identify any concurrency conflicts.
•
Add columns such as Timestamp to your database tables to help identify concurrency conflicts.
•
Use the Refresh method whenever you must ensure that you have the latest data in the cache.
•
Use the TransactionScope class to manage explicit transactions in your code.
•
If you want to implement retry behavior when a transaction fails, use the SaveChanges(SaveOptions.None) and ApplyChanges methods.
•
Create unit tests to verify all of your concurrency and transactional behavior.
Building Optimized Solutions by Using Object Services
6-1
Module 6 Building Optimized Solutions by Using Object Services Contents: Lesson 1: The Stages of Query Execution
6-3
Lesson 2: Change Tracking and Object Materialization
6-9
Lesson 3: Using Compiled Queries
6-18
Lesson 4: Using Design-Time Generated Entity Framework Views
6-24
Lesson 5: Monitoring Performance
6-29
Lesson 6: Performing Asynchronous Data Modifications
6-37
Lab: Building Optimized Solutions by Using Object Services
6-44
6-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
This module describes best practices for designing and building a scalable, optimized data access layer by using Object Services. The module introduces several techniques that you can use to optimize the performance of queries that execute against the conceptual model. It also describes a strategy that you can use to run data modification operations asynchronously.
Objectives After completing this module, you will be able to: •
Describe how the Entity Framework executes queries.
•
Understand the impact of tracking and object materialization on query performance.
•
Use compiled queries.
•
Use design-time generated views.
•
Monitor query performance.
•
Perform asynchronous data modifications.
Building Optimized Solutions by Using Object Services
6-3
Lesson 1
The Stages of Query Execution
Most applications are designed to be as highly performing as possible. Executing queries against a database can be a lengthy task, so it is important to understand which stages in the query execution process are likely to be most time-consuming and what techniques are available to reduce those times. This lesson explains the key stages in the query execution process that the Entity Framework uses to retrieve data from the underlying database and identifies the options available to maximize the performance of the process.
Objectives After completing this lesson, you will be able to: •
Explain what happens when the Entity Framework executes a query.
•
Describe where to make performance enhancements to queries.
6-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The Query Execution Process
Key Points When a client executes a query against the conceptual model, the Entity Framework must perform a sequence of operations to return entity data to the client. The following list shows the key stages of query operation: 1.
Loading metadata
2.
Opening the database connection
3.
Generating views
4.
Preparing the query
5.
Executing the query
6.
Loading and validating types
7.
Initializing change tracking
8.
Materializing the objects
Building Optimized Solutions by Using Object Services
6-5
Loading Metadata Although loading the metadata into a MetadataWorkspace object is a moderately expensive operation, it is not likely to have an impact on the performance of an individual query, because there is only a single, global instance of the MetadataWorkspace object in an application domain. The Entity Framework populates this instance once, and all ObjectContext objects in the application domain then share it.
Opening the Database Connection Opening a database connection is relatively expensive, and by default, the ObjectContext object manages this connection on your behalf, opening and closing the connection as required. You can control the connection manually through the ObjectContext object's Connection property; in some circumstances, you may be able to improve performance by using manual control. If the underlying provider supports connection pooling, the cost of opening connections is spread across the pool. This reduces the cost of opening an individual connection.
Generating Views The Entity Framework uses a set of query views to access the database. The Entity Framework creates these views once per application domain, and multiple ObjectContext instances can share them. The cost of generating these views is high; consequently, it is possible to generate them in advance and add them to the project at design time.
Preparing the Query The cost of preparing a query for execution is typically moderate, although it varies according to the complexity of the query. The Entity Framework prepares queries by generating a command tree and defining the shape of the results. This preparation must be completed once for each unique query. If you define queries by using Entity Structured Query Language (Entity SQL), the Entity Framework caches the prepared version automatically, which speeds up subsequent executions of the same query. If you use Language-Integrated Query (LINQ) to Entities, you must manually compile the query before the Entity Framework can cache the prepared version.
Executing the Query The cost of executing a query is typically low, but varies according to the complexity of the query. Subsequent executions of the same query may be faster if the underlying database caches query plans.
6-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Loading and Validating Types The cost of loading and validating types against the types defined in the conceptual model is low, and it happens once per ObjectContext instance.
Initializing Change Tracking The cost of tracking an individual object is low, but it is incurred for every object that the query returns. There is no cost incurred if the query uses the NoTracking merge option.
Materializing the Objects The cost of materializing an object is moderate, but this cost is not necessarily incurred for every object that a query returns. There is no cost if the returned entity already exists in the ObjectContext object and the query uses either of the AppendOnly or PreserveChanges merge options. Note: Queries created by using the EntityClient provider do not materialize objects.
Question: Why do queries created by using the EntityClient provider not materialize objects?
Additional Reading For more information about managing connections in the Entity Framework, see the Managing Connections and Transactions (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194046. For more information about managing connections in the Entity Framework, see the Query Execution page at http://go.microsoft.com/fwlink/?LinkID=194047.
Building Optimized Solutions by Using Object Services
6-7
Performance Enhancement Options
Key Points This topic outlines a set of strategies that you can adopt to improve the performance of queries in your Entity Framework application.
Use the NoTracking Merge Option for Queries When an ObjectContext instance tracks and manages an entity object, there is an associated cost. If you do not plan to update or delete the entities, you can use the NoTracking merge option when you run a query and create detached entity objects. Understanding when the Entity Framework materializes entity objects can also help you to manage the performance impact of queries.
Compile LINQ Queries If you plan to execute the same or similar LINQ query repeatedly in your application, you can reduce the preparation cost. To do this, compile the query and use the compiled version. Compiled queries can have parameters.
6-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Generate Views at Design Time View creation represents a significant up-front cost, because it must occur before the Entity Framework can process any queries in your application. You can generate views at design time and add the generated views to your project.
Limit the Amount of Returned Data As with any application that deals with data, you should try to retrieve just the data that your application requires. You should consider how your queries load related objects and examine options that will enable you to return your data a page at a time.
Limit the Scope of Your ObjectContext Instance The ObjectContext class manages the attached entities that your application loads from the database. You should think about the useful lifetime of a specific instance of the ObjectContext class in your application.
Manually Manage Your Database Connections By default, the ObjectContext class opens and closes connections on behalf of your application. If your application makes large numbers of calls to the SaveChanges method, you may improve your application's performance by manually opening a connection before you begin to make the calls and then closing the connection when you have completed the calls. Question: Disposing of ObjectContext instances can free up resources in your application. What negative impact on performance may occur if you frequently dispose of ObjectContext instances?
Building Optimized Solutions by Using Object Services
6-9
Lesson 2
Change Tracking and Object Materialization
Change tracking and object materialization can adversely affect the performance of your application. However, there are techniques that you can use to minimize their effect. This lesson describes how change tracking and object materialization impact the performance of your application and explains the strategies that are available to manage this performance impact.
Objectives After completing this lesson, you will be able to: •
Describe the life cycle of entities in the ObjectContext object's cache.
•
Explain when the Entity Framework materializes entity objects.
•
Describe the impact of change tracking on performance.
6-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The Life Cycle of Entity Objects
Key Points Entity objects are standard common language runtime (CLR) objects that represent types of data in the underlying database. Entity objects enable you to work with that data by using the constructs and abstractions of the Microsoft® .NET Framework rather than those of the database. To be able to improve the performance of an application that uses the Entity Framework, it is important to understand the life cycle of these entity objects.
Detached and Attached Entity Objects The Entity Framework delivers much of its functionality through the ObjectContext class, which manages a cache of entity objects. Entity objects in this cache are referred to as attached objects and support change tracking and identity resolution. Entity objects not in the cache are known as detached objects.
Creating Attached Entity Objects There are three ways to create an attached entity object:
Building Optimized Solutions by Using Object Services
1.
Run a query that uses an AppendOnly, PreserveChanges, or OverwriteChanges merge option.
2.
Load an entity object into the cache by using a Load method.
3.
Attach a detached entity object.
6-11
Accessing Attached Entity Objects The ObjectContext class organizes entity objects into collections known as entity sets. Entity sets often represent tables in the underlying database. You can iterate over these entity sets by using standard programming constructs. Every entity object has a unique key that is represented by an EntityKey object. You can directly access an entity object by its key.
Modifying Entity Objects Entity objects expose read/write properties that represent column values in the underlying database. If you change a property value, mark an entity object for deletion, or create a new entity object, the Entity Framework tracks this change by using an ObjectStateEntry object. The ObjectContext instance uses this changetracking information to persist the change to the underlying database when you call the SaveChanges method.
Detaching Entity Objects It is possible to detach an entity object from an ObjectContext instance. When you detach an entity object, the corresponding ObjectStateEntry object is removed from the ObjectContext instance.
Removing Entity Objects from Memory An ObjectContext instance holds entity objects in the cache for the duration of the life of the ObjectContext instance. Question: What factors should you consider when you decide how long to keep an ObjectContext instance in memory?
6-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Additional Reading For more information about the ObjectContext class, see the Identity Resolution, State Management, and Change Tracking (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194048. For more information about the EntityKey class, see the Working with Entity Keys (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194049.
Building Optimized Solutions by Using Object Services
Entity Materialization
Key Points When you execute a LINQ to Entities query or an Entity SQL query, the Entity Framework materializes the results from the database into CLR types. The CLR type will be one of the following: •
A collection of zero or more typed entity objects.
•
A projection of complex types from the conceptual model.
•
A CLR type that the conceptual model supports.
•
An inline collection.
•
An anonymous type.
The Entity Framework materializes entity objects when one of the following operations takes place: •
You enumerate the ObjectQuery object by using a foreach statement (Microsoft Visual C#®) or a For Each statement (Microsoft Visual Basic®).
6-13
6-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
You enumerate the ObjectQuery object by using a collection operation such as a ToArray, ToCollection, or ToList method.
•
You call the Execute method of the ObjectQuery object.
•
You apply LINQ operators, such as First or Any, to the outermost part of the query.
The MergeOption property of the query object determines which results the Entity Framework materializes into entity objects. The following table describes the different MergeOption values. MergeOption value AppendOnly
Description Objects that do not exist in the object context are attached to the context. This is the default option. If an object is already in the context, the current and original values of the object's properties in the entry are not overwritten with data source values.
OverwriteChanges Objects that do not exist in the object context are attached to the context. If an object is already in the context, the current and original values of the object's properties in the entry are overwritten with data source values. PreserveChanges
Objects that do not exist in the object context are attached to the context. If the state of the entity is Unchanged, the current and original values in the entry are overwritten with data source values. If the state of the entity is Modified, the current values of modified properties are not overwritten with data source values. The original values of unmodified properties are overwritten with the values from the data source.
NoTracking
Objects are maintained in a Detached state and are not tracked in the ObjectStateManager object.
Note: The ObjectContext class raises the ObjectMaterialized event when an entity object is materialized.
Building Optimized Solutions by Using Object Services
6-15
Question: Of the three MergeOption values (AppendOnly, OverwriteChanges, and PreserveChanges), which is likely to have the lowest impact on the performance of a query?
6-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The Impact of Change Tracking on Performance
Key Points The ObjectContext class maintains change-tracking information for attached entity objects by using ObjectStateEntry objects. An ObjectStateEntry object holds the following information for each attached entity object: •
An EntityKey instance that determines the unique identity of an entity object.
•
An EntityState object that records whether the entity object has been modified.
•
Information about any related entity objects.
•
The name of the entity set that the entity object is a member of.
•
The current and original values of the properties of the entity object.
•
The names of any modified properties.
The Entity Framework incurs a cost when it creates this change-tracking data during object materialization. There is also an ongoing cost in maintaining the change-tracking data when you make changes to entity objects. You can eliminate
Building Optimized Solutions by Using Object Services
6-17
both of these costs if you use the NoTracking merge option when you execute a query. Question: What functionality do you lose if you use the NoTracking merge option with a query?
6-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 3
Using Compiled Queries
You can improve the performance of LINQ queries by precompiling them before executing them. This avoids the default behavior where they are compiled upon every execution. This lesson describes the performance benefits that you can gain by using compiled LINQ to Entities queries in your application. It also explains how to write the code that performs the compilation.
Objectives After completing this lesson, you will be able to: •
Describe the benefits of compiled queries.
•
Explain how to compile a query.
Building Optimized Solutions by Using Object Services
6-19
The Benefits of Compiled Queries
Key Points The Adventure Works client application uses a small number of queries to retrieve data from the database for use in the user interface (UI). For example, the client application uses a query to retrieve a contact entity by key; it then uses another query to retrieve all of the claims for the contact. These queries appear to be different each time that you execute them, because they retrieve data for different contacts; however, you are repeatedly executing the same two basic queries with different parameter values. If you use standard LINQ to Entities queries, the Entity Framework will compile these queries every time you run them, resulting in a significant performance hit for your application. If you compile these queries, you will incur the cost of compilation only once per query, when it is first run. Question: What types of query will benefit from explicit compilation?
6-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
How to Compile a Query
Key Points To compile a LINQ to Entities query, you call the Compile method of the CompiledQuery class. The Compile method returns a delegate that references the compiled query. This delegate is one of the generic Func delegates, which can accommodate up to 16 query parameters, a return value, and a reference to an ObjectContext instance. The following code example shows how to compile and run a query that retrieves all of the RewardsClaimed entities from the AdventureWorks database. [Visual Basic] ' Define the delegate to reference the compiled query. Shared compiled1 As Func(Of AdventureWorksEntities, ObjectQuery(Of RewardsClaimed)) = CompiledQuery.Compile(Of AdventureWorksEntities, ObjectQuery(Of RewardsClaimed))(Function(ctx) ctx.RewardsClaimed) Shared Sub Example1() Using entities As New AdventureWorksEntities()
Building Optimized Solutions by Using Object Services
6-21
' Run the query. ' The query is compiled the first time it is run. Dim claims As IQueryable(Of RewardsClaimed) = compiled1.Invoke(entities) For Each item In claims Console.WriteLine("ClaimID: {0}, ", item.ClaimID) Next End Using End Sub
[Visual C#] // Define the delegate to reference the compiled query. static Func> compiled1 = CompiledQuery.Compile>(ctx =>ctx.RewardsClaimed); static void Example1() { using (AdventureWorksEntities entities = new AdventureWorksEntities()) { // Run the query. // The query is compiled the first time it is run. IQueryable claims = compiled1.Invoke(entities); foreach (var item in claims) { Console.WriteLine("ClaimID: {0}, ", item.ClaimID); } } }
The following code example shows how to compile and run a query that takes an integer parameter to specify which RewardsClaimed entities to retrieve from the database. [Visual Basic] ' Define the delegate to reference the compiled query. Shared compiled2 As Func(Of AdventureWorksEntities, Int32, IQueryable(Of RewardsClaimed)) = CompiledQuery.Compile(Of AdventureWorksEntities, Int32, IQueryable(Of RewardsClaimed))( Function(ctx, contactID) From claim In ctx.RewardsClaimed Where claim.ContactID = contactID Select claim)
6-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Shared Sub Example2() Using entities As New AdventureWorksEntities() Dim contactID As Integer = 23 ' Run the query. ' The query is compiled the first time it is run. Dim claims As IQueryable(Of RewardsClaimed) = compiled2.Invoke(entities, contactID) For Each item In claims Console.WriteLine("ClaimID: {0}, ", item.ClaimID) Next End Using End Sub
[Visual C#] // Define the delegate to reference the compiled query. static Func> compiled2 = CompiledQuery.Compile>( (ctx, contactID) => from claim in ctx.RewardsClaimed where claim.ContactID == contactID select claim); static void Example2() { using (AdventureWorksEntities entities = new AdventureWorksEntities()) { int contactID = 23; // Run the query. // The query is compiled the first time it is run. IQueryable claims = compiled2.Invoke(entities, contactID); foreach (var item in claims) { Console.WriteLine("ClaimID: {0}, ", item.ClaimID); } } }
Building Optimized Solutions by Using Object Services
6-23
The following code example shows how to compile and run a query that returns a calculated value from the database. In this example, the query calculates the average number of points for a claim. [Visual Basic] ' Define the delegate to reference the compiled query. Shared compiled3 As Func(Of AdventureWorksEntities, Double) = CompiledQuery.Compile(Of AdventureWorksEntities, Double)( Function(ctx) ctx.RewardsClaimed.Average(Function(claim) claim.PointsUsed)) Shared Sub Example3() Using entities As New AdventureWorksEntities() ' Run the query. ' The query is compiled the first time it is run. Dim averagePoints As Double = compiled3.Invoke(entities) Console.WriteLine("Average Points: {0}, ", averagePoints) End Using End Sub
[Visual C#] // Define the delegate to reference the compiled query. static Func compiled3 = CompiledQuery.Compile( ctx => ctx.RewardsClaimed.Average(claim => claim.PointsUsed)); static void Example3() { using (AdventureWorksEntities entities = new AdventureWorksEntities()) { // Run the query. // The query is compiled the first time it is run. Double averagePoints = compiled3.Invoke(entities); Console.WriteLine("Average Points: {0}, ", averagePoints); } }
Question: How can you overcome the limit of 16 parameters for a compiled query?
6-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 4
Using Design-Time Generated Entity Framework Views
When you execute a query, the Entity Framework generates views to access the database. You can improve performance by generating these views at design time. This lesson explains the benefits and drawbacks of using design-time generated views in your application. It also explains how to create and use these views.
Objectives After completing this lesson, you will be able to: •
Describe the benefits and drawbacks of design-time generated views.
•
Create design-time generated views.
Building Optimized Solutions by Using Object Services
6-25
The Benefits and Drawbacks of Design-Time Generated Views
Key Points Before the Entity Framework can execute a query, it must generate the set of views that it uses to access the database. It is expensive to create these views, and to reduce this cost, it is possible to generate the views at design time and include them in the project. To automate view creation, you must use pre-build and post-build steps in your project, which increases the complexity of your build environment. If you use Microsoft Visual Studio® to create ASP.NET Web sites that access your Entity Data Model (EDM), you cannot use pre-build and post-build steps. To automate the creation of views in this environment, you should consider one of the following options: •
Place your EDM in a separate class library.
•
Use an ASP.NET Web application project.
6-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Note: You must modify the connection string in your App.Config or Web.Config file if you use views generated at design time.
You can manually generate the views by using the EDM Generator tool. Question: When are views created if you do not use pre-generated views?
Additional Reading For more information about using the EDM Generator tool, see the EDM Generator (EdmGen.exe) page at http://go.microsoft.com/fwlink/?LinkID=194050. For more information about using a model defined in a class library, see the How to: Use a Model Defined in a Class Library (Entity Data Model Tools) page at http://go.microsoft.com/fwlink/?LinkID=194051.
Building Optimized Solutions by Using Object Services
6-27
Demonstration: Generating Views at Design Time to Improve Query Performance
Key Points •
Open the existing application and configure it to copy the model and mapping files to the output directory.
•
Use EdmGen.exe to add view generation to the DAL project during the prebuild event.
•
Add the views to the solution.
•
Update the connection strings to use the design-time generated views.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-06 virtual machine as Student with the password Pa$$w0rd.
2
Run E:\Demofiles\Demo.bat to set up the database for this demonstration.
3.
Open Visual Studio 2010.
6-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
4.
In Visual Studio 2010, open the ViewGeneration.sln solution in the E:\Demofiles\Mod06\Demo1\Starter folder.
5.
Change the Metadata Artifact Processing property of the AdventureWorksArchivedEDM.edmx file to Copy Output Directory.
6.
Save the file and build the solution.
7.
Add view generation to the DAL project by using EdmGen.exe to generate the views during the pre-build event as the following code example shows.
[Visual C#] "%windir%\Microsoft.NET\Framework\v4.0.30319\EdmGen.exe" /nologo /language:CSharp /mode:ViewGeneration "/inssdl:$(TargetDir)AdventureWorksArchivedEDM.ssdl" "/incsdl:$(TargetDir)AdventureWorksArchivedEDM.csdl" "/inmsl:$(TargetDir)AdventureWorksArchivedEDM.msl" "/outviews:$(ProjectDir)AdventureWorksArchivedEDM.Views.cs"
Note: This command is one long line.
8.
Save all files in the solution and then build the solution.
9.
In Solution Explorer, right-click the DAL project, point to Add, and then click Existing Item.
10. In the Add Existing Item - DAL dialog box, click AdventureWorksArchivedEDM.Views.cs, and then click Add. 11. Build the solution. 12. In App.Config for the TimingTests project, update the connection strings to use the new metadata resources. Question: If you pre-generate views in a class library project, what changes do you need to make in any client applications that use the class library?
Building Optimized Solutions by Using Object Services
6-29
Lesson 5
Monitoring Performance
Whenever you are trying to improve the performance of an application, it is useful to monitor the application to provide a benchmark for the performance before modifications and compare it with a reading after you have updated the code. In Entity Framework data access, you should also monitor the SQL statements that are generated to check for complex, time-consuming queries. This lesson explains how to monitor the SQL statements that the Entity Framework generates to interact with the underlying database. You can analyze problems more efficiently if you know which SQL statements the Entity Framework is running against your database. The lesson also describes the performance monitoring counters that can provide you with useful information about the performance of your Entity Framework application.
6-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Objectives After completing this lesson, you will be able to: •
Explain how to log the SQL statements that the Entity Framework executes.
•
Describe the key Performance Monitor counters for monitoring the performance of data access operations.
Building Optimized Solutions by Using Object Services
6-31
Logging SQL Statements Generated by the Entity Framework
Key Points The ObjectQuery and EntityCommand classes have a ToTraceString method that returns the text of the SQL statements that the Entity Framework uses to retrieve data from the database. This information is useful when you analyze performance problems, especially those that arise from complex queries or complex mapping scenarios. The following code example shows how to view the SQL statements that the Entity Framework runs against the database when your application uses a LINQ to Entities query. [Visual Basic] ''' <summary> ''' Returns a list of all of the Contact entities from the database. ''' ''' Public Function GetContactList() As List(Of Contact) ' Check you have an ObjectContext object.
6-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
If entities Is Nothing Then entities = New AdventureWorksEntities() ' Create a query from the entity set. Dim contacts As ObjectQuery(Of Contact) = entities.Contacts contacts.MergeOption = MergeOption.NoTracking ' Define the query. var(Query = From c In contacts Select c) ' Display the database commands for the query. Console.WriteLine(DirectCast(Query, ObjectQuery(Of Contact)).ToTraceString()) ' Execute the query. Dim results As List(Of Contact) = Query.ToList() ' Return the results in a list. Return results End Function
[Visual C#] /// /// /// ///
<summary> Returns a list of all of the Contact entities from the database.
public List GetContactList() { // Check you have an ObjectContext object. if (entities == null) entities = new AdventureWorksEntities(); // Create a query from the entity set. ObjectQuery contacts = entities.Contacts; contacts.MergeOption = MergeOption.NoTracking; // Define the query. var query = from c in contacts select c; // Display the database commands for the query. Console.WriteLine(((ObjectQuery)query).ToTraceString());
Building Optimized Solutions by Using Object Services
// Execute the query. List results = query.ToList(); // Return the results in a list. return results; }
Question: What alternatives to the ToTraceString method may be available?
6-33
6-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using Performance Monitor
Key Points There are no performance counters available specifically for the Entity Framework. To monitor the performance of your application's data access operations, you should use the ADO.NET performance counters and any performance counters for your database. The .NET Framework includes ADO.NET performance counters for the System.Data.SqlClient and System.Data.OracleClient providers. These performance counters provide detailed information about your application's use of database connections and connection pools. The screen shot on the slide associated with this topic shows a user adding the System.Data.SqlClient counters to the Performance Monitor tool in Windows® 7. The following table describes the ADO.NET performance counters.
Building Optimized Solutions by Using Object Services
Performance counter
6-35
Description
HardConnectsPerSecond
The number of connections per second to a database server in your application.
HardDisconnectsPerSecond
The number of disconnections per second from a database server in your application.
NumberOfActiveConnectionPoolGroups
The number of unique connection pool groups that are active. The number of unique connection strings in the application domain determines the value of this counter.
NumberOfActiveConnectionPools
The total number of connection pools.
NumberOfActiveConnections
The number of active connections that your application currently uses.
NumberOfFreeConnections
The number of connections available for use in the connection pools.
NumberOfInactiveConnectionPoolGroups The number of unique connection pool groups that are marked for pruning. The number of unique connection strings in the application domain controls determines the value of this counter. NumberOfInactiveConnectionPools
The number of inactive connection pools that have not had any recent activity and are waiting to be disposed of.
NumberOfNonPooledConnections
The number of active connections that are not pooled.
NumberOfPooledConnections
The number of active connections that the connection pooling infrastructure currently manages.
NumberOfReclaimedConnections
The number of connections that have been reclaimed through garbage collection. Application performance is impaired if you do not explicitly close or dispose of connections.
6-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Performance counter
Description
NumberOfStasisConnections
The number of connections currently awaiting completion of an action. These connections are unavailable for use by your application.
SoftConnectsPerSecond
The number of active connections that your application pulls from the connection pool every second.
SoftDisconnectsPerSecond
The number of active connections that your application returns to the connection pool every second.
The performance counters NumberOfFreeConnections, NumberOfActiveConnections, SoftDisconnectsPerSecond, and SoftConnectsPerSecond are not enabled by default. To enable these counters, add the information in the following code example to the application's configuration file. <system.diagnostics> <switches>
Question: Which performance counter can you monitor to verify that your application correctly closes connections?
Additional Reading For more information about the ADO.NET performance counters, see the Performance Counters (ADO.NET) page at http://go.microsoft.com/fwlink/?LinkID=194052. For more information about runtime profiling, see the Runtime Profiling page at http://go.microsoft.com/fwlink/?LinkID=194053.
Building Optimized Solutions by Using Object Services
6-37
Lesson 6
Performing Asynchronous Data Modifications
Persisting changes to a database can be a slow process due to many factors such as network speed and server speed. You can improve the perceived performance of an application by persisting these changes while enabling users to continue working on their next task. This lesson explains how asynchronous data modifications can improve the performance of client applications. The lesson describes a pattern that uses the BackgroundWorker class to perform data modifications asynchronously on behalf of your application.
Objectives After completing this lesson, you will be able to: •
Explain the benefits of asynchronous data modifications.
•
Describe how to perform asynchronous data modifications.
6-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The Benefits of Asynchronous Data Modifications
Key Points Data modifications can take a noticeable amount of time to complete, resulting in a poor user experience. For example, in the Adventure Works Rewards Claims application, users may find that it takes an unacceptable amount of time to add a claim to a contact. If you perform this modification asynchronously, you can return control to the client application as soon as you start the data modification process. This will enable the user to continue to use the application while the changes are persisted to the underlying database. The Entity Framework performs data modifications in two stages. In the first stage, you modify data in the cache that the ObjectContext object maintains. You can perform this data modification synchronously, because this type of operation is typically fast. In the second stage, you persist changes to the database by calling the SaveChanges method. This operation can be slow, especially if you have batched together many changes in the ObjectContext instance. You can perform this operation asynchronously. If the Entity Framework detects any errors during the call to the SaveChanges method, you must handle these errors automatically or find a way to report them
Building Optimized Solutions by Using Object Services
6-39
back to the UI. If you report them back to the UI, you must remember that the user may be in the middle of some other task. Question: Will your client application have access to the changed data values before the asynchronous method has finished persisting data to the database?
6-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
How to Perform Asynchronous Data Modifications
Key Points The following code example shows how to add a new reward entity to the database asynchronously. The example uses a BackgroundWorker object to add the new reward entity. When the operation is complete, the background task notifies the UI of the change by calling the OnModification method to fire an event that is handled in the UI. [Visual Basic] Public Sub AddReward(ByVal reward As Reward) ' Use a BackgroundWorker object to perform the data modification. BackgroundWorker(bw = New BackgroundWorker()) bw.WorkerSupportsCancellation = False bw.WorkerReportsProgress = False ' Any unhandled exceptions will be passed to the ' RunWorkerCompleted evaent handler. AddHandler bw.DoWork, Sub(o, args) Using entities As New AdventureWorksEntities()
Building Optimized Solutions by Using Object Services
6-41
Try reward.RewardID = GetNextRewardID() entities.AddToRewards(reward) entities.SaveChanges() args.Result = reward.RewardID Catch ex As InvalidOperationException Throw New DALException( "There was a problem adding the new Reward", ex) Catch ex As UpdateException Throw New DALException( "There was a problem saving the new Reward to the database", ex) End Try End Using End Sub ' Check for errors and notify the UI. AddHandler bw.RunWorkerCompleted, Sub(o, args) If args.Error IsNot Nothing Then OnDataModificationCompleted(False, args.Error.Message, -1) Else OnDataModificationCompleted(True, "Deleted Contact with ContactID: " & DirectCast(args.Result, Integer), DirectCast(args.Result, Integer)) End If End Sub ' Perform the modification on the background thread. bw.RunWorkerAsync() End Sub
6-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] public void AddReward(Reward reward) { // Use a BackgroundWorker object to perform the data modification. BackgroundWorker bw = new BackgroundWorker(); bw.WorkerSupportsCancellation = false; bw.WorkerReportsProgress = false; // Any unhandled exceptions will be passed to the // RunWorkerCompleted evaent handler. bw.DoWork += (o, args) => { using (AdventureWorksEntities entities = new AdventureWorksEntities()) { try { reward.RewardID = GetNextRewardID(); entities.AddToRewards(reward); entities.SaveChanges(); args.Result = reward.RewardID; } catch (InvalidOperationException ex) { throw new DALException( "There was a problem adding the new Reward", ex); } catch (UpdateException ex) { throw new DALException( "There was a problem saving the new Reward to the database", ex); } } }; // Check for errors and notify the UI. bw.RunWorkerCompleted += (o, args) => { if (args.Error != null)
Building Optimized Solutions by Using Object Services
6-43
OnDataModificationCompleted(false, args.Error.Message, -1); else OnDataModificationCompleted(true, "Deleted Contact with ContactID: " + (int)args.Result, (int)args.Result); }; // Perform the modification on the background thread. bw.RunWorkerAsync(); }
Using the RunWorkerCompleted event enables you to pass exceptions from the background thread to the main thread and safely notify the UI of the result. Note: The ObjectContext class is not thread-safe, so be careful when you create background threads to perform asynchronous data modifications.
Question: Will any data be saved to the database if the call to the SaveChanges method throws an exception?
Additional Reading For more information about the BackgroundWorker component, see the BackgroundWorker Component Overview page at http://go.microsoft.com/fwlink/?LinkID=194054.
6-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab: Building Optimized Solutions by Using Object Services
Objectives After completing this lab, you will be able to: •
Compare the performance of different query implementations.
•
Create, update, and delete entity data asynchronously.
Introduction In this lab, you will write code to analyze the performance of a query that you will implement by using different technologies and options. You will compare the results of the same query when you implement it by using LINQ to Entities, compiled LINQ to Entities, and Entity SQL. You will explore the effect of change tracking and generating views at design time on query performance. You will also perform data modifications asynchronously.
Building Optimized Solutions by Using Object Services
6-45
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-06 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
6-46
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Scenario
Adventure Works implements an entity model to support its customer reward program. Users of the client application have complained about the poor performance of some operations. You have been asked to evaluate the various options for running queries that the Entity Framework offers. You have also been asked to modify the existing data access layer to perform data modifications asynchronously.
Exercise 1: Improving the Performance of Query Operations Scenario In this exercise, you will create a console application to explore the performance of different query implementations in the data access layer and analyze the results. The main tasks for this exercise are as follows: 1.
Prepare the AdventureWorks database for the lab.
2.
Open the starter project for this exercise.
3.
Print timing information during query execution.
Building Optimized Solutions by Using Object Services
4.
Add code to define a compiled LINQ query.
5.
Add code to invoke the compiled LINQ query.
6.
Add code to retrieve all of the contact entities by using Entity SQL.
7.
Modify the GetContactList method to check the NoTracking variable.
8.
Build and test the application.
9.
Pre-generate views to improve query performance.
6-47
f Task 1: Prepare the AdventureWorks database for the lab 1.
Log on to the 10265A-GEN-DEV-06 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Labfiles folder, run AWReset.bat.
f Task 2: Open the starter project for this exercise 1.
Open Visual Studio 2010.
2.
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex1\Starter or E:\Labfiles\Lab06\CS\Ex1\Starter folder.
f Task 3: Print timing information during query execution 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Return all contacts with timings item in the task list. This task is located in the GetContactListDetail method.
3.
Delete the comment in the GetContactListDetail method.
4.
Write code that performs the following tasks: a.
Instantiate and start two Stopwatch objects called totaltime and stagetime.
b.
Check whether the entities variable is null. If it is, instantiate it as a new instance of the AdventureWorksEntities context object.
Note: The entities variable is a private field in the DataAccessLayer class. Your code should perform all data access operations by using this context object.
6-48
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
c.
Print the value of the ElapsedTime property from the stagetime object, and then restart the Stopwatch.
d. Retrieve an ObjectQuery object from the context's Contacts property.
5.
e.
Print the value of the ElapsedTime property from the stagetime object, and then restart the Stopwatch.
f.
Define and execute a LINQ query to retrieve all of the contacts from the ObjectQuery object, and then save the results to a List object.
g.
Print the value of the ElapsedTime property from the stagetime object, and then restart the Stopwatch.
h.
Print the value of the ElapsedTime property from the totaltime object, and then restart the Stopwatch.
i.
Return the List object.
Save the DataAccessLayer code file.
f Task 4: Add code to define a compiled LINQ query 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Define the compiled LINQ query item in the task list. This task is located in the DataAccessLayer class.
3.
Immediately after the comment, add code to define a compiled LINQ query called compiledQuery by using a static function. The query should return all of the contact entities from the EDM.
4.
Save the DataAccessLayer code file.
f Task 5: Add code to invoke the compiled LINQ query 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Retrieve all contacts using the compiled query item in the task list. This task is located in the GetContactListEntityCompiledLINQ method.
3.
Delete the comment in the GetContactListEntityCompiledLINQ method.
4.
Write code that performs the following tasks:
Building Optimized Solutions by Using Object Services
6-49
a.
Check whether the entities variable is null. If it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Obtain an ObjectQuery object by invoking the compiled LINQ query.
c.
If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption property to NoTracking.
d. Return the contact entities in a List object. 5.
Save the DataAccessLayer code file.
f Task 6: Add code to retrieve all of the contact entities by using Entity SQL 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Retrieve all contacts using Entity SQL item in the task list. This task is located in the GetContactListEntityQuery method.
3.
Delete the comment in the GetContactListEntityQuery method.
4.
Write code that performs the following tasks: a.
Check whether the entities variable is null. If it is, instantiate it as a new instance of the AdventureWorksEntities context object.
b.
Obtain an ObjectQuery object by creating a query that uses Entity SQL to retrieve all of the contact entities from the EDM.
c.
If the value of the NoTracking variable is true, set the ObjectQuery object's MergeOption property to NoTracking.
d. Return the contact entities in a List object. 5.
Save the DataAccessLayer code file.
f Task 7: Modify the GetContactList method to check the NoTracking variable 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex1 - Check NoTracking item in the task list. This task is located in the GetContactList method.
6-50
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
3.
Modify the line of code immediately below the comment to check whether the value of the NoTracking variable is true before you set the MergeOption property to NoTracking.
4.
Save the DataAccessLayer code file.
f Task 8: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
Observe the timing results obtained by running the TimingTests application: a.
In the command window, on the Detailed Timing for GetContactList() page, make a note of the Total Time values, and then press ENTER.
b.
In the command window, on the Compare Implementations of GetContactList() page, write down the Average values, and then press ENTER.
f Task 9: Pre-generate views to improve query performance 1.
Open the AdventureWorksEDM.edmx file and change the Metadata Artifact Processing property to Copy to Output Directory.
2.
Save the AdventureWorksEDM.edmx file and build the solution.
3.
Add view generation to the DAL project by using EdmGen.exe to generate the views during the pre-build event.
4.
Add the generated views to the project.
5.
Update the connection strings in the TimingTests project to use the new metadata resources.
6.
Start the application in Debug mode.
7.
Observe the timing results obtained by running the TimingTests application:
8.
a.
In the command window, on the Detailed Timing for GetContactList() page, write down the Total Time values, and then press ENTER.
b.
In the command window, on the Compare Implementations of GetContactList() page, write down the Average values, and then press ENTER.
Close the solution.
Building Optimized Solutions by Using Object Services
6-51
Exercise 2: Improving the Performance of Update Operations Scenario In this exercise, you will modify the data access layer so that you can create, update, and delete claim entities asynchronously. You will use the BackgroundWorker class to perform the modifications on a background thread. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Modify the CreateRewardsClaim method to run asynchronously.
3.
Modify the UpdateRewardsClaim method to run asynchronously.
4.
Modify the DeleteRewardsClaim method to run asynchronously.
5.
Modify your unit tests to verify your asynchronous code.
6.
Build and test the application.
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab06\VB\Ex2\Starter or E:\Labfiles\Lab06\CS\Ex2\Starter folder.
f Task 2: Modify the CreateRewardsClaim method to run asynchronously 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In CreateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in the CreateRewardsClaim method.
3.
Immediately after the comment, write code that performs the following tasks:
4.
a.
Instantiate a new BackgroundWorker object.
b.
Set the WorkerSupportsCancellation property to false.
c.
Set the WorkerReportsProgress property to false.
Locate the next comment TODO: Ex2 - In CreateRewardsClaim, place existing code in DoWork item in the task list. This task is located in the CreateRewardsClaim method.
6-52
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
5.
Assign the existing code in the CreateRewardsClaim method to the BackgroundWorker object's DoWork event handler by using a lambda expression. Replace the two existing return statements with statements that assign the new claim to the Result property of the DoWork event's args parameter.
6.
Locate the next comment TODO: Ex2 - In CreateRewardsClaim, implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is located in the CreateRewardsClaim method.
7.
Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted method with false as the first parameter, the error message as the second parameter, and -1 as the third parameter. If the DoWork event completed without errors, call the OnDataModificationCompleted method with true as the first parameter, a success message as the second parameter, and the claimID property of the new claim as the third parameter.
8.
Locate the next comment TODO: Ex2 - In CreateRewardsClaim, start the BackgroundWorker item in the task list. This task is located in the CreateRewardsClaim method.
9.
Immediately after the comment, add code to start the BackgroundWorker component running asynchronously.
10. Save the DataAccessLayer code file.
f Task 3: Modify the UpdateRewardsClaim method to run asynchronously 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in the UpdateRewardsClaim method.
3.
Immediately after the comment, write code that performs the following tasks: a.
Instantiate a new BackgroundWorker object.
b.
Set the WorkerSupportsCancellation property to false.
c.
Set the WorkerReportsProgress property to false.
Building Optimized Solutions by Using Object Services
6-53
4.
Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim, place existing code in DoWork item in the task list. This task is located in the UpdateRewardsClaim method.
5.
Assign the existing code in the UpdateRewardsClaim method to the BackgroundWorker object's DoWork event handler by using a lambda expression. Replace the two existing return statements with statements that assign the updated claim to the Result property of the DoWork event's args parameter.
6.
Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim, implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is located in the UpdateRewardsClaim method.
7.
Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted method with false as the first parameter, the error message as the second parameter, and -1 as the third parameter. If the DoWork event completed without errors, call the OnDataModificationCompleted method with true as the first parameter, a success message as the second parameter, and the claimID property of the updated claim as the third parameter.
8.
Locate the next comment by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaim, start the BackgroundWorker item in the task list. This task is located in the UpdateRewardsClaim method.
9.
Immediately after the comment, add code to start the BackgroundWorker component running asynchronously.
10. Save the DataAccessLayer code file.
f Task 4: Modify the DeleteRewardsClaim method to run asynchronously 1.
Review the task list.
2.
Open the DataAccessLayer code file by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim, instantiate a BackgroundWorker item in the task list. This task is located in the DeleteRewardsClaim method.
3.
Immediately after the comment, write code that performs the following tasks: a.
Instantiate a new BackgroundWorker object.
6-54
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
b.
Set the WorkerSupportsCancellation property to false.
c.
Set the WorkerReportsProgress property to false.
4.
Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim, place existing code in DoWork item in the task list. This task is located in the DeleteRewardsClaim method.
5.
Assign the existing code in the DeleteRewardsClaim method to the BackgroundWorker object's DoWork event handler by using a lambda expression. Delete the two existing return statements.
6.
Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim, implement the BackgroundWorkers RunWorkerCompleted event item in the task list. This task is located in the DeleteRewardsClaim method.
7.
Use a lambda expression to implement the BackgroundWorker object's RunWorkerCompleted event handler. If there were errors in the DoWork event, call the OnDataModificationCompleted method with false as the first parameter, the error message as the second parameter, and -1 as the third parameter. If the DoWork event completed without errors, call the OnDataModificationCompleted method with true as the first parameter, a success message as the second parameter, and the claimID property of the deleted claim as the third parameter.
8.
Locate the next comment by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaim, start the BackgroundWorker item in the task list. This task is located in the DeleteRewardsClaim method.
9.
Immediately after the comment, add code to start the BackgroundWorker component running asynchronously.
10. Save the DataAccessLayer code file.
f Task 5: Modify your unit tests to verify your asynchronous code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex2 - In CreateRewardsClaimTest, call CreateRewardsClaim item in the task list. This task is located in the CreateRewardsClaimTest method.
3.
Review the existing code in the CreateRewardsClaimTest method.
4.
Immediately after the comment, call the CreateRewardsClaim method in the data access layer, passing the test claim called claim as a parameter.
Building Optimized Solutions by Using Object Services
6-55
5.
Locate the next TODO comment in the CreateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - In CreateRewardsClaimTest, check the values retrieved from the database item in the task list.
6.
Immediately after the comment, add code to check that the property values of the claim object match those of the lastClaim object.
7.
Locate the first TODO comment in the UpdateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaimTest, modify the claim and save the changes item in the task list.
8.
Review the existing code in the UpdateRewardsClaimTest method.
9.
Immediately after the comment, modify the PointsUsed and RewardID properties of the claim object, and call the UpdateRewardsClaim method in the data access layer, passing the test claim called claim as a parameter.
10. Locate the next TODO comment in the UpdateRewardsClaimTest method by double-clicking the comment TODO: Ex2 - In UpdateRewardsClaimTest, check the values retrieved from the database item in the task list. 11. Immediately after the comment, add code to check that the property values of the claim object match those of the updatedClaim object and that the value of the updateResult variable is true. 12. Locate the first TODO comment in the DeleteRewardsClaimTest method by double-clicking the TODO: Ex2 - In DeleteRewardsClaimTest, delete the claim item in the task list. 13. Review the existing code in the DeleteRewardsClaimTest method. 14. Immediately after the comment, call the DeleteRewardsClaim method in the data access layer, passing the claim object's ClaimID property as a parameter. 15. Locate the next TODO comment in the DeleteRewardsClaimTest method by double-clicking the comment TODO: Ex2 - In DeleteRewardsClaimTest, check the delete succeeded item in the task list. 16. Immediately after the comment, add code to check that the value of the deleteResult variable is true. 17. Save the DataAccessLayerTest code file.
f Task 6: Build and test the application 1.
Build the solution and correct any errors.
2.
Run all of the tests in the solution.
6-56
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
3.
Verify that all of the tests succeed.
4.
Start the Customer Rewards application in Debug mode.
5.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that you can add, modify, and delete claims and that the contact’s points are adjusted correctly.
6.
Close the application.
7.
Close the solution, and then close Visual Studio.
Building Optimized Solutions by Using Object Services
6-57
Lab Review
Review Questions 1.
In the detailed timings for the GetContactList method results, what is the likely explanation for the context creation time in the first run, as compared with subsequent runs?
2.
In the detailed timings for the GetContactList method results, what is the likely explanation for the query run time in the first run, as compared with subsequent runs?
3.
In the comparison of implementations of the GetContactList method results, why does the NoTracking merge option appear to be slow on all runs?
4.
How do you write unit tests for asynchronous operations?
6-58
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
What are the main options that you can use to improve the performance of queries in the Entity Framework?
2.
How can you see which SQL statements the Entity Framework is executing against the underlying database?
3.
What information do the ADO.NET performance counters provide?
Best Practices Related to Building Optimized Solutions by Using Object Services Supplement or modify the following best practices for your own work situations: •
Use the NoTracking merge option when you run queries.
•
Compile your LINQ to Entities queries.
•
Pre-generate the views in your Entity Framework application.
Building Optimized Solutions by Using Object Services
•
Limit the amount of data that you return from queries.
•
Limit the scope of your ObjectContext objects.
•
Manage your database connections manually.
•
Perform data modification operations asynchronously.
•
Be aware that the ObjectContext class is not thread-safe.
6-59
Customizing Entities and Building Custom Entity Classes
7-1
Module 7 Customizing Entities and Building Custom Entity Classes Contents: Lesson 1: Overriding Generated Classes
7-3
Lesson 2: Using Templates to Customize Entities
7-17
Lesson 3: Creating and Using Custom Entity Classes
7-34
Lab: Customizing Entities and Building Custom Entity Classes
7-47
7-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
You may find that you want to add custom business logic to your entities to meet your application requirements. The Entity Framework generates partial classes, so you can extend these to include whatever code you need. You can also modify the templates that the code generation tools use to configure entity classes to inherit from additional custom interfaces. Alternatively, if your custom business logic already exists in a business class, you can use inheritance and attributes to convert the class into an entity class. This module describes how to customize and extend entities with your own business logic.
Objectives After completing this module, you will be able to: •
Use partial classes and methods to add business logic to generated code.
•
Create and use templates to customize code generation.
•
Modify existing business classes to take advantage of entity functionality.
Customizing Entities and Building Custom Entity Classes
7-3
Lesson 1
Overriding Generated Classes
Generally, you will want to take advantage of the ease of use of generated entity classes. However, you will sometimes need to add small blocks of code for custom functionality. This lesson describes how you can use partial classes and methods to add functionality to the existing classes.
Objectives After you have completed this lesson, you will be able to: •
Use partial classes.
•
Use partial property change methods.
•
Use partial events.
•
Create extension methods.
7-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using Partial Classes
Key Points When you create an Entity Data Model (EDM) by using Microsoft® Visual Studio® (either manually or from an existing database), classes are automatically generated based on the ObjectContext and EntityObject classes. These generated classes contain entity-specific methods and events, but do not contain business logic. If you add your business logic code to these classes, it will be overwritten if the classes are later regenerated. However, because the entity classes are partial classes, you can extend them to add custom functionality to your entities. At compile time, the compiler creates one class from the multiple partial classes in the same namespace. Note: When you use partial classes, you should only list the base classes and attributes in one of the partial classes.
For example, if you have a calculated column in your database, you can easily generate the data for display to the user in the front end without performing a round trip to save the data and retrieve the calculated column. To do this, you add
Customizing Entities and Building Custom Entity Classes
7-5
a partial class for the entity. In that class, you create a method to calculate the value and pass it to the user interface. You can then still allow the calculation to be performed in the database when the data is saved at an appropriate time. Question: You have added some methods that contain business logic to your entity class, but the code seems to have disappeared. What is happening?
Additional Reading For more information about partial classes, see the Partial Classes section of the Partial Classes and Methods (C# Programming Guide) page at http://go.microsoft.com/fwlink/?LinkID=194055.
7-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using Partial Property Change Methods
Key Points In addition to partial classes, you can also use partial methods to add custom code to your entities. The generated code for each entity contains definitions for an OnChanging and an OnChanged method. Object Services calls these two methods immediately before and after a property is changed. You can add partial methods for these two methods in a partial class to create custom logic that executes when properties are changed. For example, in the OnChanging method, you could write validation code and in the OnChanged method, you could write change logging code.
Example To add validation code for the ListPrice property of a Product entity, you create a partial class for the entity and implement the OnListPriceChanging event in that class as the following code example shows.
Customizing Entities and Building Custom Entity Classes
7-7
[Visual Basic] Partial Class Product Private Partial Sub OnListPriceChanging(ByVal value As [Decimal]) If value < 0 Then Throw New Exception("List Price must be greater than zero") End If End Sub End Class
[Visual C#] partial class Product { partial void OnListPriceChanging(Decimal value) { if (value < 0) { throw new Exception("List Price must be greater than zero"); } } }
The OnChanging and OnChanged methods also execute during object materialization, so any existing data in your data source will be validated when it loads. Question: When does the code in an OnChanged method execute?
Additional Reading For more information about partial methods, see the Partial Methods section of the Partial Classes and Methods (C# Programming Guide) page at http://go.microsoft.com/fwlink/?LinkID=194056.
7-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Using the OnChanging Method
Key Points •
Review the partial classes and methods in generated code.
•
Review the OnChanging method used to validate user input.
•
Review the XAML for a list box.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-07 virtual machine as Student with the password Pa$$w0rd.
2.
Start Visual Studio 2010.
3.
In Visual Studio 2010, open the ProductApplication.sln solution in the E:\Demofiles\Mod07\OnPropertyChangingDemo\ProductApplication folder.
4.
Open AWModel.Designer.cs.
5.
In the Product class, locate the ListPrice property.
Customizing Entities and Building Custom Entity Classes
7-9
6.
Review the code in the set, particularly the call to the OnListPriceChanging method.
7.
Note the OnListPriceChanging partial method definition.
8.
Open ProductExtension.cs.
9.
Note the partial class definition, the partial method definition, and the contents of the OnListPriceChanging method.
10. Open MainWindow.xaml. 11. Review the XAML for the List Price text box. 12. Run and test the application. 13. Close Visual Studio.
Question: Why don't you need to define that the class in ProductExtension.cs inherits from EntityObject?
7-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using Partial Events
Key Points There are four key overridable events that you can use to customize your application.
ObjectContext.SavingChanges This event executes just before changes that are cached in the ObjectContext object are saved to the data source. You can use this event to validate data that is stored in the ObjectContext object and the changes are persisted to the database. The ObjectContext object holds the data for all of the entities in a model, so the SavingChanges event provides one central place to define all of your validation logic. The following code example shows how to implement the SavingChanges event handler by overriding the OnContextCreated partial method.
Customizing Entities and Building Custom Entity Classes
7-11
[Visual Basic] Private Partial Sub OnContextCreated() AddHandler Me.SavingChanges, AddressOf AWSavingChanges End Sub Private Shared Sub AWSavingChanges(ByVal sender As Object, ByVal e As EventArgs) For Each entry As ObjectStateEntry In DirectCast(sender, ObjectContext).ObjectStateManager.GetObjectStateEntries(EntityState.Ad ded Or EntityState.Modified) If Not entry.IsRelationship AndAlso (entry.Entity.[GetType]() Is GetType(Product)) Then Dim orderToCheck As Product = TryCast(entry.Entity, Product) If orderToCheck.ListPrice < 0 Then Throw New ApplicationException("List Price must be greater than zero") End If End If Next End Sub
[Visual C#] partial void OnContextCreated() { this.SavingChanges += new System.EventHandler(AWSavingChanges); } private static void AWSavingChanges(object sender, EventArgs e) { foreach (ObjectStateEntry entry in ((ObjectContext)sender).ObjectStateManager.GetObjectStateEntries(Entit yState.Added | EntityState.Modified)) { if (!entry.IsRelationship && (entry.Entity.GetType() == typeof(Product))) { Product orderToCheck = entry.Entity as Product; if (orderToCheck.ListPrice < 0) { throw new ApplicationException("List Price must be greater than zero"); } } } }
7-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
EntityObject.PropertyChanging and EntityObject.PropertyChanged The PropertyChanging and PropertyChanged events execute when any property in the EntityObject object changes. These class-level events enable you to implement more generic business logic than the property-level OnChanging and OnChanged methods. If you implement both the methods and events, the order of code execution is: 1.
OnChanging method
2.
PropertyChanging event
3.
PropertyChanged event
4.
OnChanged method
[Visual Basic] Public Sub New() AddHandler Me.PropertyChanging, AddressOf Product_PropertyChanging AddHandler Me.PropertyChanged, AddressOf Product_PropertyChanged End Sub Private Sub Product_PropertyChanging(ByVal sender As Object, ByVal e As PropertyChangingEventArgs) Dim changingProperty As String = e.PropertyName If e.PropertyName = "ListPrice" Then ' Business logic End If End Sub Private Sub Product_PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Dim changedProperty As String = e.PropertyName If e.PropertyName = "ListPrice" Then ' Business logic End If End Sub
[Visual C#] public Product() { this.PropertyChanging += new PropertyChangingEventHandler(Product_PropertyChanging); this.PropertyChanged += new PropertyChangedEventHandler(Product_PropertyChanged); }
Customizing Entities and Building Custom Entity Classes
7-13
private void Product_PropertyChanging(object sender, PropertyChangingEventArgs e) { string changingProperty = e.PropertyName; if (e.PropertyName == "ListPrice") { // Business logic } } private void Product_PropertyChanged(object sender, PropertyChangedEventArgs e) { string changedProperty = e.PropertyName; if (e.PropertyName == "ListPrice") { // Business logic } }
RelatedEnd.AssociationChanged You can use the RelatedEnd.AssociationChanged event to respond to a change that was made to a related end of an association. It is specific to a particular navigation property of an entity and there is no mechanism for handling all association changes in an entity in one code block, as the following code example shows. [Visual Basic] Public Sub New() AddHandler Me.ProductSubcategoryReference.AssociationChanged, AddressOf Product_SubCategory_AssociationChanged End Sub Private Sub Product_SubCategory_AssociationChanged(ByVal sender As Object, ByVal e As CollectionChangeEventArgs) If e.Action = CollectionChangeAction.Remove Then ' Business logic End If End Sub
7-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] public Product() { this.ProductSubcategoryReference.AssociationChanged += new CollectionChangeEventHandler(Product_SubCategory_AssociationChanged); } private void Product_SubCategory_AssociationChanged(object sender, CollectionChangeEventArgs e) { if (e.Action == CollectionChangeAction.Remove) { // Business logic } }
Question: What is the difference between the PropertyChanging and PropertyChanged events compared to the OnChanging and OnChanged methods?
Customizing Entities and Building Custom Entity Classes
7-15
Creating Extension Methods
Key Points You can use extension methods instead of standard methods to enable Microsoft IntelliSense® support for your custom methods. In the ADO.NET Entity Framework, there is a wide range of scenarios where extension methods can enhance your applications.
Example The following code example shows an extension method named IsDirty that you can use to check whether data in an entity has changed. It follows the requirements for an extension method, so you can call it from client code by simply referencing the compiled dynamic-link library (DLL) that contains the code and adding a using statement for the namespace. [Visual Basic] Public Class MyExtensions Private Sub New() End Sub <System.Runtime.CompilerServices.Extension> _
7-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Public Shared Function IsDirty(ByVal context As ObjectContext, ByVal entity As EntityObject) As Boolean Return (entity.EntityState = EntityState.Added) OrElse (entity.EntityState = EntityState.Deleted) OrElse (entity.EntityState = EntityState.Modified) End Function End Class
[Visual C#] public static class MyExtensions { public static bool IsDirty(this ObjectContext context, EntityObject entity) { return (entity.EntityState == EntityState.Added) || (entity.EntityState == EntityState.Deleted) || (entity.EntityState == EntityState.Modified); } }
Question: You have created an extension method, compiled the code, added a reference to the DLL in your client application, and added a using statement for the namespace, but still your application does not recognize the method. What could be the problem?
Additional Reading For more information about extension methods, see the Extension Methods (C# Programming Guide) page at http://go.microsoft.com/fwlink/?LinkID=194057.
Customizing Entities and Building Custom Entity Classes
7-17
Lesson 2
Using Templates to Customize Entities
You can use templates to customize entities and their behavior. The Entity Framework supports using Text Template Transformation Toolkit (T4) templates to configure entity classes to inherit from a predefined interface and thus implement the methods that it contains. By using these templates, you can customize the entity code generation to make the generated classes implement a required interface. In this lesson, you will learn how to create and code the template, implement the interface in your entity code, and call the implementation from client code.
Objectives After you have completed this lesson, you will be able to: •
Add a code generation template to a model.
•
Describe how code generation templates work.
•
Define a custom interface in a template.
7-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
Implement a template interface in a model.
•
Use a template implementation.
Customizing Entities and Building Custom Entity Classes
7-19
Adding Code Generation Items to a Model
Key Points In an EDM project, you can create a T4 template by adding an ADO.NET EntityObject Generator item to the project. When you add the item to your project, the code behind the model designer is replaced with the code that the template generates. In the code-behind file in Visual Studio, you will simply see a comment explaining this.
f Add a T4 template to an EDM 1.
Open your model in the designer window.
2.
Right-click the designer surface, and then click Add Code Generation Item.
3.
In the Add New Item dialog box, in the Templates list, click ADO.NET EntityObject Generator.
4.
In the Name box, type a name for the item, and then click Add.
5.
In the Security Warning message box, click OK.
7-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Before you add the template to the EDM, the Code Generation Strategy property of the model is set to Default, which means that the default object-layer code generation is switched on. When you add the template to the EDM, the property changes to None and the default code generation is replaced with template code generation. Whenever you save the template or model, the code in the template runs and the entity class file is regenerated. You can change this default behavior to not run the template when you save the model by setting the Transform Related Text property of the model to False. For background information about T4 templates, see the Code Generation and Text Templates page at http://go.microsoft.com/fwlink/?LinkId=196095. Question: You have added a template to your model, but then decided not to use it. When you delete the template, you find errors in your code. What could be the problem and how can you resolve it?
Customizing Entities and Building Custom Entity Classes
7-21
Code Generation Templates
Key Points Code generation templates are written in T4 syntax. In Visual Studio, you can use the text editor to view and edit the content of the template.
Example The following code example shows an extract from a code generation template for a model based on a subset of tables from the AdventureWorks database. [Visual ... Imports Imports Imports Imports Imports Imports Imports
Basic] System System.Data.Objects System.Data.Objects.DataClasses System.Data.EntityClient System.ComponentModel System.Xml.Serialization System.Runtime.Serialization
")>
7-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
<# '''''''' '''''''' Write Relationship Attributes '''''''' region.Begin(GetResourceString("Template_RegionRelationships")); For Each association As associationType in GetSourceSchemaTypes(Of AssociationType)() #> ", "<#=association.Name#>", "<#=EndName(association, 0)#>", <#=EndMultiplicity(association, 0)#>, GetType(<#=EscapeEndTypeName(association, 0, code)#>), "<#=EndName(association, 1)#>", <#=EndMultiplicity(association, 1)#>, GetType(<#=EscapeEndTypeName(association, 1, code)#>)<#=code.StringBefore(", ", If(association.IsForeignKey, "True", Nothing))#>)> <# Next region.End() If(Not String.IsNullOrEmpty(namespaceName)) Then #> ...
[Visual C#] ... using System; using System.Data.Objects; using System.Data.Objects.DataClasses; using System.Data.EntityClient; using System.ComponentModel; using System.Xml.Serialization; using System.Runtime.Serialization; [assembly: EdmSchemaAttribute()] <# //////// //////// Write Relationship Attributes //////// region.Begin(GetResourceString("Template_RegionRelationships")); bool first = true; foreach (AssociationType association in GetSourceSchemaTypes()) { if (first) { WriteLine(string.Empty);
Customizing Entities and Building Custom Entity Classes
7-23
first = false; } #> [assembly: EdmRelationshipAttribute("<#=association.NamespaceName#>", "<#=association.Name#>", "<#=EndName(association, 0)#>", <#=EndMultiplicity(association, 0)#>, typeof(<#=EscapeEndTypeName(association, 0, code)#>), "<#=EndName(association, 1)#>", <#=EndMultiplicity(association, 1)#>, typeof(<#=EscapeEndTypeName(association, 1, code)#>)<#=code.StringBefore(", ", association.IsForeignKey ? "true" : null)#>)] <# } region.End(); if (!String.IsNullOrEmpty(namespaceName)) { #> ...
The first section, which contains the using statements, is text that is directly written to the generated code file. The code that is surrounded by <#...#> is executed to generate the entity code when you run the template. You can edit both the text and executable sections of the template to customize the entity code that is generated when you run the template. Question: You want to add a reference to a namespace into your template. Should you surround the using statement with <#...#>?
7-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Defining Custom Interfaces in a Template
Key Points You can modify any part of the template by adding either text to be written directly to the entity code or code to be executed when the template runs.
Example To add validation code to all of the entities in your model, you can modify the template to base the entity classes on a predefined interface and add the signature for the interface methods to the class. The <#=Accessibility.ForType(entity)#> statement defines the start of the entity class definition section of the template file. The code in this section runs for each entity in the model, resulting in one class for each entity in the generated code file. The following code example shows the rest of this line of code, which declares the classes.
Customizing Entities and Building Custom Entity Classes
7-25
[Visual Basic] <#=code.SpaceAfter(code.MustInheritOption(entity))#>Partial Class <#=code.Escape(entity)#> Inherits <#=BaseTypeName(entity, code)#> ... End Class
[Visual C#] <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#> : <#=BaseTypeName(entity, code)#> { ... }
This code retrieves the Abstract property setting to correctly annotate the class and then creates a partial class that uses the entity name. Any base types for that entity are then added to the declaration. To declare that all of your entities should derive from another interface, you add the interface name to the end of the base class list in the declaration, as the following code example shows. [Visual Basic] <#=code.SpaceAfter(code.MustInheritOption(entity))#>Partial Class <#=code.Escape(entity)#> Inherits <#=BaseTypeName(entity, code)#> Implements IValidate . . . End Class
[Visual C#] <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#> : <#=BaseTypeName(entity, code)#>, IValidate { . . . }
Then, within the body of the class, you can add the implementation of the IValidate interface, as the following code example shows.
7-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] <#=code.SpaceAfter(code.MustInheritOption(entity))#>Partial Class <#=code.Escape(entity)#> Inherits <#=BaseTypeName(entity, code)#> Implements IValidate Partial Private Sub OnValidate() Sub Validate() Implements IValidate.Validate OnValidate() End Sub End Class
[Visual C#] <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#> : <#=BaseTypeName(entity, code)#>, IValidate { partial void OnValidate(); void IValidate.Validate() { OnValidate(); } . . . }
Now when the template runs to regenerate the classes, they will derive from the IValidate interface, as the following code example shows. [Visual Basic] <EdmEntityTypeAttribute(NamespaceName := "AdventureWorksModel", Name := "Product")> _ <Serializable()> _ _ Public Partial Class Product Inherits EntityObject Implements IValidate Private Partial Sub OnValidate() End Sub Private Sub Validate() Implements IValidate.Validate OnValidate() End Sub End Class
Customizing Entities and Building Custom Entity Classes
[Visual C#] [EdmEntityTypeAttribute(NamespaceName="AdventureWorksModel", Name="Product")] [Serializable()] [DataContractAttribute(IsReference=true)] public partial class Product : EntityObject, IValidate { partial void OnValidate(); void IValidate.Validate() { OnValidate(); } . . . }
Question: Why should you declare the method as partial?
7-27
7-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Implementing Template Interfaces in a Model
Key Points Although the stubs for the template interface are created in the generated classes, if you write your code in them, it will be overwritten when the class is regenerated. Therefore, you should add a partial class for the entity class in a new file and implement your interfaces in that partial class. You must ensure that the namespace of both the generated entity class and your partial class are the same.
f Implement the template interface 1.
Add a new class file to the project.
2.
Verify that the namespace is the same value as the entity class and modify the class definition to be a partial class of the generated entity class.
3.
Add the method signature for the interface method, ensuring that you define it as partial.
4.
Add your business logic to the method.
Customizing Entities and Building Custom Entity Classes
7-29
The following code example shows how to implement the sample IValidate interface in the Product class. [Visual Basic] namespace ProductApplication { partial class Product { partial void OnValidate() { if (ListPrice < 0) { throw new Exception("List Price must be greater than zero"); } } } }
[Visual C#] namespace ProductApplication { partial class Product { partial void OnValidate() { if (ListPrice < 0) { throw new Exception("List Price must be greater than zero"); } } } }
Question: You have implemented your interface in a new partial class, but the code shows compile errors. What could be the problem?
7-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Using the Template Implementation
Key Points You call the template implementation by calling the method name as you would any other code in your project. You use the template implementation from wherever you need to execute the business logic that it contains. This is dependent on the structure of your application and the content of the business logic code. For simplicity, in the following code example, you will see how to call the ListPrice validation code directly from the user interface in response to a user request to save changes. In the lab, you will see how to call the business logic from a data access layer.
Example [Visual Basic] Private Sub saveListPrice_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Try Dim changedProduct As Product products = productContext.Products.ToList() changedProduct = products(productList.SelectedIndex)
Customizing Entities and Building Custom Entity Classes
7-31
DirectCast(changedProduct, IValidate).Validate() productContext.SaveChanges() Catch ex As Exception MessageBox.Show(ex.Message) productContext.Refresh(RefreshMode.StoreWins, products) End Try End Sub
[Visual C#] private void saveListPrice_Click(object sender, RoutedEventArgs e) { try { Product changedProduct; products = productContext.Products.ToList(); changedProduct = products[productList.SelectedIndex]; ((IValidate)changedProduct).Validate(); productContext.SaveChanges(); } catch (Exception ex) { MessageBox.Show(ex.Message); productContext.Refresh(RefreshMode.StoreWins, products); } }
In this code example, the validation code is surrounded by a try statement to ensure that, if validation fails, an exception is thrown that is caught in the client and handled gracefully. Question: You are using your template implementation from your client code. When validation fails, the user is informed, but the data displayed in the application still shows the invalid values until the application is closed. What is likely to be missing from your code?
7-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Using Templates to Customize Entities
Key Points •
Define an interface in a template.
•
Code the implementation in your application.
•
Use that implementation from a client application.
Demonstration Steps 1.
Start Visual Studio.
2.
Open the project named ProductApplication in the E:\Demofiles\Mod07\TemplateDemo\ProductApplication folder.
3.
In the ProductApplication project, open ProductModel.tt, and then review the IValidate code at lines 316-323.
4.
In the ProductApplication project, open ProductExtension.cs, and then review the OnValidate method.
Customizing Entities and Building Custom Entity Classes
5.
In the ProductUI project, open MainWindow.xaml.cs, and then review the SaveListPrice_Click method.
6.
Run the application.
7.
Close Visual Studio.
7-33
Question: Why is the IValidate definition in the template not enclosed in <#...#> tags?
7-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 3
Creating and Using Custom Entity Classes
You will often have existing business logic that you want to reuse in your new Entity Framework application. You can modify these classes by using inheritance and attributes to become entity classes and provide you with full entity functionality from a business class. This lesson describes how to define the entity, how to use entity attributes, and how to then use the custom entity class from your client applications.
Objectives After you have completed this lesson, you will be able to: •
Define a custom entity.
•
Use entity attributes.
•
Develop entity properties.
•
Use a custom entity class.
Customizing Entities and Building Custom Entity Classes
7-35
Defining a Custom Entity Class
Key Points You can create custom entity classes from existing business classes by inheriting from EntityObject and adding entity attributes to the class and its members. To implement this functionality, you must add references and using statements for the relevant namespaces to the class. You can then declare the class to inherit from EntityObject and add the EdmEntityType attribute to the class definition, identifying the entity namespace and name as the following code example shows. [Visual Imports Imports Imports
Basic] System.Data System.Data.Objects.DataClasses System.Data.Metadata.Edm
<EdmEntityType(NamespaceName := "AdventureWorksModel", Name := "Product")> _ Public Class myBusinessClass Inherits EntityObject
7-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] using System.Data; using System.Data.Objects.DataClasses; using System.Data.Metadata.Edm; [EdmEntityType (NamespaceName = "AdventureWorksModel", Name = "Product")] public class myBusinessClass : EntityObject
By creating custom entity classes in this way, they will contain all of the functionality required to integrate with Object Services. When you are developing custom entity classes, you can remove the corresponding classes from the generated code, but keep the remainder of the generated classes, which you do not need to customize. Alternatively, you can customize all of the model, in which case, you must supply all of the classes including the EntityContainer class that returns the EntitySet class. Question: Why might you want to create a custom entity class?
Customizing Entities and Building Custom Entity Classes
7-37
Using Entity Attributes
Key Points The System.Data.Objects.DataClasses namespace contains a set of EDM attribute classes that you can use to indicate that classes and members represent entity members. These include: •
EdmEntityType. This attribute identifies that the class represents an entity type. It takes two parameters: NamespaceName, which denotes the namespace for the entity, and Name, which defines the name of the entity.
•
EdmScalarProperty. This attribute points a property in your class to a scalar property of the entity in the model. It takes two parameters: EntityKey, which defines whether it is the key property for the entity, and IsNullable, which defines whether the property can be set to null.
•
EdmComplexProperty. This attribute points a property in your class to a complex property of the entity in the model.
•
EdmComplexType. This attribute denotes that the class represents a complex type. It takes two parameters: NamespaceName, which denotes the namespace for the type, and Name, which defines the name of the type.
7-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
EdmRelationship. This attribute defines the relationship between two entity types based on an association in the model. It takes nine parameters: •
RelationshipNamespaceName defines the namespace.
•
RelationshipName defines the name of the relationship.
•
IsForeignKey indicates whether the relationship is based on the foreign-key value.
•
Role1Multiplicity and Role2Multiplicity define the multiplicity at each end of the relationship.
•
Role1Name and Role2Name define the names of the roles at the ends of the relationship.
•
Role1Type and Role2Type define the types at the ends of the relationship.
You only define this attribute in one of the two related classes, so you must track where they are defined to avoid declaring it in more than one class. •
EdmRelationshipNavigationProperty. This attribute identifies a navigation property between entities. It takes three parameters: •
RelationshipNamespaceName defines the namespace.
•
RelationshipName defines the name of the relationship in the model.
•
TargetRoleName defines the role name of the end of the relationship.
Example The following code example shows how to use the EdmScalarProperty attribute to define a scalar property of an entity.
[Visual Basic] [EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)] public Int32 ProductID { get { // Code to retrieve the property value }
Customizing Entities and Building Custom Entity Classes
7-39
set { // Code to set the property value } }
[Visual C#] [EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)] public Int32 ProductID { get { // Code to retrieve the property value } set { // Code to set the property value } }
Question: How do you specify that a property is the key property of an attribute?
7-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Customizing an EDM for Custom Classes
Key Points You will often find that the entity types and properties in your model do not match the class and property names in your custom data classes. To resolve this issue, you need to update the XML mapping files.
f Update the conceptual schema definition language (CSDL) 1.
Rename the EntityType and EntitySet elements to match the class names in your custom data classes.
2.
Remove any EntityType and EntitySet elements that are not used in your custom data classes.
3.
Rename the Property elements in each type to match the property names in your custom data classes.
4.
Remove any Property elements that are not used in your custom data classes.
Customizing Entities and Building Custom Entity Classes
7-41
f Update the mapping specification language (MSL) 1.
Rename the EntitySetMapping element and the TypeName attribute to match the names of your custom data classes.
2.
Remove any EntitySetMapping elements that are not used in your custom data classes.
3.
Rename the ScalarProperty elements in each type to match the property names in your custom data classes.
4.
Remove any ScalarProperty elements that are not used in your custom data classes.
5.
Rename the EndProperty elements in the AssociationSetMapping element to match the names of your custom data classes.
6.
Rename the ScalarProperty elements in the AssociationSetMapping element to match the names of the properties in your custom data classes.
7.
Remove any AssociationSetMapping elements for associations that are not used in your custom data classes.
f Update the store schema definition language (SSDL) 1.
Remove any EntityType elements that are not used in your custom data classes.
2.
Remove any ScalarProperty elements that are not used in your custom data classes.
Question: How can you view the XML behind a model?
7-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Developing Entity Properties
Key Points The Entity Framework enables you to track changes to entities. Therefore, when your code modifies a property, you must also notify the change tracker that the property has changed. There are two methods of the EntityObject class that you can call to notify the change tracker: •
EntityObject.ReportPropertyChanging. This method notifies the change tracker that a property change is pending. Call this method directly before setting the property of an entity.
•
EntityObject.ReportPropertyChanged. This method notifies the change tracker that a property has changed. Call this method directly after setting the property of an entity.
To make a change to the property, you call the StructuralObject.SetValidValue method. This ensures that the change is notified to all relevant objects and correctly updated in the entity and the user interface. When you use this method to change string properties, you must pass it two parameters: the value and a Boolean value that indicates whether the property allows a null string.
Customizing Entities and Building Custom Entity Classes
7-43
Examples The following code example shows how to retrieve and set an entity property. [Visual Basic] <EdmScalarProperty(EntityKeyProperty := True, IsNullable := False)> _ Public Property ProductID() As Int32 Get ' Code to retrieve the property value Return _ProductID End Get Set(ByVal value As Int32) ' Code to set the property value ReportPropertyChanging("ProductID") _ProductID = StructuralObject.SetValidValue(value) ReportPropertyChanged("ProductID") End Set End Property
[Visual C#] [EdmScalarProperty(EntityKeyProperty = true, IsNullable = false)] public Int32 ProductID { get { // Code to retrieve the property value return _ProductID; } set { // Code to set the property value ReportPropertyChanging("ProductID"); _ProductID = StructuralObject.SetValidValue(value); ReportPropertyChanged("ProductID"); } }
You will notice that, in this code example, there is no reference to the OnChanging method. Unless you want to intercept this method with a partial method, you do not have to call it. You can validate data in the set method. If you encounter invalid data, you can throw an exception, as shown in the following code example, which can be caught at the client and handled appropriately.
7-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] <EdmScalarProperty(EntityKeyProperty = False, IsNullable = True)> _ Public Property ListPrice As Decimal Get ' Code to retrieve the property value Return _ListPrice End Get Set(ByVal value As Decimal) ' Code to set the property value If (Value < 0) Then Throw New Exception("List Price must be greater than zero") End If ReportPropertyChanging("ListPrice") _ListPrice = StructuralObject.SetValidValue(Value) ReportPropertyChanged("ListPrice") End Set End Property
[Visual C#] [EdmScalarProperty(EntityKeyProperty = false, IsNullable = true)] public Decimal ListPrice { get { // Code to retrieve the property value return _ListPrice; } set { // Code to set the property value if (value < 0) { throw new Exception("List Price must be greater than zero"); } ReportPropertyChanging("ListPrice"); _ListPrice = StructuralObject.SetValidValue(value); ReportPropertyChanged("ListPrice"); } } }
Question: What does the call to the ReportPropertyChanged method do?
Customizing Entities and Building Custom Entity Classes
7-45
Using a Custom Entity Class
Key Points You can data bind a custom entity class to a Windows® Presentation Foundation (WPF) user interface in the same way that you bind a generated class. However, if your entity property throws an exception during its validation process, you must ensure that your user interface elements can catch the exception and handle it gracefully. The simplest way to do this is to add the <ExceptionValidationRule> tag to the list of binding rules in the element for the bound control as shown in the following code example. <ExceptionValidationRule />
This validation rule causes the control to be flagged with an error if the validation fails.
7-46
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Question: What would happen in the compiled application if you did not include the <ExceptionValidationRule> tag in the binding rules?
Customizing Entities and Building Custom Entity Classes
7-47
Lab: Customizing Entities and Building Custom Entity Classes
Objectives After completing this lab, you will be able to: •
Use code templates to add custom functionality to entity classes.
•
Create custom entity classes.
Introduction In this lab, you will use a T4 template to add custom functionality to the existing Contact class. You will then replace this class with an existing business class that already contains all of the business functionality that you require. You will modify this class to inherit from EntityObject and work with Object Services.
7-48
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-07 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
Customizing Entities and Building Custom Entity Classes
7-49
Lab Scenario
You are concerned that some of the older existing applications that use your data access code might be saving invalid contact data. You decide to implement a defense-in-depth strategy and add some custom validation logic to the Contact entity class in your data access layer to support these older applications. You then discover that the business logic and validation rules for contacts is much more complicated than you originally anticipated. Instead of implementing the validation logic for this class yourself, you decide to expose the existing business class, which already validates data as an entity class.
Exercise 1: Using a Template to Add Custom Functionality to Entity Classes Scenario In this exercise, you will define an interface called IValidate that exposes a single method called Validate. You will add a code generation item to the EDM and customize the T4 template to include the IValidate interface in all generated entities and invoke a partial method called OnValidate. You will then add a partial class file for the Contact entity in the EDM and implement the OnValidate
7-50
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
method for the entity. The OnValidate method will validate the data in the entity and throw an exception if any of this data is invalid when adding or updating a contact. The main tasks for this exercise are as follows: 1.
Prepare the AdventureWorks database for the lab.
2.
Open the starter project for this exercise.
3.
Create the IValidate class.
4.
Create the template.
5.
Customize the template.
6.
View the generated code.
7.
Implement the OnValidate method.
8.
Modify the DAL code to validate the data.
9.
Add unit tests to verify your code.
10. Build and test the application.
f Task 1: Prepare the AdventureWorks database for the lab 1.
Log on to the 10265A-GEN-DEV-07 virtual machine as Student with the password Pa$$w0rd.
2.
Run AWReset.bat in the E:\Labfiles folder.
f Task 2: Open the starter project for this exercise 1.
Open Visual Studio 2010.
2.
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab07\VB\Ex1\Starter or E:\Labfiles\Lab07\CS\Ex1\Starter folder.
f Task 3: Create the IValidate class 1.
Add a new interface named IValidate to the DAL project.
2.
Modify the interface definition to make it public, and add a void method named Validate that takes no arguments.
Customizing Entities and Building Custom Entity Classes
7-51
f Task 4: Create the template 1.
Add an ADO.NET EntityObject Generator item named AWModel.tt to the DAL project.
2.
Open AdventureWorksEDM.Designer.cs or AdventureWorksEDM.Designer.vb and review the comment that it contains.
f Task 5: Customize the template 1.
In AWModel.tt, locate the line of code that begins <#=Accessibility.ForType(entity)#>.
2.
Edit the line of code to make every entity object implement the IValidate interface.
3.
Within the body of this section, declare a partial void method named OnValidate that takes no arguments.
4.
Immediately after the statement that declares the OnValidate method, implement the IValidate.Validate method. Inside this method, call the OnValidate method that you have just declared.
5.
If you are using Microsoft Visual Basic®, you must also manually adjust the namespace to match the rest of the project.
f Task 6: View the generated code •
Build the solution, and then review the generated code in each class in AWModel.cs or AWModel.vb.
f Task 7: Implement the OnValidate method 1.
Add a new class named ContactExtension to the DAL project.
2.
Modify the class definition to define the class as a public partial class for the Contact class.
3.
Add a void method named OnValidate to the class. If you are using Microsoft Visual C#®, this method should be declared partial.
4.
Add code to the OnValidate method to throw a DALValidationException exception in each of the following scenarios: •
If the CurrentPoints property is set to a negative value.
7-52
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
If the EmailAddress property does not contain an @ symbol.
•
If the EmailAddress property does not contain a period.
f Task 8: Modify the DAL code to validate the data 1.
In the DataAccessLayer class, modify the UpdateContact method to call the Validate method before saving changes to the object.
2.
In the DataAccessLayer class, modify the AddContact method to call the Validate method before saving changes to the object.
3.
In the CustomerRewards project, in the MainWindow.xaml.cs or MainWindow.xaml.vb class, in the contacts_MouseDoubleClick event, refresh the contacts list.
f Task 9: Add unit tests to verify your code 1.
Review the task list.
2.
Open the DataAccessLayerTest code file by double-clicking the comment TODO: Ex1 - Add a test for AddContact when there is a CurrentPoints validation exception item in the task list. This task is located in the AddContactCurrentPointsValidationTest method.
3.
Add an ExpectedException attribute to the method for the DALValidationException type.
4.
Delete the comment in the AddContactCurrentPointsValidationTest method.
5.
Add a unit test to create a Contact object, set the CurrentPoints property of the Contact object to an invalid value, and then add the contact to the database. Be sure to release all resources at the end of the test.
6.
Locate the AddContactAtSymbolValidationTest method by double-clicking the comment TODO: Ex1 - Add a test for AddContact when there is a missing @ sign in the e-mail address validation exception item in the task list.
7.
Add an ExpectedException attribute to the method for the DALValidationException type.
8.
Delete the comment in the AddContactAtSymbolValidationTest method.
9.
Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to an invalid value with a missing @ symbol, and then add
Customizing Entities and Building Custom Entity Classes
7-53
the contact to the database. Be sure to release all resources at the end of the test. 10. Locate the AddContactPeriodValidationTest method by double-clicking the comment TODO: Ex1 - Add a test for AddContact when there is a missing period in the e-mail address validation exception item in the task list. 11. Add an ExpectedException attribute to the method for the DALValidationException type. 12. Delete the comment in the AddContactPeriodValidationTest method. 13. Add a unit test to create a Contact object, set the EmailAddress property of the Contact object to an invalid value with a missing period, and then add the contact to the database. Be sure to release all resources at the end of the test. 14. Locate the UpdateContactCurrentPointsValidationTest method by doubleclicking the comment TODO: Ex1 - Add a test for UpdateContact when there is a CurrentPoints validation exception item in the task list. 15. Add an ExpectedException attribute to the method for the DALValidationException type. 16. Delete the comment in the UpdateContactCurrentPointsValidationTest method. 17. Add a unit test to create a Contact object, retrieve that contact from the database, set the CurrentPoints property of that Contact object to an invalid value, and then update the contact in the database. Be sure to release all resources at the end of the test. 18. Locate the UpdateContactAtSymbolValidationTest method by doubleclicking the comment TODO: Ex1 - Add a test for UpdateContact when there is a missing @ sign in the e-mail address validation exception item in the task list. 19. Add an ExpectedException attribute to the method for the DALValidationException type. 20. Delete the comment in the UpdateContactAtSymbolValidationTest method. 21. Add a unit test to create a Contact object, retrieve that contact from the database, set the EmailAddress property of the Contact object to an invalid value with a missing @ symbol, and then update the contact in the database. Be sure to release all resources at the end of the test. 22. Locate the UpdateContactPeriodValidationTest method by double-clicking the comment TODO: Ex1 - Add a test for UpdateContact when there is a
7-54
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
missing period in the e-mail address validation exception item in the task list. 23. Add an ExpectedException attribute to the method for the DALValidationException type. 24. Delete the comment in the UpdateContactPeriodValidationTest method. 25. Add a unit test to create a Contact object, retrieve that contact from the database , set the EmailAddress property of the Contact object to an invalid value with a missing period, and then add the contact to the database. Be sure to release all resources at the end of the test. 26. Save the DataAccessLayerTest code file.
f Task 10: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application in Debug mode.
3.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that the application functions as expected.
4.
Close the application.
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed.
7.
Close the solution.
Exercise 2: Creating Custom Entity Classes Scenario In this exercise, you will modify an existing business class that models customers and add functionality that enables it to operate as an entity class. You will inherit from the EntityObject class, and will add scalar and navigational properties that are exposed to the EDM. You will replace the existing Contact entity class in the data access layer with this custom implementation. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
Customizing Entities and Building Custom Entity Classes
2.
Remove the existing Contact class from the DAL project.
3.
Add the existing business class to the DAL project.
4.
Modify the business class to operate as an entity class.
5.
Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class (for Visual Basic only).
6.
Modify the user interface to catch the validation exception.
7.
Build and test the application.
7-55
f Task 1: Open the starter project for this exercise 1.
Open the DAL solution in the E:\Labfiles\Lab07\VB\Ex2\Starter or E:\Labfiles\Lab07\CS\Ex2\Starter folder.
2.
If you are using Visual C#, if a Problem Loading message is displayed, on the Build menu, click Rebuild Solution, and then in the designer pane, click Reload the designer.
f Task 2: Remove the existing Contact class from the DAL project •
Open AdventureWorksEDM.Designer.cs or AdventureWorksEDM.Designer.vb, and then in the Entities region, comment out all of the Contact partial class.
f Task 3: Add the existing business class to the DAL project 1.
Add the businessLogicCustomer.cs or businessLogicCustomer.vb file in the E:\Labfiles\Lab07\CS\Ex2\Starter or E:\Labfiles\Lab07\VB\Ex2\Starter folder to the DAL project.
2.
If you are using Visual Basic, rename the businessLogicCustomer class file and class to Contact.vb and Contact.
3.
If you are using Visual C#, rename the businessLogicCustomer class file and class to Contact.cs and Contact.
f Task 4: Modify the business class to operate as an entity class 1.
In the Contact class, add using statements for the following namespaces:
7-56
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
System.Data
•
System.Data.Objects.DataClasses
•
System.Data.Metadata.Edm
2.
In the Contact class, modify the class definition to inherit from EntityObject.
3.
In the Contact class, use the EdmEntityType attribute to link the class to the Contact entity in the AdventureWorksModel namespace.
4.
Use the EdmScalarProperty attribute to configure the entity properties in the class, as the following table shows. Property name
EntityKeyProperty
IsNullable
ContactID
true
false
NameStyle
false
false
Title
false
true
FirstName
false
false
MiddleName
false
true
LastName
false
false
Suffix
false
true
EmailAddress
false
true
EmailPromotion
false
false
Phone
false
true
PasswordHash
false
false
PasswordSalt
false
false
AdditionalContactInfo
false
true
rowguid
false
false
ModifiedDate
false
false
CurrentPoints
false
false
Customizing Entities and Building Custom Entity Classes
7-57
5.
Modify the Set statements for each property to notify the change tracker when a property change is pending and then completed, and to use the SetValidValue method of the StructuralObject object to change the property value.
6.
Add navigation properties to link the Contact entity to the SalesOrderHeader, StoreContact, and RewardsClaimed entities.
7.
If you are using Visual C#, build the solution and correct any errors.
f Task 5: Alter the AdventureWorksEDM.Designer.vb file to reflect the new Contact class (for Visual Basic only) 1.
Open the AdventureWorksEDM.Designer.vb file
2.
At the top of the file, if it is not already present, add a statement to bring the DAL namespace into scope.
3.
Update code that references AdventureWorksModel.Contact to reference DAL.Contact.
4.
Build the solution and correct any errors.
f Task 6: Modify the user interface to catch the validation exception 1.
In CustomerWindow.xaml or CustomerAddWindow.xaml, add the ExceptionValidationRule rule to the binding validation rules for the CurrentPoints text box to catch the validation exception.
2.
In CustomerWindow.xaml or CustomerAddWindow.xaml, add the binding exception validation rule for the EmailAddress text box to catch the validation exception and change the Style attribute of the text box to display errors.
f Task 7: Build and test the application 1.
Build the solution and correct any errors.
2.
Start the application without debugging.
3.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that the application functions as expected.
4.
Close the application.
7-58
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
5.
Run all of the tests in the solution.
6.
Verify that all of the tests succeed.
7.
Save and close the solution, and then close Visual Studio.
Customizing Entities and Building Custom Entity Classes
7-59
Lab Review
Review Questions 1.
In a T4 template, what does the line of code that defines an entity begin with?
2.
When creating your own custom entity classes, what method should you call to notify the change tracker that a property is about to change?
7-60
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
When you use generated entity classes, how can you write additional code to validate data in the entities?
2.
Where can you write code to execute when a navigation property changes?
3.
How can you write code for existing types without recompiling the existing type or deriving from it?
4.
What happens to the code behind the model designer when you add an ADO.NET EntityObject Generator item to a project?
5.
In a T4 template, what happens to code that is surrounded by <#...#> tags?
6.
In a T4 template, what happens to code that is not surrounded by <#...#> tags?
7.
What attribute do you add to a class definition to identify it as an entity class?
Customizing Entities and Building Custom Entity Classes
7-61
Best Practices Related to Overriding Generated Classes Supplement or modify the following best practices for your own work situations: •
Do not add custom logic to generated entities. Instead, use partial classes to ensure that the code is not overwritten when the classes are regenerated.
•
Use partial methods to populate calculated columns in the user interface. In this way, you can avoid performing a round trip to save the data and retrieve the calculated column.
•
Use extension methods to enable IntelliSense support for custom methods.
Best Practices Related to Using Templates to Customize Entities Supplement or modify the following best practices for your own work situations: •
Declare template methods as partial so that you can implement them outside the generated class.
Best Practices Related to Creating and Using Custom Entity Classes Supplement or modify the following best practices for your own work situations: •
Use the EntityObject.ReportPropertyChanging and EntityObject.ReportPropertyChanged methods in your property set methods to ensure that the change tracker is notified of the changes.
•
Use the StructuralObject.SetValidValue method to change properties to ensure that the change is notified to all relevant objects and updated in the entity and user interface.
•
Use the <ExceptionValidationRule> tag to flag validation errors in a WPF user interface.
Using POCO Classes with the Entity Framework
8-1
Module 8 Using POCO Classes with the Entity Framework Contents: Lesson 1: Requirements for POCO Classes
8-3
Lesson 2: POCO Classes and Lazy Loading
8-12
Lesson 3: POCO Classes and Change Tracking
8-18
Lesson 4: Extending Entity Types
8-23
Lab: Using POCO Classes with the Entity Framework
8-29
8-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
This module introduces the ways in which you can define custom entity classes in your Entity Framework application. By default, Microsoft® Visual Studio® generates a set of entity classes for you from the Entity Data Model (EDM). Instead of these generated classes, you may want to use an existing set of plain-old CLR object (POCO) business classes in your application. You can also extend the generated entity classes to add custom business functionality to your entity objects.
Objectives After completing this module, you will be able to: •
List the requirements that your POCO classes must meet.
•
Create POCO entities that support automatic lazy loading.
•
Create POCO entities that support automatic change tracking.
•
Describe the options for using interfaces and inheritance to create custom entity objects.
Using POCO Classes with the Entity Framework
8-3
Lesson 1
Requirements for POCO Classes
There are specific requirements that POCO entity classes must meet for them to work with the Entity Framework. There are also further requirements if you want your POCO entity classes to support lazy loading or change tracking. This lesson describes these requirements and also describes how to define a custom ObjectContext class that will work with your POCO entity classes.
Objectives After completing this lesson, you will be able to: •
Describe the characteristics of a POCO entity class.
•
Create a custom ObjectContext class.
•
Switch off automatic object layer generation.
8-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
What Are POCO Entity Classes?
Key Points POCO entity classes are standard common language runtime (CLR) classes that the Entity Framework can use in place of the entity classes that are generated from the EDM. POCO classes are useful when you have a pre-existing set of business classes that you want to reuse in your Entity Framework application, or when you want to add custom business functionality to your entity classes. An important feature of these POCO entities is that they have no dependencies on the Entity Framework. For example, Adventure Works may have an existing set of business classes that represent sales entities such as SalesOrders, SalesTerritories, and SalesPersons that it wants to reuse in the Rewards application. To use custom POCO entity classes in place of classes that are generated from the EDM, you must switch off the automatic generation of classes from the EDM. This procedure is shown later in a demonstration. The ObjectContext object can manage POCO entity objects in the same way that it manages standard entity objects: by loading data from the database, modifying data in the cache, and persisting changes back to the database. If a POCO entity
Using POCO Classes with the Entity Framework
8-5
object supports lazy loading or automatic change tracking, the ObjectContext object creates a proxy object to manage the POCO entity object. The base requirements that all POCO entity class must meet are: •
The entity type name in the EDM must be the same as the POCO entity class name.
•
Each property of the entity type must map to a public property in the POCO entity class. The names and types of these matching properties should be the same.
To support either lazy loading or automatic change tracking, your POCO entity class must also meet the following requirements: •
You must declare the class with public access.
•
You must not mark the class as sealed (Microsoft Visual C#®) or NotInheritable (Microsoft Visual Basic®).
•
You must not mark the class as abstract (Visual C#) or MustInherit (Visual Basic).
•
The class must have a public or protected no-argument constructor.
•
The class must not implement the IEntityWithChangeTracker or IEntityWithRelationships interfaces.
•
You must set the ProxyCreationEnabled property of the ObjectContext object to true. This is the default.
To ensure that the ObjectContext object creates a proxy object when you create a new POCO entity object, you must instantiate the entity object by calling the CreateObject method of the ObjectContext object instead of by using the new operator. Question: Does the ObjectContext object create proxy objects for all POCO entity objects?
8-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Defining a Custom ObjectContext Class
Key Points To use your custom POCO entity classes, you must define a custom ObjectContext class that can connect to your EDM. It should also expose properties that return ObjectSet objects that represent the entity sets in your EDM. The following code example shows a custom ObjectContext class that works with custom POCO entity classes. The custom POCO classes are mapped to the SalesOrderHeader, SalesTerritory, and SalesPerson entities in the EDM. [Visual Basic] Public Class AdventureWorksContext Inherits ObjectContext ' ObjectSet variables to hold entity set data. Private _headers As ObjectSet(Of SalesOrderHeader) Private _territories As ObjectSet(Of SalesTerritory) Private _salesPersons As ObjectSet(Of SalesPerson) ' Required public, no-argument constructor. ' Passes connection information to its parent class. Public Sub New()
Using POCO Classes with the Entity Framework
MyBase.new("name=AdventureWorksEntities", "AdventureWorksEntities") ' Explicitly enable lazy loading. Me.ContextOptions.LazyLoadingEnabled = True End Sub ' Accessor for the SalesOrderHeaders entity set. Public ReadOnly Property SalesOrderHeaders As _ ObjectSet(Of SalesOrderHeader) Get If _headers Is Nothing Then _headers = MyBase.CreateObjectSet( Of SalesOrderHeader)("SalesOrderHeaders") End If Return _headers End Get End Property ' Accessor for the SalesTerritories entity set. Public ReadOnly Property SalesTerritories As _ ObjectSet(Of SalesTerritory) Get If _territories Is Nothing Then _territories = MyBase.CreateObjectSet( Of SalesTerritory)("SalesTerritories") End If Return _territories End Get End Property ' Accessor for the SalesPersons entity set. Public ReadOnly Property SalesPersons As _ ObjectSet(Of SalesPerson)
8-7
8-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Get If _salesPersons Is Nothing Then _territories = MyBase.CreateObjectSet( Of SalesPersons)("SalesPersons") End If Return _salesPersons End Get End Property End Class
[Visual C#] public class AdventureWorksContext : ObjectContext { // ObjectSet variables to hold entity set data. private ObjectSet<SalesOrderHeader> _headers; private ObjectSet<SalesTerritory> _territories; private ObjectSet<SalesPerson> _salesPersons; // Required public, no-argument constructor. // Passes connection information to its parent class. public AdventureWorksContext() : base("name=AdventureWorksEntities", "AdventureWorksEntities") { // Explicitly enable lazy loading. this.ContextOptions.LazyLoadingEnabled = true; } // Accessor for the SalesOrderHeaders entity set. public ObjectSet<SalesOrderHeader> SalesOrderHeaders { get { if (_headers == null) { _headers = base.CreateObjectSet<SalesOrderHeader> ("SalesOrderHeaders"); } return _headers; } }
Using POCO Classes with the Entity Framework
8-9
// Accessor for the SalesTerritories entity set. public ObjectSet<SalesTerritory> SalesTerritories { get { if (_territories == null) { _territories = base.CreateObjectSet<SalesTerritory> ("SalesTerritories"); } return _territories; } } // Accessor for the SalesPersons entity set. public ObjectSet<SalesPerson> SalesPersons { get { if (_salesPersons == null) { _territories = base.CreateObjectSet<SalesPerson> ("SalesPersons"); } return _salesPersons; } } }
Question: What other functionality might you add to your custom ObjectContext class?
8-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Switching Off Object Layer Generation
Key Points •
Disable object layer generation from the EDM.
•
Rebuild a solution to remove the generated entity classes.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-08 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration.
3.
Start Visual Studio 2010.
4.
Open the ObjectLayerGeneration solution.
5.
Open the AdventureWorksArchivedEDM.edmx file and set the Code Generation Strategy property to None.
6.
Save the AdventureWorksArchivedEDM.edmx file and build the solution.
Using POCO Classes with the Entity Framework
8-11
Question: Why do you need to disable object layer generation when you are using custom POCO entity classes?
8-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 2
POCO Classes and Lazy Loading
If you are using custom POCO entity classes, you must design the classes to support lazy loading, or explicitly load entity objects in your data access when they are needed. In this lesson, you will learn how to support lazy loading when you are using custom POCO entity classes.
Objectives After completing this lesson, you will be able to: •
Describe the requirements for lazy loading.
•
Explicitly load entity objects.
Using POCO Classes with the Entity Framework
8-13
Requirements for Lazy Loading
Key Points Lazy loading is a feature of the Entity Framework that enables the ObjectContext object to load related entity objects automatically when you access them. For example, one of the properties of a SalesOrderHeader entity object is a SalesPerson object. If you run a query that loads a SalesOrderHeader entity object, the query does not load the related SalesPerson object. When you first access the SalesPerson object through the SalesPerson property of the SalesOrderHeader object, this triggers the ObjectContext object to load the SalesPerson object. For lazy loading to work with your custom POCO entity classes, two things must be true: 1.
The LazyLoadingEnabled property of your ObjectContext object must be set to true.
2.
In addition to the requirements for POCO proxy generation that the previous lesson listed, your custom POCO entity class must meet the following requirements:
8-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
For every navigation property of the entity in the EDM, there must be a navigation property with the same name in the POCO entity class.
•
The get accessor of every navigation property must be declared as public and virtual (Visual C#), or Overridable (Visual Basic).
The following code example shows how the SalesPerson navigation property is defined in the SalesOrderHeader POCO entity class. [Visual Basic] Public Overridable Property SalesPerson As SalesPerson
[Visual C#] public virtual SalesPerson SalesPerson { get; set; }
Question: Which class implements lazy loading for your POCO object?
Using POCO Classes with the Entity Framework
8-15
Explicitly Loading POCO Entities
Key Points If your custom POCO entity class does not support lazy loading, you will need to explicitly load any related POCO entity objects before you use them. Currently, the data access layer in the AdventureWorks application contains code that relies on lazy loading, as the following code example shows. [Visual Basic] Dim orders As ObjectQuery(Of SalesOrderHeader) = entities.SalesOrderHeaders Dim query = From o In orders Where o.OrderID = orderID Select o SalesOrderHeader(order = query.First()) ' The next line relies on lazy loading. Dim salesPerson As SalesPerson = order.SalesPerson
8-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] ObjectQuery<SalesOrderHeader> orders = entities.SalesOrderHeaders; var query = from o in orders where o.OrderID == orderID select o; SalesOrderHeader order = query.First(); // The next line relies on lazy loading. SalesPerson salesPerson = order.SalesPerson;
If the SalesOrderHeader POCO entity class does not support lazy loading, you must modify the code as the following code example shows. [Visual Basic] Dim orders As ObjectQuery(Of SalesOrderHeader) = entities.SalesOrderHeaders Dim query = From o In orders Where o.OrderID = orderID Select o SalesOrderHeader(order = query.First()) ' Explicitly load the SalesPerson entity object. entities.LoadProperty(order, Function(o) o.SalesPerson) Dim salesPerson As SalesPerson = order.SalesPerson
[Visual C#] ObjectQuery<SalesOrderHeader> orders = entities.SalesOrderHeaders; var query = from o in orders where o.OrderID == orderID select o; SalesOrderHeader order = query.First(); // Explicitly load the SalesPerson entity object. entities.LoadProperty(order, o => o.SalesPerson); SalesPerson salesPerson = order.SalesPerson;
Note: As an alternative to lazy loading, you can use the Include method on an ObjectQuery object to load objects as a part of a query.
Using POCO Classes with the Entity Framework
8-17
Question: There is an overloaded version of the LoadProperty method that takes a third parameter of type MergeOption. What behavior does this third parameter control?
Additional Reading For more information about loading related objects, see the Loading Related Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194058.
8-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 3
POCO Classes and Change Tracking
When you are using POCO entity classes, you must either design your entity classes to support change tracking, or explicitly check for changes in your entity objects. In this lesson, you will learn how to manage change tracking when you are using custom POCO entity classes.
Objectives After completing this lesson, you will be able to: •
List the requirements that custom POCO entity classes must meet to support automatic change tracking.
•
Manually detect changes in your POCO entity objects.
Using POCO Classes with the Entity Framework
8-19
Requirements for Change Tracking
Key Points The Entity Framework tracks changes to entity objects by using the ObjectStateManager class. The Entity Framework uses the ObjectStateManager class to track the changes that it must persist to the database when your application calls the SaveChanges method. The change tracking information also enables the Entity Framework to detect concurrency issues when it is saving changes. When your application uses POCO entity classes, there are two ways that the Entity Framework can track changes to entity objects: •
If your entity object has a proxy object, the proxy object can perform the change tracking function.
•
If there is no proxy object, you must perform the change tracking manually by using snapshots.
8-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The Entity Framework can only create the proxy objects that perform the change tracking function if your POCO entity classes meet the following requirements in addition to the base requirements for POCO proxy object creation: •
Each property of the entity class that is mapped to a property of the entity type in the EDM must be declared by using public and virtual (Visual C#) or Overridable (Visual Basic) get and set accessors.
•
Every navigation property of the entity class that represents the "many" end of a relationship must return a type that implements the ICollection interface, where T is the type of the object at the other end of the relationship.
•
If you want the Entity Framework to create a proxy object along with your entity object, use the CreateObject method on the ObjectContext object when you create a new entity object, instead of the new operator.
If your POCO entity classes meet these requirements, you do not need to make any changes to the code in your data access layer to support their use. Question: What are the requirements that relate to the constructor of a POCO entity class if the class must support proxy creation?
Additional Reading For more information about identifying proxy objects, see the How to: Identify that a POCO Entity is a Proxy (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194059.
Using POCO Classes with the Entity Framework
8-21
Tracking Changes in POCO Entity Objects by Using Snapshots
Key Points If the Entity Framework cannot create a proxy object for your POCO entity object, the Entity Framework cannot perform automatic change tracking. Otherwise, you must detect any changes in your entity object by calling the DetectChanges method of the ObjectContext object. If there is no proxy object, when an entity object is attached to the ObjectContext object, the Entity Framework takes a snapshot of all of the property values of the entity object. When you call the DetectChanges method, the Entity Framework updates the information in the ObjectStateManager object by comparing the current values of the properties of the entity object with the values in the snapshot. The no-argument version of the SaveChanges method calls the DetectChanges method before it processes any data modifications, so in many cases you do not need to include a call to DetectChanges in your code. However, if you call any of the following methods in your code and you are not using proxy objects, you should call the DetectChanges method first to ensure that the information that the ObjectStateManager object maintains is up to date:
8-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
•
Any query or object load operation that you execute with a MergeOption value of PreserveChanges.
•
The AddObject, Attach, AttachTo, DeleteObject, Detach, GetObjectByKey, TryGetObjectByKey, ApplyCurrentValues, ApplyOriginalValues, Refresh, or ChangeObjectState methods of the ObjectContext class.
•
The GetObjectStateEntry, TryGetObjectStateEntry, GetObjectStateEntries, or ChangeRelationshipState of the ObjectStateManager class.
•
All of the methods of the ObjectStateEntry class.
Question: If your POCO entity object has no proxy object, the Entity Framework updates the ObjectStateManager object when you call the DetectChanges method. If your POCO entity object has a proxy object, when does the Entity Framework update the ObjectStateManager object?
Using POCO Classes with the Entity Framework
8-23
Lesson 4
Extending Entity Types
An alternative to creating POCO entity classes is to extend the classes that the Entity Framework generates to incorporate additional functionality or business logic. This is an appropriate option if you do not need to use existing domain classes with the Entity Framework. You can also implement the Entity Framework interfaces directly in your custom classes, but you are strongly recommended to use POCO entity classes instead.
Objectives After completing this lesson, you will be able to: •
Extend the entity classes that the Entity Framework generates.
•
Understand the roles of the Entity Framework interfaces.
8-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Extending Generated Types
Key Points The Entity Framework generates entity classes automatically from the EDM. These entity classes provide full support for querying and modifying entity data. However, these classes do not include any custom functionality or business logic. The Entity Framework generates entity classes as partial classes. This means that you can extend these classes by adding code to a separate source file that will not be modified when the Entity Framework refreshes the generated source files. The following code example shows how to add a ValidatePassword method to the Contact class by using a partial class. [Visual Basic] Public Partial Class Contact Public Function ValidatePassword(ByVal password As String) As Boolean Dim passwordHash As String = Hashing.CreatePasswordHash(password, Me._PasswordSalt) If passwordHash = Me._PasswordHash Then Return True
Using POCO Classes with the Entity Framework
8-25
Else Return False End If End Function End Class
[Visual C#] public partial class Contact { public bool ValidatePassword(string password) { string passwordHash = Hashing.CreatePasswordHash(password, this._PasswordSalt); if (passwordHash == this._PasswordHash) return true; else return false; } }
Question: When would you choose to extend a generated entity type instead of defining a POCO entity class?
8-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Interfaces and Inheritance for Custom Entity Types
Key Points You can also create custom entity objects by inheriting from the EntityObject class or by implementing the IEntityWithChangeTracker and IEntityWithRelationships interfaces. The Entity Framework supports these approaches for reasons of backward compatibility, and you are recommended to use POCO entity classes for any new development.
Inheriting from the EntityObject Class If your custom entity class inherits from the EntityObject class, you must include a call to the ReportPropertyChanging method before you set a property value, and include a call to ReportPropertyChanged after you set a property value.
Implementing the Custom Data Class Interfaces If you cannot inherit from the EntityObject class, you can create a custom entity class by implementing the following three interfaces: •
IEntityWithChangeTracker. This interface is required for change tracking and it enables the Entity Framework to track changes to the entity object. The
Using POCO Classes with the Entity Framework
8-27
IEntityWithChangeTracker interface defines the SetChangeTracker method, which specifies the IEntityChangeTracker object that is used to report changes to the Entity Framework. •
IEntityWithKey. This optional interface exposes the entity key to the Entity Framework for improved performance. The IEntityWithKey interface defines the EntityKey property and the Entity Framework uses the EntityKey property to manage objects in the ObjectContext object.
•
IEntityWithRelationships. This interface is required for entities with associations because it enables the Entity Framework to manage relationships between entity objects. The IEntityWithRelationships interface defines the RelationshipManager property.
Using the EDM Attributes In addition to inheriting from the EntityObject class or implementing the custom data class interfaces, you must use EDM attributes in your custom data class to map class properties to entity properties in the EDM. The following table describes these attributes.
Attribute name
Description
EdmSchemaAttribute
Apply this attribute to an assembly that contains entity types. It is only required to be applied once, but it may be applied multiple times.
EdmRelationshipAttribute
Apply this attribute at the assembly level with one instance for each association. There can be multiple attributes of this type in an assembly. The details for the role of an entity type in a particular association must match the association that is defined in the conceptual schema.
EdmEntityTypeAttribute
This attribute links a class to an entity type in the EDM. Apply this attribute to classes that represent entity types.
8-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Attribute name EdmScalarPropertyAttribute
Description This attribute links a scalar property on a data class to a property of an entity type or complex type that is defined in the conceptual model. Apply this attribute to properties that return scalar types.
Question: Why is it necessary to call the ReportPropertyChanging and ReportPropertyChanged methods?
Additional Reading For more information about mapping custom objects to entities, see the How to: Map Custom Objects to Entities (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194060. For more information about inheriting from the EntityObject class, see the How to: Inherit from the EntityObject and ComplexObject Base Classes (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194061. For more information about implementing the custom data class interfaces, see the How to: Implement Custom Data Class Interfaces (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194062.
Using POCO Classes with the Entity Framework
8-29
Lab: Using POCO Classes with the Entity Framework
Objectives After completing this lab, you will be able to: •
Create POCO entity classes that support lazy loading and automatic change tracking.
•
Create and use POCO entity classes that do not support lazy loading and automatic change tracking.
Introduction In this lab, you will disable object layer generation in the EDM. You will then complete the implementation of the custom POCO entity classes for Adventure Works. You will modify the data access layer where necessary to work with the POCO entity classes.
8-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-08 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
Using POCO Classes with the Entity Framework
8-31
Lab Scenario
Adventure Works implements an EDM to support its customer reward program. You have been asked to modify the data access layer to use custom POCO entity classes that implement some of the business logic that Adventure Works requires. You have also been asked to update the data access layer in two stages. In the first stage, you will replace the generated entity classes with simple POCO entity classes that support lazy loading and automatic change tracking. You will then enhance the POCO entity classes to include additional business logic and adapt the data access layer to work with these enhanced entity classes.
Exercise 1: Using POCO Classes Scenario You have been asked to replace the existing generated entity classes with custom POCO entity classes. At this stage, the POCO classes will support lazy loading and automatic change tracking, but you must create a new ObjectContext class to load the new custom entity classes. You must also ensure that change tracking is enabled by instantiating entity objects by using the CreateObject method instead of the new operator.
8-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
The main tasks for this exercise are as follows: 1.
Prepare the AdventureWorks database for the lab.
2.
Open the starter project for this exercise.
3.
Disable object layer generation for your EDM.
4.
Implement a custom ObjectContext class.
5.
Complete the RewardsClaimed class in the AdventureWorks project.
6.
Modify the data access layer to work with the new POCO classes.
7.
Build and test the application.
f Task 1: Prepare the AdventureWorks database for the lab 1.
Log on to the 10265A-GEN-DEV-08 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Labfiles folder, run AWReset.bat.
f Task 2: Open the starter project for this exercise 1.
Open Visual Studio 2010.
2.
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex1\Starter or E:\Labfiles\Lab08\VB\Ex1\Starter folder.
f Task 3: Disable object layer generation for your EDM 1.
Open the AdventureWorks EDM model in the ADO.NET Entity Data Model Designer (Entity Designer).
2.
In the AdventureWorks EDM model, set the Code Generation Strategy property to None.
3.
Save the AdventureWorks EDM model.
f Task 4: Implement a custom ObjectContext class 1.
Review the task list.
Using POCO Classes with the Entity Framework
8-33
2.
Open the AdventureWorksContext file by double-clicking the TODO: Ex1 Add a constructor task in the task list. This task is located in the AdventureWorksContext class.
3.
Immediately after the comment, add a no-argument constructor that enables automatic lazy loading. The constructor should invoke the base class constructor passing the strings "name=AdventureWorksEntities" and "AdventureWorksEntities" as parameters.
4.
Locate the next comment in the AdventureWorksContext file by doubleclicking the TODO: Ex1 - Define the Contacts entity set task in the task list. This task is located in the AdventureWorksContext class.
5.
Immediately after the comment, add a read-only property called Contacts based on the ObjectSet generic type. Specify Contact as the type parameter for the ObjectSet type. Create the ObjectSet object if it does not exist by calling the CreateObjectSet method in the base class, and then save the ObjectSet object in a private field.
6.
Locate the next comment in the AdventureWorksContext file by doubleclicking the TODO: Ex1 - Define the RewardsClaimed entity set task in the task list. This task is located in the AdventureWorksContext class.
7.
Immediately after the comment, add a read-only property called RewardsClaimed based on the ObjectSet generic type. Specify RewardsClaimed as the type parameter for the ObjectSet type. Create the ObjectSet object if it does not exist by calling the CreateObjectSet method in the base class, and then save the ObjectSet object in a private field.
8.
Locate the next comment in the AdventureWorksContext file by doubleclicking the TODO: Ex1 - Define the Rewards entity set task in the task list. This task is located in the AdventureWorksContext class.
9.
Immediately after the comment, add a read-only property called Rewards based on the ObjectSet generic type. Specify Rewards as the type parameter for the ObjectSet type. Create the ObjectSet object if it does not exist by calling the CreateObjectSet method in the base class, and then save the ObjectSet object in a private field.
10. Locate the next comment in the AdventureWorksContext file by doubleclicking the TODO: Ex1 - Define the AddToContacts method task in the task list. This task is located in the AdventureWorksContext class. 11. Immediately after the comment, add a void method called AddToContacts that takes a contact entity as a parameter. The method should call the
8-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
AddObject method in the base class to add the contact entity to the contacts entity set. 12. Locate the next comment in the AdventureWorksContext file by doubleclicking the TODO: Ex1 - Define the AddToRewards method task in the task list. This task is located in the AdventureWorksContext class. 13. Immediately after the comment, add a void method called AddToRewards that takes a reward entity as a parameter. The method should call the AddObject method in the base class to add the reward entity to the rewards entity set. 14. Save the AdventureWorksContext file.
f Task 5: Complete the RewardsClaimed class in the AdventureWorks project 1.
Review the task list.
2.
Open the RewardsClaimed file by double-clicking the TODO: Ex1 - Add virtual public accessors for every RewardsClaimed entity property task in the task list. This task is located in the RewardsClaimed class.
3.
Immediately after the comment, add a virtual public property for every entity property of the RewardsClaimed entity object in the EDM.
4.
Locate the next comment in the RewardsClaimed file by double-clicking the TODO: Ex1 - Add virtual public accessors for every RewardsClaimed navigation property task in the task list. This task is located in the RewardsClaimed class.
5.
Immediately after the comment, add a virtual public property for every navigation property of the RewardsClaimed entity object in the EDM.
6.
Save the RewardsClaimed file.
f Task 6: Modify the data access layer to work with the new POCO classes 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the TODO: Ex1 - Add a using clause for the AdventureWorks namespace task in the task list. This task is located near the top of the DataAccessLayer file.
Using POCO Classes with the Entity Framework
8-35
3.
Immediately after the comment, add a using statement for the AdventureWorks namespace.
4.
Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Use the custom ObjectContext class task in the task list. This task is located in the SetContext method.
5.
Immediately after the comment, modify the next line of code to use the AdventureWorksContext class instead of the AdventureWorksEntities class.
6.
Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a new contact by using the CreateObject method task in the task list. This task is located in the AddContact method.
7.
Immediately after the comment, add code that creates a new contact entity by calling the CreateObject method. Then, use the Copy method of the contact object to copy the values from the parameter passed to the AddContact method.
8.
Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a new reward by using the CreateObject method task in the task list. This task is located in the AddReward method.
9.
Immediately after the comment, add code that creates a new reward entity by calling the CreateObject method. Then, use the Copy method of the reward object to copy the values from the parameter passed to the AddReward method. You must check the type of reward passed as a parameter to the AddReward method (AdventureWorksReward, SupermarketReward, or AirMilesReward), and then create the correct reward type.
10. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex1 - Create a new claim by using the CreateObject method task in the task list. This task is located in the CreateRewardsClaim method. 11. Immediately after the comment, add code that creates a new RewardsClaimed entity by calling the CreateObject method. Then, use the Copy method of the RewardsClaimed object to copy the values from the parameter passed to the CreateRewardsClaim method. 12. Save the DataAccessLayer file.
f Task 7: Build and test the application 1.
Build the solution and correct any errors.
2.
Run all of the tests in the solution.
8-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
3.
Verify that all of the tests succeed.
4.
Start the application in Debug mode.
5.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that you can add, search for, and delete customer data and that you can add and delete claim data.
6.
Close the application.
7.
Close the solution.
8.
Reset the AdventureWorks Database. In the E:\Labfiles folder, run AWReset.bat.
Exercise 2: Extending Your POCO Classes Scenario You have been asked to enhance the custom POCO entity classes to include some custom business logic. These changes mean that your custom entity classes no longer support lazy loading or automatic change tracking, so you must update your data access layer. The main tasks for this exercise are as follows: 1.
Open the starter project for this exercise.
2.
Add business operations to the Contact class.
3.
Add a business operation to the RewardsClaimed class.
4.
Modify the data access layer to work with your new POCO entities.
5.
Build and test the application.
f Task 1: Open the starter project for this exercise •
Open the existing solution, DAL.sln, in the E:\Labfiles\Lab08\CS\Ex2\Starter or E:\Labfiles\Lab08\VB\Ex2\Starter folder.
f Task 2: Add business operations to the Contact class 1.
Review the task list.
Using POCO Classes with the Entity Framework
8-37
2.
Open the Contact file by double-clicking the TODO: Ex2 - Create the Salt and Hash task in the task list. This task is located in the Password property.
3.
Immediately after the comment, generate a value for the PasswordSalt property by calling the static CreateSalt method of the Hashing class with a parameter value of 5. Then, generate a value for the PasswordHash property by calling the CreatePasswordHash method of the Hashing class, passing the password and PasswordSalt value as parameters.
4.
Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the AddRewardClaim method task in the task list. This task is located in the AddRewardClaim method.
5.
Immediately after the comment, add code to decrement the CurrentPoints property by the value of the PointsUsed property of the claim object, set the ModifiedDate property to the current date and time, and then add the claim object to the _rewardsClaimed list.
6.
Locate the next comment in the Contact file by double-clicking the TODO: Ex2 - Implement the RemoveRewardClaim method task in the task list. This task is located in the RemoveRewardClaim method.
7.
Immediately after the comment, add code to increment the CurrentPoints property by the value of the PointsUsed property of the claim object, set the ModifiedDate property to the current date and time, and then add the claim object to the _rewardsClaimed list.
8.
Save the Contact file.
f Task 3: Add a business operation to the RewardsClaimed class 1.
Review the task list.
2.
Open the RewardsClaimed file by double-clicking the TODO: Ex2 Implement the ModifyClaim method task in the task list. This task is located in the ModifyClaim method.
3.
Immediately after the comment, add code to perform the following tasks: a.
Increment the CurrentPoints property of the Contact property by the value of the PointsUsed property of the current claim object.
b.
Decrement the CurrentPoints property of the Contact property by the value of the pointsUsed parameter.
c.
Assign the rewardID parameter to the RewardID property.
8-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
d. Assign the pointsUsed parameter to the PointsUsed property. 4.
Save the RewardsClaimed file.
f Task 4: Modify the data access layer to work with your new POCO entities 1.
Review the task list.
2.
Open the DataAccessLayer file by double-clicking the TODO: Ex2 - Delete the call to the EncryptPassword method task in the task list. This task is located in the AddContact method.
3.
The Contact class now handles password encryption. Delete the line of code after the comment that calls the EncryptPassword method.
4.
Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Remove the EncryptPassword method task in the task list. This task is located in the DataAccessLayer class.
5.
The password encryption functionality is now in the AdventureWorks project. Delete the whole of the EncryptPassword method from the DataAccessLayer class.
6.
Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Make sure that all the claims are loaded task in the task list. This task is located in the DeleteContact method.
7.
The new POCO classes do not support automatic lazy loading. Immediately after the comment, add code to load all of the claims that are related to the contact by using the LoadProperty method.
8.
Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Load the contact and then call the AddRewardClaim method task in the task list. This task is located in the CreateRewardsClaim method.
9.
Immediately after the comment, add code to perform the following tasks: a.
Create an EntityKey object for the contact associated with the claim.
b.
Use the TryGetObjectByKey method to load the contact entity.
c.
Use the AddRewardClaim method to add the claim to the contact.
10. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Remove the claim before you refresh the contact task in the task list. This task is located in the CreateRewardsClaim method.
Using POCO Classes with the Entity Framework
8-39
11. Immediately after the comment, add code to remove the claim from the contact by calling the RemoveRewardClaim method. 12. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the AddRewardClaim method task in the task list. This task is located in the CreateRewardsClaim method. 13. Immediately after the comment, add code to add the claim to the contact by calling the AddRewardClaim method on the Contact property of the claim variable. 14. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the ModifyClaim business method task in the task list. This task is located in the UpdateRewardsClaim method. 15. Immediately after the comment, add code to call the ModifyClaim method. 16. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Give the original points back to the Contact task in the task list. This task is located in the UpdateRewardsClaim method. 17. Immediately after the comment, add code to call the ModifyClaim method, passing the originalPoints variable as the second parameter. 18. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the ModifyClaim method to give the points to the contact task in the task list. This task is located in the UpdateRewardsClaim method. 19. Immediately after the comment, add code to call the ModifyClaim method, passing the RewardID property of the rewardClaim object as the first parameter and the PointsUsed property of the rewardClaim object as the second parameter. 20. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the RemoveRewardClaim method task in the task list. This task is located in the DeleteRewardsClaim method. 21. Immediately after the comment, add code to call the RemoveRewardClaim method of the relatedContact object, passing the rewardClaimToDelete object as a parameter. 22. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Add the claim back while you refresh the contact task in the task list. This task is located in the DeleteRewardsClaim method. 23. Immediately after the comment, add code to call the AddRewardClaim method of the relatedContact object, passing the rewardClaimToDelete object as a parameter.
8-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
24. Locate the next comment in the DataAccessLayer file by double-clicking the TODO: Ex2 - Use the RemoveRewardClaim method again task in the task list. This task is located in the DeleteRewardsClaim method. 25. Immediately after the comment, add code to call the RemoveRewardClaim method of the relatedContact object, passing the rewardClaimToDelete object as a parameter. 26. Save the DataAccessLayer file.
f Task 5: Build and test the application 1.
Build the solution and correct any errors.
2.
Run all of the tests in the solution.
3.
Verify that all of the tests succeed.
4.
Start the application in Debug mode.
5.
In the AdventureWorks Rewards window, click All Customers to load data from the entity model into the data grid. Verify that you can add, modify, and delete contact data. Verify that you can add, modify, and delete Adventure Works reward data.
6.
Close the application.
7.
Close the solution, and then close Visual Studio.
Using POCO Classes with the Entity Framework
8-41
Lab Review
Review Questions 1.
The POCO entity classes are defined in the AdventureWorks project. Does this project need to reference any of the Entity Framework assemblies?
2.
In Exercise 1, does the Entity Framework create proxy objects for the POCO entity objects?
3.
In Exercise 1, why is it necessary to modify the code in the data access layer to call the CreateObject method?
4.
In Exercise 2, why is it not necessary to call the CreateObject method?
5.
In Exercise 2, why is it not necessary to call the DetectChanges method in all of the data modification methods?
8-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
What must the Entity Framework create to enable lazy loading and automatic change tracking for POCO entity objects?
2.
Must your POCO entity classes implement any specific interfaces?
3.
How can you load related entity objects if your POCO entity classes do not support lazy loading?
4.
Can you have a mixture of generated entity classes and POCO entity classes in your application?
Best Practices Related to Using Custom Entity Classes in Your Entity Framework Application Supplement or modify the following best practices for your own work situations: •
Use custom POCO entity classes to enable the reuse of existing business classes.
Using POCO Classes with the Entity Framework
8-43
•
Use custom POCO entity classes instead of implementing the custom data class interfaces or extending the EntityObject class.
•
Add additional logic to the generated entity classes by placing code in partial classes.
Building an N-Tier Solution by Using the Entity Framework
9-1
Module 9 Building an N-Tier Solution by Using the Entity Framework Contents: Lesson 1: Designing an N-Tier Solution
9-4
Lesson 2: Defining Operations and Implementing Data Transport Structures
9-12
Lesson 3: Protecting Data and Operations
9-32
Lab: Building an N-Tier Solution by Using the Entity Framework
9-40
9-2
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Overview
This module introduces the architectural issues that you may encounter when you build an n-tier solution and explains how you can solve these problems by using the Entity Framework. N-tier architectures are commonly used to build enterpriseclass business applications because they enable you to build applications that are scalable, secure, maintainable, and interoperable. An n-tier solution can use other technologies, such as ASP.NET and Windows® Communication Foundation (WCF), in addition to the Entity Framework. This module does not attempt to cover these additional technologies in detail; instead; it focuses on the role of the Entity Framework and how the Entity Framework interacts with these technologies. This module will discuss how you can build an n-tier application that supports query operations. A later module will explain how you can support data modifications in an n-tier application.
Objectives After completing this module, you will be able to: •
Discuss the issues and strategies that are relevant to building n-tier applications.
Building an N-Tier Solution by Using the Entity Framework
9-3
•
Understand the key components that you must create to implement an n-tier application.
•
Protect operations and data in an n-tier application.
9-4
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 1
Designing an N-Tier Solution
You must consider several issues when you design an n-tier application. This lesson introduces some of these issues and discusses some of the alternative strategies that you can choose between when you decide how to transport data between tiers.
Objectives After completing this lesson, you will be able to: •
List the key issues that you must be aware of when you design n-tier applications.
•
Describe the common patterns for transporting data between tiers.
•
List the options for hosting your tiers.
Building an N-Tier Solution by Using the Entity Framework
9-5
Architectural Issues
Key Points This module focuses on the issues concerned with building the data access layer tier in an n-tier application; however, many of these issues will be relevant to the design of other tiers. There is no one right way to design a data access layer; the requirements will be specific to the application, but you should treat the guidance that this module provides as an indication of best practice. Designers frequently adopt n-tier architectures because they help to deliver requirements such as scalability, maintainability, security, and interoperability to the solution. The following questions are examples of the type of questions that you must answer when you design an n-tier application.
What Operations Will the Client Applications Require? The data access layer tier exists to provide client applications with access to the data that they require. The client application may display the data in the user interface (UI), for example, to allow a customer to browse the products that Adventure Works sells. Alternatively, the client application may control a part of a business process, such as allocating a reward to a customer. The design goals are
9-6
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
to identify the operations that the various client applications must be able to invoke and to ensure that you can extend the set of available operations in the future. To help to ensure that the data access layer exposes the correct set of operations, you should identify operations based on their business role. For example, for Adventure Works, a set of business-centric operations that enable users to place, retrieve, or cancel orders is better than a set of data-centric operations that enable users to retrieve or move data.
How Should You Partition the Operations in the Data Access Layer? Not all clients will require access to all of the operations exposed by the data access layer. In some scenarios, it may not be desirable for certain clients to have access to certain operations; for example, security considerations may prohibit access. In this case, you must identify the operations that particular interfaces in the data access layer expose.
What Data Will the Client Applications Require? Client applications will not need to access all of the data in the database. You should identify the data that client applications will require and ensure that the data access layer only exposes this data. You should also consider whether all client applications require access to all data; if not, examine ways to partition the data. It is a waste of effort, and a potential security risk, to expose more data than is necessary.
How Can You Minimize Network Overhead? N-tier applications, by definition, must move data over a network. There is a tradeoff between making a large number of requests that transfer small amounts of data and making just a few requests that transfer large amounts of data. An application that uses a 'chatty' interface will suffer from performance problems that result from the overhead of a large number of small requests and the network latency on all of those requests. Fetching large quantities of data can swamp the network and often moves data around unnecessarily. Again, you should create business-centric operations to help you to ensure that you move the right amount of data across the network.
How Should You Transport Data over the Network? The format of the data that you move over the network is important. The objects that you use to move the data must be serializable, but you must also consider how much functionality you should build into these objects and what dependencies these objects may introduce into the client application. The functionality of these objects will be discussed later in this module, but an important goal should be to avoid any tight coupling of the client application to the underlying data store.
Building an N-Tier Solution by Using the Entity Framework
9-7
How Should You Host the Data Access Layer? This question will be discussed in more detail later in this module.
How Can You Maximize the Scalability of the Data Access Layer? This is a key goal for n-tier architectures. The most significant design decision that you can make in the data access layer is to make this tier stateless. Each operation that you define in the data access layer should be self-contained without any dependencies on any other public operations. This approach maximizes the opportunities to recycle and reuse objects in the data access layer tier. If any of your operations require state, you should consider very carefully where that state data should be maintained. Question: One of the goals of n-tier architectures is to promote interoperability. Which of the issues that are discussed in this topic will have the most impact on interoperability with the data access layer?
Additional Reading For more information about application architecture, see the Microsoft Application Architecture Guide, 2nd Edition page at http://go.microsoft.com/fwlink/?LinkID=194063.
9-8
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Strategies for N-Tier Applications
Key Points In an n-tier application, the data access layer must deliver data to the client application over the network, and if the client modifies the data, the client must send the data back to the data access layer. To move the data over the network, the format that you use to represent the data must be serializable; however, you must make additional choices about the format, and these choices will have a significant impact on the design of the client and the data access layer. This topic outlines and evaluates four alternative strategies. You can adopt any of these strategies for use in an application that incorporates the Entity Framework.
Change Set Objects A change set object can hold any data from the database, so you can populate it with the data that you require for the particular business-centric operation that you implement. A change set will often implement its own change-tracking mechanism, so the client can make changes to the contents of the change set before it returns the change set to the data access layer. The DataSet class in ADO.NET is a good example of a type that you can use as a change set. You can fill a DataSet object with DataTable objects that contain the data that an operation requires, and the
Building an N-Tier Solution by Using the Entity Framework
9-9
DataSet and DataTable classes have a comprehensive application programming interface (API) that enables you to work with the data held in the DataTable objects. Although the DataSet class is very easy to work with, DataSet objects are large, they are not interoperable, and they often lead to tight coupling between the client and the data access layer. You must also be careful to validate the contents of a DataSet object in the data access layer when it is received from a client.
Data Transfer Objects Change sets are large and complex objects, but data transfer objects (DTOs) are simple and compact objects. They contain just the data that you need to transfer between the tiers, but both the client and the data access layer have their own object representations. This pattern results in a very loose coupling between the client and the data access layer, but it requires the most development effort.
Simple Entities Simple entities (SEs) enable you to use the same object representation for both the data access layer and the client. SEs should be as simple as possible (unlike change sets) and represent simple entity data. A client application can make changes to entity properties before it sends those entities back to the data access layer. The major advantage of this strategy is simplicity. This does make it more difficult to implement scenarios that are more complex especially if multiple entities are involved. For example, in the AdventureWorks EDM, if you modify a reward entity, this may affect rewards claimed entities and contact entities. Using SEs in this type of scenario may lead the application to become too 'chatty.'
Self-Tracking Entities Self-tracking entities (STEs) are SEs with tracking capabilities. They solve some of the problems of SEs by building more intelligence into the entity objects. STEs keep track of their own changes and changes to related objects. This helps to reduce the number of interactions between the client and the data access layer, because the client can send a single message that includes multiple related changes to the data access layer. Ideally, you implement STEs as plain-old CLR objects (POCOs), so that you minimize the dependencies in the client. STEs are specific to an entity type; therefore, they are more compact than change sets and can include custom validation rules. Question: What type of projects would benefit most from the use of DTOs?
9-10
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Hosting Your Service
Key Points A data access tier in an n-tier solution typically exposes its functionality to clients over a network. You have a number of choices about how you can host your data access layer.
Windows Communication Foundation This section discusses the four choices that WCF offers for hosting your data access layer. An advantage of WCF is that the code that you write to implement a service will not have to change significantly if you decide to change your hosting option later.
Internet Information Services Internet Information Services (IIS) is a highly scalable and highly available platform. Hosting a WCF service in IIS means that you can immediately use all of the functionality that IIS offers, including security, management tools, and monitoring. You do not need to write any hosting code, but you must use the HTTP transport for your application.
Building an N-Tier Solution by Using the Entity Framework
9-11
Windows Process Activation Service Windows Process Activation Service (WAS) is a process-activation mechanism that was introduced with Windows Server® 2008. It offers the same features as IIS for your WCF service, but removes the dependency on the HTTP transport. You can use TCP, Message Queuing, or named pipes as your transport mechanism.
Managed Windows Services You can host your application as a Windows service; the operating system controls the lifetime of this service. You must write some hosting and installation code.
Self-Hosting You can write your own managed hosting application to host your WCF service. This is the most flexible option, but can require that you write an extensive volume of code to provide robust hosting facilities.
Windows Azure Windows Azure™ is a cloud-based operating system that you can use to host services. You can use a cloud-based version of IIS to host your services. Alternatively, you can use the cloud-based AppFabric Service Bus to provide a hosted, secure, and widely accessible platform for service publishing.
Microsoft .NET Remoting Microsoft® .NET Remoting was the precursor to WCF. It is now regarded as a legacy technology that should not be used for new developments. You can use a wide variety of deployment models and choose from a wide variety of message formats. Question: What are the advantages of hosting your service as a WCF service in IIS?
Additional Reading For more information about Windows Azure, see the About Windows Azure page at http://go.microsoft.com/fwlink/?LinkID=194064. For more information about the AppFabric Service Bus, see the AppFabric Service Bus page at http://go.microsoft.com/fwlink/?LinkID=194065. For more information about Microsoft SQL Azure, see the SQL Azure page at http://go.microsoft.com/fwlink/?LinkID=194066. For more information about .NET Remoting, see the .NET Remoting page at http://go.microsoft.com/fwlink/?LinkID=194067.
9-12
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 2
Defining Operations and Implementing Data Transport Structures
You can use the ADO.NET Self-Tracking Entity Generator Text Template Transformation Toolkit (T4) template to easily generate classes implementing the STE pattern. The generated classes are simple POCO classes that can track changes to their data. The template is optimized for use with WCF, but there are choices that you must make about the configuration of the service, such as where to place your STE classes. After you have created and configured your service, there are choices to make about how to optimize the service and how to manage relationships. This lesson explains how to use the ADO.NET Self-Tracking Entity Generator T4 template. It also explains how to optimize your service when you use WCF hosting and how to manage more complex query operations.
Building an N-Tier Solution by Using the Entity Framework
Objectives After completing this lesson, you will be able to: •
Generate an STE by using the ADO.NET Self-Tracking Entity Generator T4 template.
•
Identify the differences among implementations of DTOs, SEs, and STEs.
•
Choose the appropriate configuration options for your WCF service.
•
Manage parent/child relationships between entity objects.
•
Describe the structure of an n-tier solution.
9-13
9-14
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Using the Self-Tracking Entity T4 Template
Key Points •
Use the ADO.NET Self-Tracking Entity Generator T4 template to generate the code for an STE.
•
Examine the code that the T4 template generates for the ObjectContext class and the entity classes.
Demonstration Steps 1.
Log on to the 10265A-GEN-DEV-09 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration.
3.
Start Microsoft Visual Studio® 2010.
4.
Open the STEDemo solution.
5.
Add an ADO.NET Self-Tracking Entity Generator.
Building an N-Tier Solution by Using the Entity Framework
6.
Review the code generated for the ObjectContext class.
7.
Review a generated entity class.
8.
Save the project and close Visual Studio.
Question: What functionality is implemented in the AdventureWorksModel.Context.Extensions.cs file?
9-15
9-16
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Data Transport Structures
Key Points In the previous demonstration, you saw the code that was generated by using a T4 template to create an STE. A WCF service can serialize this STE to a client, and the client can read the properties of the STE object. In a later module, you will see how the client can modify the data in the STE and then send the modified STE back to the data access layer, where you can update the database. If the client does not modify the data, you can use a simpler data structure to return data to the client.
Simple Entity Objects In an earlier module, you saw how to create POCOs that you can use as entity objects. If they are serializable, you can use these POCOs to transport data to the client application. The following code example shows a class that you can use to represent contact entities.
Building an N-Tier Solution by Using the Entity Framework
[Visual Basic] <Serializable()> Public Class Contact Public Public Public Public Public Public Public Public Public Public Public Public Public Public Public Public
Property Property Property Property Property Property Property Property Property Property Property Property Property Property Property Property
ContactID As String NameStyle As Boolean Title As String FirstName As String MiddleName As String LastName As String Suffix As String EmailAddress As String EmailPromotion As Integer Phone As String PasswordHash As String PasswordSalt As String AdditionalContactInfo As String rowguid As Guid ModifiedDate As DateTime CurrentPoints As Integer
Private _rewardsClaimed As New List(Of RewardsClaimed) Public Property RewardsClaimed As List(Of RewardsClaimed) Get Return _rewardsClaimed End Get Set(ByVal value As List(Of RewardsClaimed)) _rewardsClaimed = value End Set End Property End Class
[Visual C#] [Serializable] public class Contact { public int ContactID { get; set; } public bool NameStyle { get; set; } public string Title { get; set; } public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } public string Suffix { get; set; } public string EmailAddress { get; set; } public int EmailPromotion { get; set; } public string Phone { get; set; } public string PasswordHash { get; set; }
9-17
9-18
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
public public public public public
string PasswordSalt { get; set; } string AdditionalContactInfo { get; set; } Guid rowguid { get; set; } DateTime ModifiedDate { get; set; } int CurrentPoints { get; set; }
private List _rewardsClaimed = new List(); public List RewardsClaimed { get { return _rewardsClaimed; } set { _rewardsClaimed = value; } } }
Note: The additional considerations that you must take into account if your client modifies an SE are discussed in a later module.
Data Transfer Objects An SE object contains all of the entity properties in its definition. This means that you transport all of an entity's data over the network, and both the data access layer and the client share the same object representation. You design your DTOs to transport just the data that each operation requires; this design decouples the data access layer and the client. There is some additional complexity, because you must create code that translates between the DTO representation and each tier's entity representation. The following code example shows a DTO that you can use when the client displays e-mail contact information. [Visual Basic] <Serializable()> Public Class ContactEmailData Public Property ContactID As Integer Public Property EmailAddress As String Public Property EmailPromotion As Integer End Class
Building an N-Tier Solution by Using the Entity Framework
[Visual C#] [Serializable] public class ContactEmailData { public int ContactID { get; set; } public string EmailAddress { get; set; } public int EmailPromotion { get; set; } }
Question: What advantages does a DTO have over an STE or SE in an n-tier application?
9-19
9-20
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Creating a WCF Service
Key Points WCF offers the most flexible set of options to expose the operations in your data access layer to the client tier. In fact, the code generated by the ADO.NET SelfTracking Entity Generator T4 template is optimized for use with WCF. This topic describes the key elements that you must create if you use WCF to host a data access layer built by using STEs. A WCF service implements one or more operations. To define a WCF operation, you start with a contract. This contract is an interface that defines the operations that your service exposes, including any fault information that you may need to return to the client. The following code example shows an interface that defines two operations that you want to expose from the Adventure Works data access layer. It also shows how to specify the format of the fault data that you can use to notify the client of an error.
Building an N-Tier Solution by Using the Entity Framework
[Visual Basic] Public Class ServiceFault Public ExceptionType As String Public ExceptionMessage As String End Class <ServiceContract(Name:="RewardsWebService", Namespace:="http://microsoft.com")> Public Interface IRewardsService Function GetRewardDetails(ByVal rewardID As Integer) As Reward Function GetRewardsClaimedDetails(ByVal claimID As Integer) _ As RewardsClaimed End Interface
[Visual C#] [DataContract] public class ServiceFault { [DataMember] public string ExceptionType; [DataMember] public string ExceptionMessage; } [ServiceContract (Name="RewardsWebService", Namespace="http://microsoft.com")] public interface IRewardsService { [FaultContract(typeof(ServiceFault))]
9-21
9-22
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[OperationContract] Reward GetRewardDetails(int rewardID); [FaultContract(typeof(ServiceFault))] [OperationContract] RewardsClaimed GetRewardsClaimedDetails(int claimID); }
Next, you must implement the interface. The following code example shows how you can implement the operations that are defined in the IRewardsService interface. The Reward and RewardsClaimed types that the operations return are the entity types that the T4 templates generate. The InstanceContextMode property of the ServiceBehavior attribute is set to the PerCall value. This value causes every call from the client to create an instance of the service, which results in a stateless service. The ConcurrencyMode property is set to the value Multiple to enable multiple threads to access an instance. The example also shows how to return a fault to the client; an alternative strategy is to log full details of any errors on the server and minimize the amount of information about the service that you return to the client. [Visual Basic] <ServiceBehavior(Name:="RewardsWebService", Namespace:="http://microsoft.com", InstanceContextMode:=InstanceContextMode.PerCall, ConcurrencyMode:=ConcurrencyMode.Multiple)> Public Class RewardsServiceImpl Implements IRewardsService
Public Function GetRewardDetails(ByVal rewardID As Integer) As reward Implements IRewardsService.GetRewardDetails Dim reward As reward = Nothing Try Using context As New AdventureWorksEntities() Dim matchingRewards As IEnumerable(Of reward) = From r In context.Rewards Where r.RewardID = rewardID Select r If matchingRewards.Count() > 0 Then reward = matchingRewards.First() End If Return reward
Building an N-Tier Solution by Using the Entity Framework
9-23
End Using Catch ex As Exception Throw New FaultException(Of ServiceFault)( New ServiceFault() With { .ExceptionType = "GetRewardsClaimedDetails", .ExceptionMessage = "Failed to retrieve reward" }) End Try End Function Public Function GetRewardsClaimedDetails(ByVal claimID As Integer) As RewardsClaimed Implements IRewardsService.GetRewardsClaimedDetails Dim claim As RewardsClaimed = Nothing Try Using context = New AdventureWorksEntities() { Dim matchingClaims As IEnumerable(Of RewardsClaimed) = From c In context.RewardsClaimed Where c.ClaimID = claimID Select c If matchingClaims.Count() > 0 Then claim = matchingClaims.First() End If Return claim End Using Catch ex As Exception Throw New FaultException(Of ServiceFault)( New ServiceFault() With { .ExceptionType = "GetRewardsClaimedDetails", .ExceptionMessage = "Failed to retrieve claim" }) End Try End Function End Class
9-24
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] [ServiceBehavior(Name = "RewardsWebService", Namespace = "http://microsoft.com", InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)] public class RewardsServiceImpl : IRewardsService { public Reward GetRewardDetails(int rewardID) { Reward reward = null; try { using (AdventureWorksEntities context = new AdventureWorksEntities()) { IEnumerable matchingRewards = from r in context.Rewards where r.RewardID == rewardID select r; if (matchingRewards.Count() > 0) { reward = matchingRewards.First(); } return reward; } } catch (Exception ex) { throw new FaultException<ServiceFault>( new ServiceFault() {ExceptionType="GetRewardsClaimedDetails", ExceptionMessage="Failed to retrieve reward"}); } } public RewardsClaimed GetRewardsClaimedDetails(int claimID) { RewardsClaimed claim = null; try { using (AdventureWorksEntities context = new AdventureWorksEntities()) { IEnumerable matchingClaims =
Building an N-Tier Solution by Using the Entity Framework
9-25
from c in context.RewardsClaimed where c.ClaimID == claimID select c; if (matchingClaims.Count() > 0) { claim = matchingClaims.First(); } return claim; } } catch (Exception ex) { throw new FaultException<ServiceFault>( new ServiceFault() {ExceptionType="GetRewardsClaimedDetails", ExceptionMessage="Failed to retrieve claim"}); } } }
The service interface and implementation do not include any details about the service infrastructure. You specify this information in the configuration file for the service. The following code example shows a part of the web.config file for the service. The configuration data specifies the transport to use, the security options to enforce, and the end point for the service. <system.serviceModel> <messageLogging logEntireMessage="true" logMalformedMessages="false" logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="false" /> <wsHttpBinding> <security mode="None" /> <services> <service name="RewardsService.RewardsServiceImpl"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="RewardsServiceWSHttpBindingConfig" contract="RewardsService.IRewardsService" />
9-26
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
<serviceBehaviors> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" /> <serviceDebug includeExceptionDetailInFaults="false" />
One of the architectural goals of building n-tier applications is to decouple the client from the data access layer. To achieve this goal, you should place your STE classes in a separate assembly that will have no dependencies on the Entity Framework. You can reference this assembly from the client project to avoid introducing any dependencies on the Entity Framework. To achieve this, you must select the Reuse types in specified referenced assemblies option when you add the service reference to the client project. This prevents the proxy from generating its own types that are based on the service metadata. Question: What are the consequences of setting the ConcurrencyMode property of the service to the Single value?
Additional Reading For more information about serializing entity objects, see the Serializing Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194068. For more information about WCF, see the Fundamental Windows Communication Foundation Concepts page at http://go.microsoft.com/fwlink/?LinkID=194069. For more information about WCF sessions and concurrency, see the Sessions, Instancing, and Concurrency page at http://go.microsoft.com/fwlink/?LinkID=194070.
Building an N-Tier Solution by Using the Entity Framework
9-27
Working with Parent/Child Relationships
Key Points When WCF moves objects over the network, it does so by serializing these objects. Entity objects in the Entity Framework usually have navigation properties that enable you to navigate to related objects; for example, a rewards claim entity can have a navigation property that enables you to navigate to the related contact entity. You have seen in previous modules that the Entity Framework can use lazy loading to materialize related entities automatically when you reference them. You must be careful that the WCF serializer does not return more data than expected to the client. If you use binary serialization and WCF data contract serialization, WCF serializes and transports related objects to the client, so in this scenario, you should disable automatic lazy loading. If you use XML serialization, WCF does not serialize related objects. The ObjectContext class that the T4 templates generate does not enable lazy loading. The following code example shows the code in the Initialize method that all of the constructors in the ObjectContext class call.
9-28
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual Basic] Private Sub Initialize() ' Creating proxies requires the use of the ProxyDataContractResolver and ' may allow lazy loading which can expand the loaded graph during serialization. ContextOptions.ProxyCreationEnabled = False AddHandler ObjectMaterialized, AddressOf HandleObjectMaterialized End Sub
[Visual C#] private void Initialize() { // Creating proxies requires the use of the // ProxyDataContractResolver and // may allow lazy loading, which can expand the loaded graph // during serialization. ContextOptions.ProxyCreationEnabled = false; ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized); }
If you must return related entities to the client, you can explicitly load them by using the Include operator. The following code example shows how to load the related Reward entity when you query for a RewardsClaimed entity. [Visual Basic] Dim matchingClaims As IEnumerable(Of RewardsClaimed) = From c in context.RewardsClaimed.Include("Reward") Where c.ClaimID = claimID Select c
[Visual C#] IEnumerable matchingClaims = from c in context.RewardsClaimed.Include("Reward") where c.ClaimID == claimID select c;
Building an N-Tier Solution by Using the Entity Framework
9-29
Question: Does the Entity Framework enable lazy loading by default?
Additional Reading For more information about how the Entity Framework serializes objects, see the Serializing Objects (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194071. For more information about WCF serialization, see the Data Transfer and Serialization page at http://go.microsoft.com/fwlink/?LinkID=194072. For more information about returning related objects in a query, see the How to: Use Query Paths to Shape Results (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194073.
9-30
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Demonstration: Examining an End-to-End Example
Key Points •
Examine a complete end-to-end example of an n-tier solution.
•
Organize your application into separate assemblies.
Demonstration Steps 1.
In the E:\Demofiles folder, run Demo.bat to set up the database for this demonstration.
2.
Create the virtual directory in IIS for the service.
3.
Start Visual Studio 2010 as an administrator.
4.
Open the RewardsDAL solution.
5.
Review that the entity classes are now in the RewardsClientLibrary project, and review that they have been extended by using partial classes.
6.
In the RewardsService project, review the contract and implementation of the WCF service.
Building an N-Tier Solution by Using the Entity Framework
9-31
7.
In the RewardsWebService project, review the service configuration.
8.
Add a console application project to the solution called RewardsClientApp.
9.
Add a reference to the RewardsClientLibrary project.
10. Add a service reference to the Rewards Web service. 11. Add code to invoke the service in Program.cs. 12. Test the client application. 13. Close the application, and then close Visual Studio.
Question: What assemblies are required by the RewardsClientLibrary project?
9-32
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lesson 3
Protecting Data and Operations
After you have built an n-tier solution, you will need to decide how to protect the data from unauthorized access and how to protect the application from attack. This lesson describes how to add security to your n-tier solution. It explains how to use the security features in WCF to protect operations and data, and how to design your application to withstand common attacks.
Objectives After completing this lesson, you will be able to: •
Configure authentication and authorization policies for the operations in your data access layer.
•
Encrypt the data that your application transfers between the data access layer and the client.
•
Protect your application from common attacks.
Building an N-Tier Solution by Using the Entity Framework
9-33
Authentication and Authorization
Key Points When you define a service by using WCF, you can use the PrincipalPermission attribute to specify the authorization rules for your service operation. The following code example demonstrates an authorization rule that restricts access to the GetRewardDetails method to members of the RewardAdmin role. [Visual Basic] Public Function GetRewardDetails(ByVal rewardID As Integer) As Reward ... End Function
9-34
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] [PrincipalPermission(SecurityAction.Demand, Role="RewardAdmin")] public Reward GetRewardDetails(int rewardID) { ... }
In an n-tier application, you can collect the authentication data in the client. The following code example demonstrates how to add the credential data to the service client object. [Visual Basic] svc = New RewardsWebServiceClient(bindingConfiguration) svc.ClientCredentials.Windows.ClientCredential.UserName = userName svc.ClientCredentials.Windows.ClientCredential.Password = password
[Visual C#] svc = new RewardsWebServiceClient(bindingConfiguration); svc.ClientCredentials.Windows.ClientCredential.UserName = userName; svc.ClientCredentials.Windows.ClientCredential.Password = password;
You configure the mechanism used to validate these credentials in the web.config file for the service. You can use the Windows operating system, a third-party security token service (STS), or a custom authentication mechanism to perform the authentication. WCF offers two modes of security that affect how you move credentials over the network. Transport level security relies on a secure transport such as HTTPS to handle authentication. Message level security uses the WS-Security standards to encode the credentials in the message itself. The following code example from the web.config file shows how to specify both transport-based and message-based security for a service. In this scenario, the transport security provides the integrity and confidentiality, and the message security handles the credentials transfer. <wsHttpBinding> <security mode="TransportWithMessageCredential"/>
Building an N-Tier Solution by Using the Entity Framework
9-35
Question: What are the advantages of using message-level security instead of transport-level security to pass credentials to the data access layer?
Additional Reading For more information about WCF authentication, see the Authentication page at http://go.microsoft.com/fwlink/?LinkID=194074. For more information about WCF authorization, see the Authorization page at http://go.microsoft.com/fwlink/?LinkID=194075.
9-36
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Encrypting Data
Key Points When you move data over a network, you must take steps to ensure that you protect any confidential data. This confidential data can include data that your application retrieves from the database, data that the client sends to the data access layer, and security credentials. If you use an HTTP transport, you should configure your service to use the HTTPS protocol that encrypts the HTTP messages by using Secure Sockets Layer (SSL) security. HTTPS encrypts your data to provide confidentiality. It also guarantees the integrity of your data, authenticates the server, and can optionally authenticate the client. To configure HTTPS in IIS, you will need a suitable certificate. For testing purposes, you can use the IIS management tool to generate a self-signed certificate. In the IIS management tool, you specify the bindings for the HTTPS protocol at the site level. The bindings specify the port number and the certificate that you want to use for the HTTPS protocol. You can specify whether SSL is required or optional for a virtual directory or for an individual address.
Building an N-Tier Solution by Using the Entity Framework
9-37
An additional consideration for a WCF service is whether the service metadata is accessible by using HTTPS. The following code example from the web.config file shows how make sure that the metadata for a service is only available by using HTTPS. <system.serviceModel> <serviceBehaviors> <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
Question: Why should you not use a self-signed certificate in a production environment?
Additional Reading For more information about IIS security, see the Configure Web Server Security (IIS 7) page at http://go.microsoft.com/fwlink/?LinkID=194076.
9-38
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Protecting Against Common Attacks
Key Points By using the HTTPS protocol, you can mitigate the risk of some common attacks. For example, SSL encrypts all of the data that the service transfers between the client and the server, which helps to minimize the risk of unnecessary information disclosure. This also applies to the metadata endpoints that a WCF service exposes. SSL also helps to minimize the risk of anyone tampering with the data while the service moves it over the network, and it can help to prevent replay attacks. Another common attack is a denial-of-service (DoS) attack. A DoS attack is a malicious attempt to swamp a server with valid requests so that legitimate users cannot use the service. A partial mitigation for this type of attack is to limit the amount of data that an individual request can return so that it becomes more difficult for an attacker to overwhelm the server with expensive requests. If you use Language-Integrated Query (LINQ) to Entities, you can use the Take operator to limit the number of entities that a query can return. If you use Entity Structured Query Language (Entity SQL), you can use the Limit operator. The following code example shows how to use the Take operator to impose a limit of 50 on the number of claims that an operation returns.
Building an N-Tier Solution by Using the Entity Framework
9-39
[Visual Basic] Return claims.Take(50).ToList(Of RewardsClaimed)()
[Visual C#] return claims.Take(50).ToList();
If you use Entity SQL, you should take steps to avoid the risk of any SQL injection attacks by ensuring that you never combine user input from the client with the Entity SQL command text. Question: Why is LINQ to Entities not susceptible to traditional SQL injection attacks?
Additional Reading For more information about Entity Framework security considerations, see the Security Considerations (Entity Framework) page at http://go.microsoft.com/fwlink/?LinkID=194077. For more information about WCF security considerations, see the Security Considerations page at http://go.microsoft.com/fwlink/?LinkID=194078.
9-40
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab: Building an N-Tier Solution by Using the Entity Framework
Objectives After completing this lab, you will be able to: •
Create a data access layer for an n-tier application.
•
Protect the data access tier.
Introduction In this lab, you will develop a data access tier to fetch and manage contact and order data. You will configure the data access tier to defend against common attacks and then implement authentication and authorization to ensure that clients can only access the data they are allowed to use.
Building an N-Tier Solution by Using the Entity Framework
9-41
Lab Setup For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: •
Start the 10265A-GEN-DEV-09 virtual machine, and then log on by using the following credentials: •
User name: Student
•
Password: Pa$$w0rd
9-42
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Lab Scenario
You have been asked to modify an existing client/server Orders application to use a separate data access tier. As a proof of concept, you have been asked to build a data access tier that client applications can use to query data. In this architecture, you will need to redesign the data access layer to transport objects between the data access tier and the business tier. You will also ensure that the data access tier is protected against common threats, and that it restricts each group of employees so that they can only access data appropriate to their role.
Exercise 1: Creating the Contacts and Orders Data Access Tier Scenario In this exercise, you will create the orders data access tier holding the Contact, Order, and OrderDetail entity objects. The data access tier will be implemented by using a WCF service that will expose business methods that return data as selftracking entities. Each operation will include logic to prevent a request from returning too much data that could potentially swamp the network and hog resources on the server.
Building an N-Tier Solution by Using the Entity Framework
9-43
When a client application requests the details of an order, the corresponding entity objects are created, populated, and transported to the business layer in the client application. The main tasks for this exercise are as follows: 1.
Prepare the environment for the lab.
2.
Prepare the AdventureWorks database for the lab.
3.
Open the starter project.
4.
Create the OrdersDAL class library.
5.
Create the OrdersClientLibrary class library.
6.
Create the IOrdersService interface.
7.
Implement exception handling and logging.
8.
Implement the IOrdersService interface.
9.
Create the OrdersWebService Web service.
10. Configure the OrderManagement application. 11. Test the OrderManagement application.
f Task 1: Prepare the environment for the lab 1.
Log on to the 10265A-GEN-DEV-09 virtual machine as Student with the password Pa$$w0rd.
2.
In the E:\Labfiles folder, run EnvSetup.bat as an administrator. This file configures IIS and creates the required users and groups.
f Task 2: Prepare the AdventureWorks database for the lab •
In the E:\Labfiles folder, run AWReset.bat.
f Task 3: Open the starter project 1.
In the E:\Labfiles\Lab09\VB\Ex1\Starter folder (if you are using Microsoft Visual Basic®), or E:\Labfiles\Lab09\CS\Ex1\Starter folder (if you are using Microsoft Visual C#®), run ExSetup.bat as an administrator. This script adds the required virtual directories to IIS.
9-44
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
2.
Open Visual Studio 2010 as an administrator.
3.
Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex1\Starter\OrdersDAL or E:\Labfiles\Lab09\CS\Ex1\Starter\OrdersDAL folder.
f Task 4: Create the OrdersDAL class library 1.
Add a new Class Library project named OrdersDAL to the solution.
2.
Delete the Class1 file.
3.
Add a new ADO.NET Entity Data Model (EDM) named AdventureWorksModel to the OrdersDAL project. Generate the EDM from the AdventureWorks Microsoft SQL Server® database and create entities for the Contact, SalesOrderHeader, and SalesOrderDetail tables.
4.
Modify the EDM to generate self-tracking entities by adding the ADO.NET SelfTracking Entity Generator code generation item to the EDM. Name the code generation item AdventureWorksModel.tt. Allow Visual Studio to overwrite the existing AdventureWorks.Context.tt file when prompted.
5.
Build the OrdersDAL project and correct any errors.
Important: Only build the OrdersDAL project. The OrderManagement project will not build successfully because it is not yet complete.
f Task 5: Create the OrdersClientLibrary class library 1.
Add a new Class Library project named OrdersClientLibrary to the solution.
2.
Delete the Class1 file.
3.
Add a reference to the System.Runtime.Serialization assembly.
4.
If you are using Visual Basic, change the Root namespace property of the OrdersClientLibrary project to OrdersDAL.
5.
Move the AdventureWorksModel.tt file to the OrdersClientLibrary project.
6.
Add a reference to the OrdersClientLibrary assembly to the OrdersDAL project.
7.
Add a class named AdditionalMethods to the OrdersClientLibrary project.
Building an N-Tier Solution by Using the Entity Framework
8.
9-45
In the AdditionalMethods class, perform the following tasks: a.
Remove the AdditionalMethods class declaration.
b.
If you are using Visual C#, change the namespace to OrdersDAL.
c.
Create a public partial class named SalesOrderHeader.
d. Create a public partial class named SalesOrderDetail. 9.
In the partial SalesOrderHeader class, add code to overwrite the ToString method by returning a string that contains the SalesOrderID, ContactID, AccountNumber, OrderDate, PurchaseOrderNumber, and TotalDue properties from the current object.
10. In the partial SalesOrderDetail class, add code to overwrite the ToString method by returning the ProductID, OrderQty, UnitPrice, UnitPriceDiscount, and LineTotal properties of the current object. 11. Build the OrdersClientLibrary project and correct any errors. Important: Only build the OrdersClientLibrary project. The OrderManagement project will not build successfully because it is not yet complete.
f Task 6: Create the IOrdersService interface 1.
Add a new Class Library project named OrdersService to the solution.
2.
Delete the Class1 file.
3.
Create an interface named IOrdersService in the OrdersService project.
4.
Add a reference to the OrdersClientLibrary assembly.
5.
Add a reference to the OrdersDAL assembly.
6.
Add a reference to the System.ServiceModel assembly.
7.
Add a reference to the System.Runtime.Serialization assembly.
8.
Add a reference to the System.Data.Entity assembly.
9.
In the IOrdersService code file, add code to bring the System.Runtime.Serialization, System.ServiceModel, and OrdersDAL namespaces into scope.
10. In the IOrdersService file, before the interface declaration, write code to perform the following tasks:
9-46
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
a.
Add a new class called ServiceFault. Prefix the class with the DataContract attribute.
b.
Add a public string field to the ServiceFault class called ExceptionType. Prefix the field with the DataMember attribute.
c.
Add a public string field to the ServiceFault class called ExceptionMessage. Prefix the field with the DataMember attribute.
11. In the IOrdersService file, perform the following tasks: a.
Make the IOrdersService interface public (if it is not already public).
b.
Specify that the IOrdersService interface defines a service contract called OrdersWebService. Use http://microsoft.com as the Web service namespace.
12. In the IOrdersService interface, perform the following tasks: a.
Define a method called GetContactDetails that returns an object of type Contact and accepts an integer named contactID as a parameter.
b.
Specify that the GetContactDetails method is Web service operation that is part of the OrdersWebService service contract.
c.
Specify that the GetContactDetails method returns a message of type ServiceFault if an exception occurs.
13. In the IOrdersService interface, perform the following tasks: a.
Define a method called GetAllContactsInRange that returns a generic IEnumerable object of with a type parameter of Contact, and accepts an integer named lowerBound and an integer named upperBound as parameters.
b.
Specify that the GetAllContactsInRange method is Web service operation that is part of the OrdersWebService service contract.
c.
Specify that the GetAllContactsInRange method returns a message of type ServiceFault if an exception occurs.
14. In the IOrdersService interface, perform the following tasks: a.
Define a method called GetOrderDetails that returns an object of type SalesOrderHeader and accepts an integer named orderID as a parameter.
b.
Specify that the GetOrderDetails method is a Web service operation that is part of the OrdersWebService service contract.
Building an N-Tier Solution by Using the Entity Framework
c.
9-47
Specify that the GetOrderDetails method returns a message of type ServiceFault if an exception occurs.
15. In the IOrdersService interface, perform the following tasks: a.
Define a method called GetOrdersForContact that returns a generic IEnumerable object of with a type parameter of SalesOrderHeader and accepts an integer named contactID as a parameter.
b.
Specify that the GetOrdersForContact method is a Web service operation that is part of the OrdersWebService service contract.
c.
Specify that the GetOrdersForContact method returns a message of type ServiceFault if an exception occurs.
16. In the IOrdersService interface, perform the following tasks: a.
Define a method called GetOrdersForProduct that returns a generic IEnumerable object of with a type parameter of SalesOrderHeader and accepts an integer named productID as a parameter.
b.
Specify that the GetOrdersForProduct method is a Web service operation that is part of the OrdersWebService service contract.
c.
Specify that the GetOrdersForProduct method returns a message of type ServiceFault if an exception occurs.
17. In the IOrdersService interface, perform the following tasks: a.
Define a method called GetAllOrdersInRange that returns a generic IEnumerable object with a type parameter of SalesOrderHeader and accepts an integer named lowerBound and an integer named upperBound as parameters.
b.
Specify that the GetAllOrdersInRange method is a Web service operation that is part of the OrdersWebService service contract.
c.
Specify that the GetAllOrdersInRange method returns a message of type ServiceFault if an exception occurs.
18. Build the project and correct any errors. Important: Only build the OrdersService project. The OrderManagement project will not build successfully because it is not yet complete.
9-48
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 7: Implement exception handling and logging 1.
Create a class named OrdersServiceImpl in the OrdersService project.
2.
In the OrdersServiceImpl code file, write code to bring the following namespaces into scope: •
System.Text
Note: If you are using Visual C#, the System.Text namespace is already in scope and there is no need to add it again.
3.
•
System.ServiceModel
•
System.Diagnostics
•
System.Threading
•
OrdersDAL
•
System.Security.Permissions
In the OrdersServiceImpl code file, perform the following tasks: a.
Prefix the OrdersWebService class with the ServiceBehavior attribute, specify the name of the service as OrdersWebService, and set the namespace to http://microsoft.com.
b.
Set the InstanceContextMode property of the ServiceBehavior attribute to PerCall.
c.
Set the ConcurrencyMode property of the ServiceBehavior attribute to Multiple.
d. Declare the OrdersServiceImpl class as a public class (if it is not already public) that implements the IOrdersService interface. 4.
In the OrdersServiceImpl class, write code to perform the following tasks: a.
Declare a constant integer named maxContactsCount and assign it a value of 500.
b.
Declare a constant integer named maxOrdersCount and assign it a value of 400.
c.
Declare a constant string named eventSource and assign it the value "Orders Service".
Building an N-Tier Solution by Using the Entity Framework
9-49
d. Declare a constant string named eventLog and assign it the value "Application". 5.
In the OrdersServiceImpl class, write code to perform the following tasks: a.
Create a new private method named logException that accepts an Exception object named ex and a string object named eventName as parameters. This method should not return a value.
b.
In the logException method, verify that the event source identified by the eventSource constant exists by calling the SourceExists method of the EventLog class in the System.Diagnostics namespace. If the event source does not exist, create it by calling the CreateEventSource method of the EventLog class, specifying the eventSource and eventLog constants as parameters.
c.
Declare a string named eventMessage and assign it a value by combining the eventName variable, the Message property of the ex variable, and the value of the Thread.CurrentPrincipal.Identity.Name property.
d. Call the WriteEntry method of the EventLog class, passing the eventSource and eventMessage variables as parameters and specifying Error as the EventLogEntryType parameter. 6.
7.
In the OrdersServiceImpl class, create a new private method named handleException that accepts the following parameters: •
An Exception object named ex.
•
A string parameter named operationName.
•
An optional integer parameter named operationData with a default value of 0.
In the handleException method, write code to perform the following tasks: a.
Create a new StringBuilder object named eventMessageBuilder.
b.
Append the message "Failure in {0}" to the eventMessageBuilder object, and specify the value of the operationName parameter as the {0} placeholder.
c.
If the value of the operationData variable is not equal to 0, append the value of the operationData parameter to the eventMessageBuilder object.
d. Call the logException method, passing the ex object and the string value of the eventMessageBuilder object as parameters. 8.
In the handleException method, add code to perform the following tasks:
9-50
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
a.
If the ex object is of type ApplicationException, create a new ServiceFault object named sf. Set the ExceptionType property to the type of the ex object, and set the ExceptionMessage property by using Message property of the ex object.
b.
Throw a new FaultException<ServiceFault> exception, specifying the sf object and the string value of the eventMessageBuilder object as parameters.
c.
If the ex object is some other type of exception, create a new ServiceFault object named sf, and set the ExceptionType property to the type of the ex object and the value of the ExceptionMessage property to the string "Exception occurred fetching data".
d. Throw a new FaultException<ServiceFault> exception. Pass the value of the sf object and the message "Failure in {0}" where the {0} placeholder is the value of the operationName variable as parameters to the constructor. 9.
Save the OrdersServiceImpl file.
f Task 8: Implement the IOrdersService interface 1.
In the OrdersServiceImpl class, generate stub methods for each of the items in the IOrdersService interface.
2.
Locate the GetContactDetails method. This method takes an integer value as a parameter and returns a Contact object.
3.
If you are using Visual C#, delete the default method body that throws a NotImplementedException exception.
4.
In the body of the method, add code to perform the following tasks: a.
Create a Contact object named contact and assign it the value null (Nothing in Visual Basic).
b.
Create a new AdventureWorksEntities object.
c.
Define a LINQ query named matchingContacts that retrieves all of the Contact entities by using the AdventureWorksEntities object, with a ContactID property that is equal to the value of the contactID variable.
d. If there is at least one Contact object in the matchingContacts collection, return it; otherwise, return a null (Nothing in Visual Basic) value. If there is more than one matching contact, return the first one found.
Building an N-Tier Solution by Using the Entity Framework
9-51
Note: The ContactID should be unique, so there should only be at most one matching contact. However, it is good practice to write defensive code just in case a database administrator amends the structure of the Contact table in the database and creates a different key column.
e.
Handle any exceptions by calling the handleException method; pass the Exception object, the method name, and the contactID variable as parameters before returning a null (Nothing in Visual Basic) value.
5.
Locate the GetAllContactsInRange method. This method takes two integer values, lowerBound and upperBound, as parameters and returns an IEnumerable list of Contact objects.
6.
If you are using Visual C#, delete the default method body that throws a NotImplementedException exception.
7.
In the body of the method, add code to perform the following tasks: a.
Create an IEnumerable object named contacts by using the Contact type as the type parameter, and assign it the value null (Nothing in Visual Basic).
b.
Create a new AdventureWorksEntities object.
c.
Define a LINQ query that retrieves all of the Contact entities where the ContactID property is between the values of the lowerBound and upperBound variables. The result of this query should be assigned to the contacts object.
d. If the number of objects in the contacts collection is greater than or equal to the value of the maxContactsCount constant, throw a new ApplicationException exception with the message "Too many contacts". e.
Return the contacts collection as a generic List object. If you are using Visual C#, specify the Contact type as the type parameter for the List object.
f.
Handle any exceptions by calling the handleException method; pass the exception object and the method name as parameters before returning null (Nothing in Visual Basic).
8.
Locate the GetOrderDetails method. This method takes an integer value as a parameter and returns a SalesOrderHeader object.
9.
If you are using Visual C#, delete the default method that throws a NotImplementedException exception.
9-52
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
10. In the body of the method, add code to perform the following tasks: a.
Create a SalesOrderHeader object named order and assign it the value null (Nothing in Visual Basic).
b.
Create a new AdventureWorksEntities object.
c.
Define a LINQ query named matchingOrders that retrieves all of the SalesOrderHeader entities and the related SalesOrderDetail entities where the SalesOrderID property matches the value in the orderID variable.
d. If the number of objects in the matchingOrders collection is greater than zero, return the first SalesOrderHeader object in the collection; otherwise, return null (Nothing in Visual Basic). e.
Handle any Exception exceptions by calling the handleException method; pass the exception object, the method name, and the orderID variable as parameters before returning null (Nothing in Visual Basic).
11. Locate the GetOrdersForContact method. This method takes an integer value as a parameter and returns an IEnumerable list of SalesOrderHeader objects. 12. If you are using Visual C#, delete the default method body that throws a NotImplementedException exception. 13. In the body of the method, add code to perform the following tasks: a.
Create an IEnumerable object named orders by using the SalesOrderHeader type as the type parameter, and assign it the value null (Nothing in Visual Basic).
b.
Create a new AdventureWorksEntities object.
c.
Define a LINQ query that retrieves all of the SalesOrderHeader entities and related SalesOrderDetail entities where the ContactID property matches the value in the contactID variable. Assign the result of this query to the orders object.
d. If the number of objects in the orders collection is greater than or equal to the value of the maxOrdersCount constant, throw a new ApplicationException exception with the message "Too many orders". e.
Return the orders collection as a generic List object. If you are using Visual C#, specify the SalesOrderHeader type as the type parameter for the List object.
Building an N-Tier Solution by Using the Entity Framework
f.
9-53
Handle any exceptions by calling the handleException method; pass the exception object, the method name, and the contactID variable as parameters before returning null (Nothing in Visual Basic).
14. Locate the GetOrdersForProduct method. This method takes an integer value as a parameter and returns an IEnumerable list of SalesOrderHeader objects. 15. If you are using Visual C#, delete the default method body that throws a NotImplementedException exception. 16. In the body of the method, add code to perform the following tasks: a.
Create an IEnumerable object named orders by using the SalesOrderHeader type as the type parameter, and assign it the value null (Nothing in Visual Basic).
b.
Create a new AdventureWorksEntities object.
c.
Define a LINQ query that retrieves all of the SalesOrderHeader entities and related SalesOrderDetail entities where the ProductID property of at least one of the SalesOrderDetail entities for the SalesOrderHeader entity matches the productID variable. Assign the result of this query to the orders object.
Note: The SalesOrderDetail records for an order specify the products being ordered. An order can have one or more SalesOrderDetail records. To find all orders for a specific product, you must find all SalesOrderDetail records that match the product and return the SalesOrderHeader objects that reference these SalesOrderDetail records.
d. If the number of objects in the orders collection is greater than or equal to the value of the maxOrdersCount constant, throw a new ApplicationException exception with the message "Too many orders". e.
Return the orders collection as a generic List object. If you are using Visual C#, specify the SalesOrderHeader type as the type parameter for the List object.
f.
Handle any exceptions by calling the handleException method; pass the exception object, and the method name as parameters before returning null (Nothing in Visual Basic).
17. Locate the GetAllOrdersInRange method. This method takes two integer values, lowerBound and upperBound, as parameters and returns an IEnumerable list of Contact objects.
9-54
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
18. If you are using Visual C#, delete the default method body that throws a NotImplementedException exception. 19. In the body of the method, add code to perform the following tasks: a.
Create an IEnumerable object named orders by using the SalesOrderHeader type as the type parameter, and assign it the value null (Nothing in Visual Basic).
b.
Create a new AdventureWorksEntities object.
c.
Define a LINQ query that retrieves all of the SalesOrderHeader entities and related SalesOrderDetails entities where the value in the SalesOrderID property is between the lowerBound and upperBound variables. The result of this query should be assigned to the orders object.
d. If the number of objects in the orders collection is greater than or equal to the value of the maxOrdersCount constant, throw a new ApplicationException exception with the message "Too many orders". e.
Return the orders collection as a generic List object. If you are using Visual C#, specify the SalesOrderHeader type as the type parameter for the List object.
f.
Handle any exceptions by calling the handleException method; pass the exception object, and the method name as parameters before returning null (Nothing in Visual Basic).
20. Build the OrdersService project and correct any errors. Important: Only build the OrdersService project. The OrderManagement project will not build successfully because it is not yet complete.
f Task 9: Create the OrdersWebService Web service 1.
Add a new empty ASP.NET Web Application project named OrdersWebService to the solution.
2.
Configure the project to use the local IIS Web server with the URL http://localhost/OrdersWebService.
3.
Add a new WCF service called OrdersWebService.svc to the OrdersWebService project.
Building an N-Tier Solution by Using the Entity Framework
9-55
4.
Delete the IOrdersWebService code file from the OrdersWebService project.
5.
Add a reference to the OrdersClientLibrary assembly.
6.
Add a reference to the OrdersDAL assembly.
7.
Add a reference to the OrdersService assembly.
8.
Delete the OrdersWebService code-behind file.
9.
In the OrdersWebService.svc file, delete the existing markup code.
10. In the OrdersWebService.svc file, add markup statements that perform the following tasks: a.
Create a ServiceHost instance and set the Service property to point to the OrdersServiceImpl service that is defined in the OrdersService project.
b.
Specify that this service is located in the OrdersService assembly.
Note: Visual Studio reports that it cannot find the OrdersService assembly. This warning will disappear when you build the project and you can safely ignore it.
11. Delete the Web.config file from the OrdersWebService project. 12. Add the existing Web.config file from the E:\Labfiles\Lab09\CS\Ex1\Starter or E:\Labfiles\Lab09\VB\Ex1\Starter folder to the OrdersWebService project. 13. Build the OrdersWebService project and correct any errors. Important: Only build the OrdersWebService project. The OrderManagement project will not build successfully because it is not yet complete.
f Task 10: Configure the OrderManagement application 1.
In the OrderManagement project, add a reference to the OrdersClientLibrary assembly.
2.
Add a service reference to the OrdersWebService service to the OrderManagement application. Generate the service reference in the OWService namespace. The URL of the OrdersWebService is http://localhost/OrdersWebService/OrdersWebService.svc. Use System.Collections.Generic.List as the collection type and ensure that you reuse types in referenced assemblies.
9-56
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
3.
In the app.config file, modify the definition of the wsHttpBinding binding by increasing the maxBufferPoolSize property to 5242880 and the maxReceivedMessageSize property to 5242880.
4.
Review the task list.
5.
Open the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab 9, Ex1 - Fetch a single contact task in the task list. This task is located in the RetrieveContacts method.
6.
Immediately after the TODO: Lab9, Ex1 - Fetch a single contact comment, write code to perform the following tasks: a.
Create a new Contact object named contact by calling the GetContactDetails method of the service object. Pass the rangeFrom variable as a parameter.
Note: The service variable is an OrdersWebServiceClient object. The OdersWebServiceClient type was generated when you added the service reference to the OrdersWebService service. This type provides the Web service proxy for connecting to the OrdersWebService service, and it exposes methods that you can call to invoke the operations in the OrdersWebService service.
b.
If the contact object is not null (Nothing in Visual Basic), instantiate the contacts generic List collection and specify Contact as the type parameter for this list.
c.
Add the contact object to the contacts list.
7.
Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab 9, Ex1 - Fetch all contacts in range task in the task list.
8.
Immediately after the TODO: Lab 9, Ex1 - Fetch all contacts in range comment, write code to call the GetAllContactsInRange method of the service object. Specify the rangeFrom and rangeTo variables as parameters. Assign the returned value to the contacts object.
9.
Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact task in the task list. This task is located in the getOrdersForContact_Click method.
10. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified contact comment, write code to call the GetOrdersForContact method of the
Building an N-Tier Solution by Using the Entity Framework
9-57
service object. Specify the contactID variable as the parameter. Assign the returned value to the orders object. 11. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab 9, Ex1 - Fetch the orders for the specified product task in the task list. This task is located in the getOrdersForProduct_Click method. 12. Immediately after the TODO: Lab 9, Ex1 - Fetch the orders for the specified product comment, write code to call the GetOrdersForProduct method of the service object. Specify the productID variable as the parameter. Assign the returned value to the orders object. 13. Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab 9, Ex1 - Fetch a single order task in the task list. This task is located in the RetrieveOrders method. 14. Immediately after the comment, write code to create a new SalesOrderHeader object named order. Assign this the value that is returned by calling the GetOrderDetails method of the service object. Specify the rangeFrom variable as a parameter. If an order is returned by the GetOrderDetails method, instantiate the orders generic List object and add the order to this list. Specify SalesOrderHeader as the type parameter for the orders list. 15. Locate the next comment in the code behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab 9, Ex1 - Fetch all orders in range task in the task list. 16. Immediately after the TODO: Lab 9, Ex1 - Fetch all orders in range comment, write code to call the GetAllOrdersInRange method of the service object. Specify the rangeFrom and rangeTo variables as parameters. Assign the returned value to the orders object. 17. Build the solution and correct any errors.
f Task 11: Test the OrderManagement application 1.
Check that the OrderManagement application is set as the startup project.
2.
Start the application without debugging.
3.
In the Order Management window, on the Contacts tab, click Get. One contact is displayed in the contacts grid.
9-58
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
4.
Adjust the To slider to a value that is less than 500, and then click Get. Check that the correct number of rows is returned.
5.
Adjust the To slider to a value that is greater than 500, and then click Get. An exception will be thrown containing the message "Too many contacts".
6.
In the Service Fault occurred dialog box, click OK.
7.
In the Order Management window, click the General Orders tab.
8.
On the General Orders tab, adjust the From slider to a value that is greater than 50000, and then click Get. A single order is displayed in the orders grid. Expand the order to view the order details.
9.
Adjust the To slider to retrieve a range of between 20 and 50 orders, and then click Get. Verify that the correct number of orders is displayed.
You can use the arrow keys to adjust the slider in small increments.
10. Adjust the To slider to retrieve a range of over 500 orders. Verify that a message box appears with the message “Too many orders.” 11. In the Service Fault occurred dialog box, click OK. 12. On the Orders By Contact tab, in the Contact ID box, type 10 and then click Get. The four orders placed by customer 10 should appear. Expand each order to view the details. 13. On the Orders By Product tab, in the Product ID box, type 710 and then click Get. The 44 orders for this product should appear. Expand each order to view the details. 14. Close the Order Management window and return to Visual Studio. 15. Open the Orders Service event log to view the details of exceptions generated by the Web service. 16. Close the solution.
Exercise 2: Protecting Data Access Operations Scenario In this exercise, you will configure the data access tier to defend against common attacks, and implement authentication and authorization to ensure that client applications can only access the data that they are allowed to use.
Building an N-Tier Solution by Using the Entity Framework
9-59
You will define two roles—OrderAdmin and ContactAdmin—and then configure the DAL so that only users in the ContactAdmin role can use the GetContactDetails and GetAllContactsInRange operations, and only users in the OrderAdmin role can invoke the GetOrderDetails, GetOrdersForContact, GetOrdersForProduct, and GetAllOrdersInRange operations. You will then configure the service to encrypt data as it traverses the network, and will protect against replay and DoS attacks. The main tasks for this exercise are as follows: 1.
Open the starter project.
2.
Host the OrdersWebService Web service by using SSL.
3.
Apply security demands.
4.
Modify the OrdersWebService Web service to use transport security.
5.
Modify the OrderManagement application.
6.
Create unit tests.
7.
Build and test the application.
f Task 1: Open the starter project 1.
In the E:\Labfiles\Lab09\VB\Ex2\Starter folder (if you are using Visual Basic), or E:\Labfiles\Lab09\CS\Ex2\Starter folder (if you are using Visual C#), run ExSetup.bat as an administrator.
2.
Open the existing solution, OrdersDAL.sln, in the E:\Labfiles\Lab09\VB\Ex2\Starter\OrdersDAL or E:\Labfiles\Lab09\CS\Ex2\Starter\OrdersDAL folder.
f Task 2: Host the OrdersWebService Web service by using SSL 1.
Open IIS Manager as an administrator.
2.
Add a new self-signed certificate named OrdersWebService.
3.
Edit the binding of the default Web site to use HTTPS and the OrdersWebService certificate.
4.
Set the OrdersWebService Web service to require SSL security.
5.
Close IIS Manager.
9-60
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
6.
Set the Project Url property for the OrdersWebService project to use HTTPS.
f Task 3: Apply security demands 1.
Review the task list.
2.
Open the OrdersServiceImpl code file by double-clicking the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetAllContactsInRange task in the task list.
3.
Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetAllContactsInRange comment, add the PrincipalPermission attribute to specify that users must be members of the ContactAdmin role.
4.
Locate the next comment in the OrdersServiceImpl code file by doubleclicking the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetContactDetails task in the task list.
5.
Immediately after the TODO: Lab9, Ex2 - Allow ContactAdmins to call GetContactDetails comment, add the PrincipalPermission attribute to specify that users must be members of the ContactAdmin role.
6.
Locate the next comment in the OrdersServiceImpl code file by doubleclicking the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetAllOrdersInRange task in the task list.
7.
Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetAllOrdersInRange comment, add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin role.
8.
Locate the next comment in the OrdersServiceImpl code file by doubleclicking the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrderDetails task in the task list.
9.
Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrderDetails comment, add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin role.
10. Locate the next comment in the OrdersServiceImpl code file by doubleclicking the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForContact task in the task list. 11. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForContact comment, add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin role.
Building an N-Tier Solution by Using the Entity Framework
9-61
12. Locate the next comment in the OrderServiceImpl code file by double-clicking the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForProduct task in the task list. 13. Immediately after the TODO: Lab9, Ex2 - Allow OrderAdmins to call GetOrdersForProduct comment, add the PrincipalPermission attribute to specify that users must be members of the OrderAdmin role. 14. Build the solution and correct any errors.
f Task 4: Modify the OrdersWebService Web service to use transport security with message-level credentials 1.
In the OrdersWebService project, open the web.config file.
2.
In the web.config file, locate the TODO: Lab 9, Ex2 - Use TransportWithMessageCredential security mode comment. This comment is located in the bindings section.
3.
Change the security mode from None to TransportWithMessageCredential.
4.
In the web.config file, locate the TODO: Lab 9, Ex2 - Enable https, disable http comment. This comment is located in the serviceBehaviors section.
5.
Change the serviceMetadata property to enable HTTPS and disable HTTP.
6.
Build the solution and correct any errors.
f Task 5: Modify the OrderManagement application 1.
Update the OrdersWebService service reference to use the new binding and security.
2.
Review the task list.
3.
Open the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab9, Ex2 - Add credentials in getContacts_Click task in the task list.
4.
Immediately after the TODO: Lab9, Ex2 - Add credentials in getContacts_Click comment, add code to perform the following tasks: a.
Assign the value of the userName.Text property to the service.ClientCredentials.Windows.ClientCredential.UserName property.
9-62
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
b.
Assign the value of the password.Password property to the service.ClientCredentials.Windows.ClientCredential.Password property.
5.
Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrders_Click task in the task list.
6.
Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrders_Click comment, add code to perform the following tasks: a.
Assign the value of the userName.Text property to the service.ClientCredentials.Windows.ClientCredential.UserName property.
b.
Assign the value of the password.Password property to the service.ClientCredentials.Windows.ClientCredential.Password property.
7.
Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click task in the task list.
8.
Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForContact_Click comment, add code to perform the following tasks:
9.
a.
Assign the value of the userName.Text property to the service.ClientCredentials.Windows.ClientCredential.UserName property.
b.
Assign the value of the password.Password property to the service.ClientCredentials.Windows.ClientCredential.Password property.
Locate the next comment in the code file behind the OrderManagementWindow.xaml window by double-clicking the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click task in the task list.
10. Immediately after the TODO: Lab9, Ex2 - Add credentials in getOrdersForProduct_Click comment, add code to perform the following tasks:
Building an N-Tier Solution by Using the Entity Framework
a.
Assign the value of the userName.Text property to the service.ClientCredentials.Windows.ClientCredential.UserName property.
b.
Assign the value of the password.Password property to the service.ClientCredentials.Windows.ClientCredential.Password property.
9-63
11. Build the solution and correct any errors.
f Task 6: Create unit tests 1.
Review the task list.
2.
Open the OrderServiceImplTest code file by double-clicking the TODO: Lab9, Ex2 - Create a unit test for the GetAllContactsInRange method task in the task list.
3.
Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllContactsInRange method comment, add the following unit test code. This code calls the GetAllContactsInRange method to fetch all contacts in the range 100 to 500, and verifies that the method returns the correct number of rows. This method specifies the user name Fred and the password Pa$$w0rd. This user is a member of the ContactAdmin role. Your code should resemble the following code example.
[Visual Basic] Public Sub GetAllContactsInRangeTest() service.ClientCredentials.Windows.ClientCredential.UserName = "Fred" service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd" Dim Dim Dim Dim Dim
lowerBound As Integer = 100 upperBound As Integer = 500 expectedCount As Integer = 401 expectedFirstName As String = "Jackie" actual As IEnumerable(Of Contact) = service.GetAllContactsInRange(lowerBound, upperBound) Assert.AreEqual(expectedCount, actual.Count()) Assert.AreEqual(expectedFirstName, actual.First().FirstName) End Sub
9-64
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
[Visual C#] [TestMethod()] public void GetAllContactsInRangeTest() { service.ClientCredentials.Windows.ClientCredential.UserName = "Fred"; service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; int lowerBound = 100; int upperBound = 500; int expectedCount = 401; string expectedFirstName = "Jackie"; IEnumerable actual = service.GetAllContactsInRange(lowerBound, upperBound); Assert.AreEqual(expectedCount, actual.Count()); Assert.AreEqual(expectedFirstName, actual.First().FirstName); }
4.
Locate the next comment in the OrderServiceImplTest code file by doubleclicking the TODO: Lab9, Ex2 - Create a unit test for the GetAllOrdersInRange method task in the task list.
5.
Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetAllOrdersInRange method comment, add the following unit test code. This code calls the GetAllOrdersInRange method to retrieve all orders in the range 43650 to 43700, and verifies that the method returns the correct number of rows. This method specifies the user name Fred and the password Pa$$w0rd.
[Visual Basic] Public Sub GetAllOrdersInRangeTest() service.ClientCredentials.Windows.ClientCredential.UserName = "Bert" service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd" Dim Dim Dim Dim Dim
lowerBound As Integer = 43650 upperBound As Integer = 43700 expectedCount As Integer = 42 expectedSalesOrderDetailCount As Integer = 12 actual As IEnumerable(Of SalesOrderHeader) = service.GetAllOrdersInRange(lowerBound, upperBound)
Building an N-Tier Solution by Using the Entity Framework
9-65
Assert.AreEqual(expectedCount, actual.Count()) Assert.AreEqual(expectedSalesOrderDetailCount, actual.First().SalesOrderDetails.Count()) End Sub
[Visual C#] [TestMethod()] public void GetAllOrdersInRangeTest() { service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"; service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; int lowerBound = 43650; int upperBound = 43700; int expectedCount = 42; int expectedSalesOrderDetailCount = 12; IEnumerable<SalesOrderHeader> actual = service.GetAllOrdersInRange(lowerBound, upperBound); Assert.AreEqual(expectedCount, actual.Count()); Assert.AreEqual(expectedSalesOrderDetailCount, actual.First().SalesOrderDetails.Count()); }
6.
Locate the next comment in the OrderServiceImplTest code file by doubleclicking the TODO: Lab9, Ex2 - Create a unit test for the GetContactDetails method task in the task list.
7.
Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetContactDetails method comment, add the following unit test code. This code calls the GetContactDetails method to retrieve the details of contact 13, and verifies that the method returns the correct data. This method specifies the user name Fred and the password Pa$$w0rd.
[Visual Basic] Public Sub GetContactDetailsTest() service.ClientCredentials.Windows.ClientCredential.UserName = "Fred" service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd" Dim contactID As Integer = 13 Dim expectedFirstName As String = "Robert"
9-66
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Dim expectedLastName As String = "Ahlering" Dim actual As Contact = service.GetContactDetails(contactID) Assert.AreEqual(expectedFirstName, actual.FirstName) Assert.AreEqual(expectedLastName, actual.LastName) End Sub
[Visual C#] [TestMethod()] public void GetContactDetailsTest() { service.ClientCredentials.Windows.ClientCredential.UserName = "Fred"; service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; int contactID = 13; string expectedFirstName = "Robert"; string expectedLastName = "Ahlering"; Contact actual = service.GetContactDetails(contactID); Assert.AreEqual(expectedFirstName, actual.FirstName); Assert.AreEqual(expectedLastName, actual.LastName); }
8.
Locate the next comment in the OrderServiceImplTest code file by doubleclicking the TODO: Lab9, Ex2 - Create a unit test for the GetOrderDetails method task in the task list.
9.
Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrderDetails method comment, add the following unit test code. This code calls the GetOrderDetails method to retrieve the details of order 71780, and verifies that the method returns the correct data. This method specifies the user name Bert and the password Pa$$w0rd. This user is a member of the OrderAdmin role.
[Visual Basic] Public Sub GetOrderDetailsTest() service.ClientCredentials.Windows.ClientCredential.UserName = "Bert" service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd" Dim orderID As Integer = 71780 Dim expectedSalesOrderID As Integer = 71780 Dim expectedAccountNumber As String = "10-4020-000340" Dim expectedSalesOrderDetailsCount As Integer = 29 Dim actual As SalesOrderHeader = service.GetOrderDetails(orderID)
Building an N-Tier Solution by Using the Entity Framework
9-67
Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID) Assert.AreEqual(expectedAccountNumber, actual.AccountNumber) Assert.AreEqual(expectedSalesOrderDetailsCount, actual.SalesOrderDetails.Count) End Sub
[Visual C#] [TestMethod()] public void GetOrderDetailsTest() { service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"; service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; int orderID = 71780; int expectedSalesOrderID = 71780; string expectedAccountNumber = "10-4020-000340"; int expectedSalesOrderDetailsCount = 29; SalesOrderHeader actual = service.GetOrderDetails(orderID); Assert.AreEqual(expectedSalesOrderID, actual.SalesOrderID); Assert.AreEqual(expectedAccountNumber, actual.AccountNumber); Assert.AreEqual(expectedSalesOrderDetailsCount, actual.SalesOrderDetails.Count); }
10. Locate the next comment in the OrderServiceImplTest code file by doubleclicking the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForContact method task in the task list. 11. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForContact method comment, add the following unit test code. This code calls the GetOrdersForContact method to retrieve the orders for contact 100, and verifies that the method returns the correct data. This method specifies the user name Bert and the password Pa$$w0rd. [Visual Basic] Public Sub GetOrdersForContactTest() service.ClientCredentials.Windows.ClientCredential.UserName =
9-68
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
"Bert" service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd" Dim Dim Dim Dim
contactID As Integer = 100 expectedOrdersCount As Integer = 4 expectedSalesOrderDetailCount As Integer = 3 actual As IEnumerable(Of SalesOrderHeader) = service.GetOrdersForContact(contactID) Assert.AreEqual(expectedOrdersCount, actual.Count()) Assert.AreEqual(expectedSalesOrderDetailCount, actual.First().SalesOrderDetails.Count()) End Sub
[Visual C#] [TestMethod()] public void GetOrdersForContactTest() { service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"; service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; int contactID = 100; int expectedOrdersCount = 4; int expectedSalesOrderDetailCount = 3; IEnumerable<SalesOrderHeader> actual = service.GetOrdersForContact(contactID); Assert.AreEqual(expectedOrdersCount, actual.Count()); Assert.AreEqual(expectedSalesOrderDetailCount, actual.First().SalesOrderDetails.Count()); }
12. Locate the final comment in the OrderServiceImplTest code file by doubleclicking the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForProduct method task in the task list. 13. Immediately after the TODO: Lab9, Ex2 - Create a unit test for the GetOrdersForProduct method comment, add the following unit test code. This code calls the GetOrdersForProduct method to retrieve the orders that contain product 709, and verifies that the method returns the correct data. This method specifies the user name Bert and the password Pa$$w0rd.
Building an N-Tier Solution by Using the Entity Framework
[Visual Basic] Public Sub GetOrdersForProductTest() service.ClientCredentials.Windows.ClientCredential.UserName = "Bert" service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd" Dim Dim Dim Dim
productID As Integer = 709 expectedOrderCount As Integer = 188 expectedOrderDetailsCount As Integer = 12 actual As IEnumerable(Of SalesOrderHeader) = service.GetOrdersForProduct(productID) Assert.AreEqual(expectedOrderCount, actual.Count()) Assert.AreEqual(expectedOrderDetailsCount, actual.First().SalesOrderDetails.Count) End Sub
[Visual C#] [TestMethod()] public void GetOrdersForProductTest() { service.ClientCredentials.Windows.ClientCredential.UserName = "Bert"; service.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; int productID = 709; int expectedOrderCount = 188; int expectedOrderDetailsCount = 12; IEnumerable<SalesOrderHeader> actual = service.GetOrdersForProduct(productID); Assert.AreEqual(expectedOrderCount, actual.Count()); Assert.AreEqual(expectedOrderDetailsCount, actual.First().SalesOrderDetails.Count); }
14. Build the solution and correct any errors.
9-69
9-70
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
f Task 7: Build and test the application 1.
Run all of the tests in the solution and verify that they are successful.
2.
Start the OrderManagement application without debugging.
3.
In the Order Management window, on the Contacts tab, click Get to retrieve the details of contact 1 from the Web service. No credentials have been supplied; therefore, an exception is thrown. In the message box displaying the message "Access is denied", click OK.
4.
Test the application by using the two sets of credentials in the following table. You should only be able to use the Contacts tab when you are authenticated as Fred, and you should only be able to use the General Orders, Orders By Contact, and Orders By Product tabs when you are authenticated as Bert. You should not be able to access any data if you omit or specify incorrect credentials. Username
5.
Password
Role
Fred
Pa$$w0rd
ContactAdmin
Bert
Pa$$w0rd
OrderAdmin
Close Visual Studio.
Building an N-Tier Solution by Using the Entity Framework
9-71
Lab Review
Review Questions 1.
How did you implement the code that generates self-tracking entities for an EDM?
2.
How did you restrict the volume of data returned by the OrdersWebService Web service?
3.
How did you restrict the users that can invoke the operations in the OrdersWebService Web service?
4.
How did you specify the credentials for a user invoking operations in the OrdersWebService Web service?
9-72
Developing Data Access Solutions with Microsoft® Visual Studio® 2010
Module Review and Takeaways
Review Questions 1.
In an n-tier application, should you try to make the size of the individual messages that the client and the data access layer exchange as small as possible?
2.
What are the two main components that the T4 ADO.NET Self-Tracking Entity Generator templates generate?
3.
What is the potential problem that occurs if lazy loading is enabled in an n-tier data access layer?
4.
How does the HTTPS protocol help to protect your application from replay attacks?
Building an N-Tier Solution by Using the Entity Framework
9-73
Best Practices Related to Building an N-Tier Solution by Using the Entity Framework Supplement or modify the following best practices for your own work situations: •
Use DTOs if you only need to pass a subset of data in each entity to client applications.
•
Use STEs if you need to pass complete entities to client applications.
•
Use WCF to host your data access layer.
•
Use the T4 templates to generate your STEs.
•
Make sure that you secure your WCF service.
•
Make sure that just the necessary information is moved over the network.