Programming Entity Framework, 1st Edition by Julia Lerman
Publisher: O'Reilly Media, Inc. Pub Date: February 12, 2009 Print ISBN-13: 978-0-596-52028-1 Pages: 832
Overview Programming Entity Framework is a thorough introduction to Microsoft's new core framework for modeling and accessing data in .NET applications. This book not only gives experienced developers a hands-on tour of the Entity Framework, and explains its use in a variety of applications, but also provides a deep dive into its architecture and APIs. From the Entity Data Model (EDM) and Object Services to EntityClient and the Metadata Workspace, Programming Entity Framework covers it all. Written by Julia Lerman, the leading independent authority on the framework, Programming Entity Framework includes scores of reusable examples written in both Visual Basic and C# that you can implement right away. With this book, you will: Understand the core concepts you need to make best use of the Entity Framework (EF) in your applications Learn to query your data, using either LINQ to Entities or Entity SQL Create Windows Forms, WPF, and ASP.NET applications Build ASMX web services, and WCF services Use Object Services to work directly with your entity objects Delve into model customization, relationship management, change tracking, data concurrency, and more Presented in a clear narrative style that reflects the hundreds of hours the author has spent consulting, teaching, and writing about this new data access model and testing its myriad features, Programming Entity Framework will help you master the technology and put it to work.
Copyright Copyright © 2009, Julia Lerman. All rights reserved. Printed in the United States of America. Published by O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O'Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://safari.oreilly.com ). For more information, contact our corporate/institutional sales department: (800) 998-9938 or
[email protected]. Editor: John Osborn Production Editor: Loranah Dimant Editor: Audrey Doyle Nutshell Handbook, the Nutshell Handbook logo, and the O'Reilly logo are registered trademarks of O'Reilly Media, Inc. Programming Entity Framework , the image of a Seychelles blue pigeon, and related trade dress are trademarks of O'Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O'Reilly Media, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. .NET is a registered trademark of Microsoft Corporation. While every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.
Foreword If you have been involved in the .NET programming community over the last couple of years, there's a pretty good chance that you have at least heard of Microsoft's Entity Framework (EF). I've been fortunate enough to be part of the team building what is turning into a central, foundational technology for data access. The Entity Framework builds on the programming patterns established in ADO.NET, and even though there are many new ideas in the EF, those ideas build on familiar concepts like connection strings, data readers, and transaction management. A key goal of the EF is to help ADO.NET developers feel at home while giving them the opportunity to make a radical shift in their approach to data access, and at the same time make data access much more approachable to those folks who don't dream in SQL. The Entity Framework changes the game by replacing the building blocks you use to model your data. Instead of tables and foreign-keys used in the relational model, the Entity Data Model (EDM) provides rich entity definitions, including inheritance and first-class relationships. The EDM building blocks are much more valuable because they are closer to the way we think and reason about data. In fact, the core ideas behind entity-relationship modeling have been around for a long time, and they are often used as a means to specify the intent for how a database should be designed. Traditionally, however, they have been confined to diagrams on a whiteboard or project specifications squirreled away somewhere. The EDM codifies these concepts so that the model can become a first-class part of your application with a digital representation that captures information about the structure of your data and the way you intend to use it. The Entity Framework is the first piece of a broad new data platform built around the EDM. It provides object/relational mapping capabilities, a rich provider model that makes it possible to query and update data stored in many different types of databases without altering your code, and much more. What really makes the EF powerful, though, is the way the EDM enables a whole ecosystem of services to work together. Once you have an EDM, for instance, you can very easily leverage it to provide REST-oriented web services using ADO.NET Data Services or auto-generated websites using ASP.NET Dynamic Data. And in future releases, more and more Microsoft offerings will be able to work with the EDM to build services that have a common way to reason about data and a deeper understanding of how developers intend for it to be used. Reporting services, sync, analysis services, data visualization, workflow, and cloud data services are all examples of projects underway, but chances are we haven't yet even dreamed of what will become the most important such service. Microsoft is making a major commitment to the EF—in many ways it is the future of data access and many other projects. There is an awful lot that it can do for you. Unfortunately, though, there are a lot of new concepts to learn. If you want to be in a position to leverage what the EF can do for you in the long run, in my opinion you've got to start building your understanding and real-world experience with it now so that you will be in a position to take advantage of all it has to offer in the future. That's where Julie Lerman and this book come in. As you take on the challenge of learning this new technology, you will find that a map and a friendly, knowledgeable guide are a HUGE asset. This book is that map and Julie is that guide. Here you will find what you need to start your journey and to extract real value from the EF now. Along the way, you'll learn how to build a model; how to customize it; more ways to query it than you thought possible; something about how to expose that data in Winforms, WPF, and ASP.NET; how to build web services on top of it; things to think about when it comes time to tune performance; and much more. Several books touch on the Entity Framework as part of a general discussion about LINQ or new data access technologies, but this book gets down to work and really focuses on the EF from end-to-end. You have probably encountered some technical books that are largely expanded reference works designed to supplement other sources of reference information like MSDN. This is not one such book. It is designed to teach you EF concepts and show you how to apply them in a logical progression. Even though it isn't a reference book, Julie has done a lot to dig into all the corners and find the important points, which is great because reading the book gives you a good understanding of capabilities and a solid overview so that you can figure out where you might want to drill in deeper. I especially appreciate Julie's effort to give samples for the multiple ways to do things when it comes to differences between C# and VB (more than you might expect in LINQ) or between LINQ and Entity SQL and the like. So turn the page and keep reading. There's a lot to explore…. Oh yeah, and as you go, please take a moment now and
then to join in the conversation at http://blogs.msdn.com/efdesign . You can make a difference in the EF and the EDM. Please help us make them even more valuable to your development efforts. At the end of the day, that's the real measure of our success.
Preface In June 2006, I was invited to attend a meet-up for data geeks at TechEd North America. As it was early enough not to compete with the many fun evening parties at TechEd, I happily crossed the lovely bridge between the Convention Center and the hotel where the meeting was to take place. Little did I know I was about to see a new technology from Microsoft's Data Programmability team that was going to be the focus of my attention for the next few years. In addition to other geeky discussions about data access, Pablo Castro, Mike Pizzo, and Britt Johnson (all from Microsoft) talked to us about a new technology that was coming in the next version of ADO.NET. It would allow developers to create their own views of their database and query against these views rather than against the database. As usual, TechEd was overwhelming, so as interesting as this new way of working with data looked to me, I had to put it in a back corner of my mind and let it simmer for a few months. I finally downloaded the preview and began playing with it. What was most fun to me when I started exploring this technology, called Entity Framework, was the lack of serious documentation, which forced me to play with all of its knobs and dials to figure out what was in there and how it worked.
NOTE Unlike many in-development technologies from Microsoft, the Entity Framework did not start off with a cool name as did WCF (née Indigo) and ADO.NET Data Services (Astoria). Although it is often hard to give up these early technology nicknames for their final (and relatively boring) names, the Entity Framework has had its "grown-up name" since the beginning. Over this time, it also became clear how important the Entity Framework and its underlying Entity Data Model are to Microsoft. They are a critical part of Microsoft's strategy for the data access that all of its products perform, whether this is the data that Reporting Services uses to enable us to build reports, the data that comprises Workflow, data in the cloud, or data that we developers want our .NET applications to access. As the Entity Framework evolved and further CTPs were released, followed by betas, I became quite fond of working against a data model and no longer having to think about the structure of the database I was working against. I began to peel away the top layers of the Entity Framework and discovered that I could make it do nearly anything I wanted as I gained a better understanding of how it worked. When I hit a wall, I asked the Entity Framework team how to get past it, and if there wasn't a way to do so I camped out on their virtual doorstep until they modified the framework or Designer to enable me to do what I wanted and what I knew other developers would need. During this time, I was excited to share what I was learning with other developers through the MSDN forums, my blog, conference sessions, and articles. However, I constantly felt restrained by the time or space allotted to me. Conference sessions are generally 75–90 minutes long. Magazine articles tend to be 5–10 pages. I felt as though I was going to self-combust if I couldn't share all of this new information, and so I contacted O'Reilly to ask whether I could write a book about the Entity Framework. My editor, John Osborn, was a bit taken aback because for years I had shunned publishers, saying I would have to have lost my mind to write a book. It's not a simple undertaking. But I knew that if I didn't write a book about ADO.NET Entity Framework, I certainly would lose my mind. The time had come. I was so excited, and of course, I had no idea what I was in for! Over these past two years, I have torn apart the Entity Framework to figure out how it works and how to take advantage of it to access data as well as work with the data that is returned. I have worked very closely with the Entity Framework team and am grateful to them for listening to my feedback and helping me to better understand this technology, as well as for the encouragement and support they have given me while writing this book. In the course of presenting on the Entity Framework at .NET User Groups and conferences around the world, as well as spending a lot of time in the MSDN forums interacting with others who are investigating and implementing the Entity Framework into their applications, I have been able to look at the Entity Framework through the eyes and questioning minds of hundreds of developers, which has been so educational. This book is the culmination of understanding how the Entity Framework version 1 can meet not only my demands as a developer, but also those of the many developers and architects I have interacted with in the past few years.
P2.1. Who This Book Is For This book is written for developers who are familiar with .NET programming, whether you are entirely new to the Entity Framework or have been using it and want to solidify your current understanding as well as go deeper. The first half of the book (Chapters Chapter 1–Chapter 14 ) covers introductory topics, and the latter half (ChaptersChapter 15–Chapter 22 ) dives under the covers to give you a deep understanding of what you'll find in the Entity Framework and how it works, as well as how to get the most out of it. The early walkthroughs, which demonstrate the use of the Entity Framework in a variety of applications (Windows Forms, Windows Presentation Foundation, ASP.NET, Web Services, and WCF services), are written so that you can follow them even if you have never created a particular application type before. The goal of this book is to help developers not only get up and running with the Entity Framework, but also be empowered to gain granular control over the model and the objects that result through use of the core Entity Framework APIs. Its focus is the version 1 that was released as part of Visual Studio 2008 Service Pack 1 in August 2008. Although the book will provide some guidance for using the Entity Framework in your application architecture, it is not a book about architecture. Instead, the book attempts to provide you with the information and knowledge you need to use the Entity Framework to solve your specific domain problems. Because of the vast scope of the Entity Framework, topics on tools that leverage the Entity Framework, such as ADO.NET Data Services (a.k.a. Astoria) and the ASP.NET Dynamic Data Controls, are not included. Some of the Entity Framework's features are comparable to LINQ to SQL and other object relational models such as NHibernate and LLBLGen. Apart from a few paragraphs in Chapter 1 , this book does not directly position the Entity Framework against these object relational models.
NOTE All of the code samples inProgramming Entity Framework are provided in both Visual Basic and C#. The book includes many debugger screenshots, and unless noted, these have been taken from the Visual Basic IDE.
P2.2. How This Book Is Organized Programming Entity Framework focuses on two ways for you to learn. If you learn best by example, you'll find many walkthroughs and code samples throughout the book; if you're always looking for the big picture, you'll also find chapters that dive deeply into conceptual information. I have tried to balance the walkthroughs and conceptual information I provide so that you will never get too much of one at a time. The first half of the book is introductory, and the second half digs much deeper. Following is a brief description of each chapter:
Chapter 1 This chapter provides an overview of the ADO.NET Entity Framework—where it came from, what problems it attempts to solve, and the classic "10,000-foot view" of what it looks like. The chapter also addresses the most frequently asked questions about the Entity Framework, such as how it fits into the .NET Framework, what databases it works with, what types of applications you can write with it, how it differs from object relational models, and how it works with the rest of ADO.NET.
Chapter 2 The Entity Data Model (EDM) lies at the core of the Entity Framework. This chapter explains what the EDM is, and teaches you how to create one using the EDM Wizard and then manipulate your model using the Designer. You will also get a walkthrough of the various parts of the EDM, viewing it through the Designer or through its raw XML.
Chapter 3 The Entity Framework provides a number of methods for querying against the EDM—LINQ to Entities, Entity SQL with ObjectQuery, EntityClient , and a few more. Each method has its own benefits. In this chapter, you will learn the basics for leveraging the various query modes by requesting the same data using each method. You will also learn the pros and cons for choosing one method over another, as well as gain an understanding of what happens behind the scenes in between query execution and the data that results.
Chapter 4 With the query basics in hand, you can now learn how to perform different types of tricks with querying: projection, filtering, aggregates, and so forth. Because the objects you are querying are related, you can also query across these relationships. This chapter will walk you through a huge variety of queries, and for each task you will see how to write the query using the various methods of querying. This is by no means an exhaustive depiction of every type of query you can perform, but it will give you a huge head start. There are more than 100 query samples in this chapter.
Chapter 5 This chapter presents a high-level view of how the Entity Framework tracks changes to entities, processes updates, and builds the final queries that are executed at the database. By having a better understanding of the Entity Framework's default functionality, you will be better prepared to address common concerns regarding security and performance. Additionally, understanding the default process will make the following chapter on stored procedures much more meaningful.
Chapter 6 This chapter is the first of two to dig into using stored procedures in the Entity Framework. The EDM Designer provides support for one set of scenarios, and that is what is covered in this chapter. Chapter 13 covers the set of scenarios that require more effort.
Chapter 7 Up to this point in the book, you will have been working with a very simplistic database and model so that you can focus on all of the new tools. This chapter introduces a larger model and database that support the fictitious travel adventure company, BreakAway Geek Adventures, which you will use throughout the rest of the book. With this model, you will get a better understanding of building and customizing a model. Chapter 12 will go even further into customizing the model with advanced modeling and mappings.
Chapter 8
This chapter provides two walkthroughs for using the Entity Framework to perform data binding in Windows Forms and WPF. In the course of these walkthroughs, you'll learn a lot of tips and tricks that are specific to doing data binding with Entity Framework objects, as well as expand your knowledge of the Entity Framework along the way.
Chapter 9 The Entity Framework's Object Services API provides all of the functionality behind working with the objects that are realized from the data shaped by your Entity Data Model. Although the most critical of Object Services' features is its ability to keep track of changes to entity objects and manage relationships between them, it offers many additional features as well. This chapter provides an overview of all of Object Services' responsibilities, how it impacts most of the work you do with the Entity Framework, and how you can use these features directly to impact how the Entity Framework operates. Later chapters focus even more deeply on particular areas within Object Services.
Chapter 10 So far, the objects you will have been working with are based on the default classes that the Entity Framework generates directly from the model, but you don't need to be limited to what's in the objects. There are plenty of opportunities for customizing the code-generated classes. This chapter walks you through how to take advantage of these extensibility points. It is also possible to completely avoid the generated classes and use your own custom classes, an option we will cover in Chapter 19.
Chapter 11 It's time to create another application with the Entity Framework. There are a lot of hurdles to overcome when using the Entity Framework in an ASP.NET application that allows users to edit data. The EntityDataSource control is part of the family of ASP.NETDataSource controls that you can configure in the UI and that will automate data access and updating for you. This chapter will show you how to use this control. Later chapters will teach you what you need to know to overcome these hurdles yourself, and Chapter 20 leverages this knowledge to address building layered ASP.NET applications rather than putting the logic in the UI.
Chapter 12 One of the most important features of the Entity Data Model is the ability to customize it to shape your data structure in a way that is more useful than working directly against the database schema. This chapter walks through many of the ways you can achieve this, demonstrating how to implement a variety of inheritance mappings, create an entity that maps to multiple tables, build complex types, and more. If you are following along with the walkthroughs, most of the modifications you make to the sample model in this chapter you will use for applications you'll build in later chapters.
Chapter 13 Chapter 6 covers the stored procedure scenarios that the Designer supports, but you can achieve much more if you are willing to crack open the model's raw XML and perform additional customizations. This chapter will walk you through adding "virtual" store queries and stored procedures into the model, and taking advantage of other features that will make the model work for you, rather than being constrained by its most commonly used features.
Chapter 14 Like ASP.NET, using the Entity Framework in web and WCF services provides a number of challenges. In this chapter, you will learn how to build and consume an ASMX ("classic") Web Service and then build and consume a WCF service. If you have never created services before, have no fear. The walkthroughs will help you with step-by-step instructions. This chapter is the first of two that address building services. Chapter 22 revisits building WCF services, leveraging knowledge you will gain in the latter portion of the book. The preceding chapters will have provided you with a solid base of understanding and working with the Entity Framework. Starting with Chapter 15, you will learn about the Entity Framework's advanced topics:
Chapter 15 The Entity Data Model is based on Entity Relationship Modeling, which is about entities and relationships. Relationships are a critical part of the model and how the Entity Framework performs its functions. In fact, relationships are instantiated as objects. To really understand and control the Entity Framework and avoid hurting your head when the relationships don't behave the way you might expect, you should have a deep comprehension of how relationships work in the model and your Entity Framework code. This chapter will provide you with that.
Chapter 16 Up to this point, you have seen bits and pieces of code out of the context of real-world applications. But how does the Entity Framework fit in with the everyday concerns of software developers? This chapter will address some of the many questions developers ask after learning the basics about the Entity Framework. How do you control connections? Is there any connection pooling? Are database calls transactional? What about security? How's the performance?
Chapter 17 This is another chapter where you get to dig even further into the APIs to interact with your objects in the same way that many of the internal functions do. With the two classes featured in this chapter, you can write code to generically work with EntityObject s or raw data whether you want to create reusable code for your apps or write utilities. There are some hefty samples in this chapter.
Chapter 18 Hard as we try to write perfect code, things can still go wrong in our applications, which is why we need exception handling. The Entity Framework provides a set of its own exceptions to help you deal with the unique problems that may occur when working with entities—poorly written queries, entities that are missing required related objects, or even a problem in the database. In addition to typical coding problems, data access highlights another arena of issues regarding concurrency: when multiple people are editing and updating data. The Entity Framework supports optimistic concurrency and uses the OptimisticConcurrencyException to detect these problems. The chapter will also show you how to take advantage of this.
Chapter 19
In Chapter 10 , you learned how to customize the classes generated from the model. However, you can use your own classes as well and still take advantage of the model, querying, and the change tracking and relationship management features of Object Services. In this chapter, you will learn two mechanisms for implementing your own classes into an Entity Framework-based application: inheriting from EntityObject and implementing the IPOCO interfaces. At this point in the book, you will have learned quite a lot about how the Entity Framework functions and how to work with the objects and the model in a granular way. The next three chapters focus on challenges and solutions for using the Entity Framework in enterprise applications. The book concludes with a forward look at what's down the road for the Entity Framework, and an appendix that serves as a guide to the assemblies and namespaces of the Entity Framework:
Chapter 20 The earlier Windows application walkthroughs focused on simple architectures to get you started with data binding. Most medium to large applications are not written in this way, but rather separate their logic and data layers from the UI. This chapter will look at some of the specific features you can take advantage of and challenges you might face when architecting Windows and WPF applications to keep the data access and business logic out of the user interface.
Chapter 21 Chapter 11 focused on building ASP.NET apps using theEntityDataSource control to avoid some of the issues with change tracking across tiers in the Entity Framework. Now that you have learned much more about working with EntityObject s, it is time to address these challenges head-on and learn how you can build ASP.NET application layers. This chapter begins by addressing the specific issues that the ASP.NET Page life cycle poses for entities, and walks through a solution that leverages the ASP.NET
ObjectDataSource control to work with classes that control all of the interaction with the Entity Framework. The sample in this chapter provides a first step toward concepts that will help you architect applications to fit your own domain model.
Chapter 22 Like the preceding two chapters, this chapter revisits WCF using all of the lessons you've learned about the Entity Framework since Chapter 14 . In this chapter, you will see a WCF service that leverages Data Transfer Objects (DTOs) to not only create a much more succinct and lightweight payload for the consumer, but also build in properties you can use to overcome the challenges of change tracking across tiers.
Chapter 23 The Entity Framework described in this book is version 1. As this book is going to print, the Entity Framework team is hard at work on version 2. This chapter introduces you to some additional resources that will fill in some of the gaps in version 1 that you can use today, and it introduces you to some of the features you will see when the next iteration of the Entity Framework is released as part of .NET 4.0 and Visual Studio 2010.
Appendix A This appendix is a guide to the physical files that are part of the Entity Framework and each namespace in the programming model.
P2.3. What You Need to Use This Book The Entity Framework is part of Microsoft Visual Studio 2008 Service Pack 1. You can use the Entity Framework with any of the Visual Studio 2008 versions, from Express through Team System. Although the Entity Framework can work with many database providers, the SqlClient provider is part of Visual Studio 2008, and therefore all of the samples here are based on SQL Server. You can use SQL Server Express or Standard, and the Entity Framework will recognize versions 2000, 2005, and 2008. The first draft of this book was written against SQL Server 2005 and the final version used SQL Server 2008. Following is a specific list of system requirements: Windows XP with SP2, Windows Server 2003, or Windows Vista and SP1 Microsoft SQL Server 2000, Microsoft SQL Server 2005, Microsoft SQL Server 2005 Express Edition, Microsoft SQL Server 2008, or Microsoft SQL Server 2008 Express Edition Microsoft Visual Studio 2008 with SP1
P2.4. This Book's Website Visit http://www.ProgrammingEntityFramework.com/ (also available at http://www.LearnEntityFramework.com/ ) for downloads, errata, links to resources, and other information. In the Downloads area, you will find: Scripts for creating the sample databases used in this book. Scripts for SQL Server 2005 and SQL Server 2008 are provided. The sample applications from the book in both Visual Basic and C#. Note that there are also hundreds of code samples in the book. You will find many but not all of them on the website as well.
P2.5. Conventions Used in This Book The following typographical conventions are used in this book:
Italic Indicates new terms, URLs, email addresses, filenames, file extensions, pathnames, directories, and Unix utilities
Constant width Indicates commands, options, switches, variables, attributes, keys, functions, types, classes, namespaces, methods, modules, properties, parameters, values, objects, events, event handlers, XML tags, HTML tags, macros, the contents of files, or the output from commands
Constant width bold
Shows commands or other text that should be typed literally by the user
Constant width italic Shows text that should be replaced with user-supplied values
NOTE This icon signifies a tip, suggestion, or general note.
This icon indicates a warning or caution.
This icon indicates a Visual Basic code sample.
This icon indicates a C# code sample.
P2.6. Using Code Examples This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permission unless you're reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O'Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product's documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: "Programming Entity Framework, by Julia Lerman. Copyright 2009 Julia Lerman, 978-0-596-52028-1." If you feel your use of code examples falls outside fair use or the permission given here, feel free to contact us at
[email protected].
P2.7. Safari® Books Online
NOTE When you see a Safari® Books Online icon on the cover of your favorite technology book, that means the book is available online through the O'Reilly Network Safari Bookshelf.
Safari offers a solution that's better than e-books. It's a virtual library that lets you easily search thousands of top tech books, cut and paste code samples, download chapters, and find quick answers when you need the most accurate, current information. Try it for free at http://safari.oreilly.com.
P2.8. Comments and Questions Please address comments and questions concerning this book to the publisher: O'Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at: http://www.oreilly.com/catalog/9780596520281/ To comment or ask technical questions about this book, send email to:
[email protected] For more information about our books, conferences, Resource Centers, and the O'Reilly Network, see our website at: http://www.oreilly.com/
P2.9. Acknowledgments Where do I begin? I have had great help and support from many people at Microsoft and many developers around the globe. At Microsoft, I would like to thank many people on the Entity Framework and Data Programmability teams for their support and continual efforts to help me not only understand the Entity Framework, but also enable me to share my understanding with so many others through speaking at conferences and user groups as well as through articles. Elisa Flasko and Mike Pizzo helped me to better understand the big picture of the Entity Framework and have ensured that I have access to the information I need. Technically, I couldn't have done any of this without the support of Danny Simmons, Zlatko Michailov, Pablo Castro, Noam Ben-Ami, Colin Meek, Sanjay Nagamangalam, Diego Vega, Mike Kaufman, Jeff Reed, Alex James, Jeff Derstadt, Brad Sarsfield, and Bruno Gardia Robles. Many of these folks patiently answered my questions in the forums as well as in elaborate email threads (even on nights and weekends). They also looked over many of the chapters to make sure I didn't mislead anyone, and to provide further enlightenment. Throughout the course of writing this book, a number of folks in the community looked at what I was doing and helped me shape the content. Everyone reading this book owes these people a debt of gratitude, as their feedback and comments had a huge impact on the final product. My harshest critic, whom I thank profusely for that criticism, was Jim Wooley, coauthor of the beautifully written LINQ in Action . My muse for making sure I wasn't too confusing for those brand-new to the Entity Framework was Camey Combs, who I'm proud to report recently gave her first Code Camp presentation in Seattle and her topic was the Entity Framework. Fabulous feedback in critical areas from Shawn Wildermuth, Markus Rytterkull, Jarod Ferguson, Tony Sneed, and Paul Gielens also had a big impact on the final product. But the man to blame for this whole adventure is Guy Barrette, who gave me the final push from contemplating this book to committing to it. I am grateful for the friendship and support of three brilliant women who have been through this process before: Kathleen Dollard, Kate Gregory, and Michele Leroux Bustamante. Thanks also to TechSmith (http://www.techsmith.com/ ), which provided me with a license to its awesome screen
capture utility, SnagIt, which I used for all of the screenshots in the book. And if not for Tangible's Instant C# and Instant VB (http://tangiblesoftwaresolutions.com/ ) code conversion tools, I might still be sitting here pounding out the dual C# and VB samples used throughout the book. Thanks to my editor, John Osborn, for keeping me on track and giving me lots of virtual pats on the back when I experienced some of the difficulties of book writing that many other technical authors warned me about, and in general to O'Reilly Media for so much support and encouragement and boxes full of chocolate goodies when I needed them most. (Thanks, Laurel!) As this is my first book, I had no idea what I was getting into. John was a great mentor with respect to understanding how to bring structure to the flurry of ideas that I was eager to get down on paper. Audrey Doyle, the copyeditor, was heroic in not only cleaning up my words, but also keeping me honest when I drifted off course. At a later stage in the process, O'Reilly's production editor, Loranah Dimant, was equally patient, tireless, and meticulous as we polished everything up to get it ready for the printer. My mother is an author whose very first novel, written in her early 30s, was put up for a Pulitzer Prize by her publisher, DoubleDay Press. She's a tough act to follow for sure, but here I am having found my own path to writing. She'll not understand a word of this book, but we have found new entertainment in comparing our books' daily rankings on Amazon. The guy who suffered most through this long and arduous process was my very patient husband, Rich Flynn, who thankfully expanded his culinary expertise beyond Kraft Macaroni and Cheese as I emerged less frequently from my office to make dinner. In so many ways, Rich truly enabled me to hunker down to work on this book so that I didn't have to worry too much about the day-to-day things in our lives, even though much of what I was doing was a complete mystery to him. In the middle of this process, during one awful week, I suffered the painful loss of my two beloved Newfoundland dogs, Tasha and Daisy. At ages 14½ and 13½, these dear, dear girls had lived wonderfully long lives, which is very unusual for "Newfies." I have put off truly mourning them so that I could get through writing this book, and I thank their lovely spirits for their patience.
Chapter 1. Introducing the ADO.NET Entity Framework Developers spend far too much time worrying about their backend database, its tables and their relationships, the names and parameters of stored procedures and views, as well as the schema of the data that they return. Microsoft's new Entity Framework changes the game for .NET developers so that we no longer have to be concerned with the details of the data store as we write our applications. We can focus on the task of writing our applications, rather than accessing the data. The ADO.NET Entity Framework is a new data access platform from Microsoft for writing .NET applications. It was released in July 2008 as part of the Visual Studio Service Pack 1 and .NET 3.5 Service Pack 1, two years after Microsoft announced it at its TechEd 2006 Conference. Although the existing data access features remain in ADO.NET, this new framework is part of Microsoft's core data access strategy going forward. Therefore, the Entity Framework will receive the bulk of the innovation and resources from Microsoft's Data Programmability team. It's an important technology for Microsoft, and one that you should not ignore. Why do we need a new data access technology? After many years of forcing developers to switch from DAO to RDO to ADO and then to ADO.NET, with ADO.NET it seemed that Microsoft had finally settled on a tool in which we could make a big investment. As Visual Studio and the .NET Framework have evolved, ADO.NET has evolved by enhancement and addition, but has remained backward compatible all along. Our investment was safe. And it remains safe. The Entity Framework is another enhancement to ADO.NET, giving developers an added mechanism for accessing data and working with the results in addition to DataReaders and DataSet s. One of the core benefits of the Entity Framework is that you don't have to be concerned with the structure of your database. All of your data access and storage are done against a conceptual data model that reflects your own business objects.
1.1. Programming Against a Model, Not Against the Database With DataReader s and many other data access technologies, you spend a lot of time getting data from the database, reading through the results, picking out bits of data, and pushing those bits into your business classes. With the Entity Framework, you are not querying against the schema of the database, but rather against a schema that reflects your own business model. As the data is retrieved, you are not forced to reason out the columns and rows and push them into objects; they are returned as objects. When it's time to save changes back to the database, you have to save only those objects. The Entity Framework will do the necessary work to translate your objects back into the rows and columns of the relational store. The Entity Framework does this part of the job for you, similar to the way an Object Relational Mapping tool works. The Entity Framework uses a model called an Entity Data Model (EDM), which evolved from Entity Relationship Modeling (ERM), a concept that has been used in database development for many years.
The Entity Data Model's Roots The Entity Framework evolved from a methodology called Entity Relationship Modeling (ERM) that has been trapped on whiteboards for more than 30 years. ERM defines a schema of entities and their relationships with one another. Entities are not the same as objects. Entities define the schema of an object, but not its behavior. So, an entity is something like the schema of a table in your database, except that it describes the schema of your business objects. We have drawn these ERMs for years to help us figure out how to transpose the structured tabular data of our databases into the structure of our objects. No mention of ERM is complete without a nod to Dr. Peter Chen, who is credited with the first definitive paper on ERM in 1976. With a host of database gurus in its ranks, Microsoft Research began to devise a way to automate the process of bridging a conceptual model to the database schema. And it needed to be a two-way street so that we could retrieve data from the database into our entities and persist changes back into the database. In June 2006, Microsoft Research published its first paper on the EDM, its answer to ERM. The paper's authors include database legend Jim Gray, who tragically disappeared while sailing off the coast of San Francisco Bay in 2007.
1.2. The Entity Data Model: A Client-Side Data Model An EDM is a client-side data model and it is the core of the Entity Framework. It is not the same as the database model; that belongs to the database. The data model in the application describes the structure of your business objects. It's as though you were given permission to restructure the database tables and views in your enterprise's database so that the tables and relationships look more like your business domain rather than the normalized schema that is designed by database administrators. Figure 1-1 shows the schema of a typical set of tables in a database. PersonalDetails provides additional information about a Person that the database administrator has chosen to put into a separate table for the sake of scalability.
SalesPerson is a table that is used to provide additional information for those people who are salespeople.
Figure 1-1. Schema of normalized database tables
When working with this data from your application, your queries will be full of inner joins and outer joins to access the additional data about Person records. Or you will access a variety of predefined stored procedures and views, which might each require a different set of parameters and return data that is shaped in a variety of ways. A T-SQL query to retrieve a set of SalesPerson records along with their personal details would look something like this:
SELECT FROM
SalesPerson.*, PersonalDetails.*, Person.* Person
INNER JOIN PersonalDetails ON Person.PersonID = PersonalDetails.PersonID INNER JOIN SalesPerson ON Person.PersonID = SalesPerson.PersonID
Imagine that a particular application could have its own view of what you wish the database looked like. Figure 1-2 reshapes the schema.
Figure 1-2. Person data shaped to match your business objects
All of the fields fromPersonalDetails are now part ofPerson. And SalesPerson is doing something that is not even possible in a database; it is deriving from Person, just as you would in an object model. Now imagine that you can write a LINQ query that looks like this:
From p In People.OfType(Of SalesPerson) Select p
from p in People.OfType select p
In return, you will have a set ofSalesPerson objects with all of the properties defined by this model (see Figure 1-3).
Figure 1-3. The SalesPerson object
NOTE LINQ exists only in the C# and Visual Basic languages. With the Entity Framework there is another way to express queries, which not only allows you to use other languages, but also provides additional benefits that you can take advantage of as necessary. It's called Entity SQL, and you will learn much more about it and LINQ to Entities in Chapters Chapter 3 and Chapter 4. This is the crux of how the Entity Framework can remove the pain of having not only to interact with the database, but also to translate the tabular data into objects. .NET is but one tool that uses an EDM. The next version of SQL Server will use an EDM for Reporting Services. You will begin to see other Microsoft applications that will adopt the EDM concept as well. In fact, you will find that model-driven development in general is getting more and more attention from Microsoft. When working with the Entity Framework, you will use a particular implementation of an EDM. In the Entity Framework, an EDM is represented by an EDMX file at design time that is split into a set of three XML files at runtime.
1.3. The Entity in "Entity Framework" The items described in the EDM are called entities . The objects that are returned are based on these entities and are calledentity objects . They differ from typical domain objects in that they have properties but no behavior apart from methods to enable change tracking. Figure 1-4 shows the class diagram for both of the classes that the model generates automatically. Each class has a factory method and methods that are used to notify the Entity Framework of whether a property has changed.
Figure 1-4. Class diagrams for the Person and SalesPerson entities
You can add business logic to the generated classes, pull the results into your own business objects, and even link your business objects to the EDM and remove the generated classes. But by definition, the entities describe only their schema. In addition to being able to reshape the entities in the data model, you can define relationships between entities.Figure 1-5 adds a Customer entity (also deriving from person) and anOrder entity to the model. Notice the relationship lines betweenSalesPerson and Order, showing a one-to-many relationship between them. There is also a one-to-many relationship betweenCustomer and Order.
Figure 1-5. SalesPerson and Customer entities, each with a relationship to Order entities
When you write queries against this version of the model, you don't need to useJOIN s. The model provides navigation between the entities. The following LINQ to Entities query retrieves order information along with information about the customer. It navigates into the Customer property of the Order to get the FirstName and LastName of the Customer:
From o In context.Orders Select o.OrderID,o.OrderNumber,o.Customer.FirstName,o.Customer.LastName
from o in context.Orders select new {o.OrderID,o.OrderNumber,o.Customer.FirstName,o.Customer.LastName}
Once that data is in memory, you can navigate through each object and its properties,myOrder.Customer.LastName, just as readily. The Entity Framework also lets you retrieve graphs, which means you can return shaped data such as aCustomer with all of its Order details already attached. These are some of the huge benefits to querying against a data model, rather than directly against the database.
1.4. Choosing Your Backend You may have noticed that I have not mentioned the actual data store that owns the data being queried. The model doesn't have any knowledge of the data store—what type of database it is, much less what the schema is. And it doesn't need to. The database you choose as your backend will have no impact on your model or your code. The Entity Framework communicates with the same ADO.NET data providers that ADO.NET already uses, but with a caveat. The provider must be updated to support the Entity Framework. The provider takes care of reshaping the Entity Framework's queries and commands into native queries and commands. All you need to do is identify the provider and a database connection string so that the Entity Framework can get to the database. This means that if you need to write applications against a number of different databases, you won't have to learn the ins and outs of each database. You can write queries with the Entity Framework's syntax (either LINQ to Entities or Entity SQL) and never have to worry about the differences between the databases. If you need to take advantage of functions or operators that are particular to a database, Entity SQL allows you to do that as well.
NOTE You may have noticed I use the term data store rather than always referring to the database. Although the Entity Framework currently focuses on working with databases, Microsoft's vision is to work with any relational store—for example, an XML file with a known schema.
1.4.1. Available Providers Microsoft's SqlClient API that is included with Visual Studio 2008 SP 1 supports the Entity Framework. It will allow you to use SQL Server 2000, 2005, and 2008. You can use the full or Express version of SQL Server 2005 and 2008 and the full version of SQL Server 2000. SQL Server CE version 3.5 supports the Entity Framework as well in the System.Data.SqlServerCe.3.5 API. At the time of this writing, a host of other providers are available—and more are on their way—that will allow you to use Oracle, IBM databases, SQL Anywhere, MySQL, SQLite, VistaDB, and many other databases. The providers are being written by the database vendors as well as by third-party vendors.
NOTE On the Resources page of this book's website, you can find a list of provider APIs that support the Entity Framework.
1.4.2. Access and ODBC A provider that supports the Entity Framework needs to have specific knowledge about the type of database it is connecting to. It needs to be aware of the available functions and operators for the database, as well as the proper syntax for native queries. Open Database Connectivity (ODBC) providers provide generic access to a variety of databases, including Access, and cannot furnish the necessary database particulars to act as a provider for the Entity Framework. Therefore, ODBC is not a valid provider for the Entity Framework. Unless someone creates a provider specifically for Access, you won't be able to use it with Entity Framework applications. Microsoft does not have plans to build an Access provider because the demand is too low.
1.5. Entity Framework Features In addition to the EDM, the Entity Framework provides a set of .NET APIs that let you write .NET applications using the EDM. It also includes a set of design tools for designing the model. Following is a synopsis of the Entity Framework's key features.
1.5.1. The Entity Data Model Although the Entity Framework is designed to let you work directly with the classes from the EDM, it still needs to interact with the database. The conceptual data model that the EDM describes is stored in an XML file whose schema identifies the entities and their properties. Behind the conceptual schema described in the EDM is another pair of schema files that map your data model back to the database. One is an XML file that describes your database and the other is a file that provides the mapping between your conceptual model and the database. During query execution and command execution (for updates), the Entity Framework figures out how to turn a query or command that is expressed in terms of the data model into one that is expressed in terms of your database. When data is returned from the database, it does the job of shaping the database results into the entities and further materializing objects from those results.
1.5.2. Entity Data Model Design Tools The screenshots in Figures Figure 1-2 and Figure 1-3 are taken from the EDM Designer. It is part of Visual Studio and provides you with a way to work visually with the model rather than tangle with the XML. You will work with the Designer right away in Chapter 2 , and you'll learn how to use it to do some more advanced modeling, such as inheritance, in Chapter 12 . You will also learn about the Designer's limitations, such as the fact that it does not support all of the features of the EDM. With some of the less frequently used EDM features, you'll have to work directly with the XML after all. In Chapter 2 , you will get a look at the XML and how it relates to what you see in the Designer so that when it comes time to modify it in Chapter 12 , you'll have some familiarity with the raw schema files. The Designer also allows you to map stored procedures to entities, which you'll learn about in Chapter 6 . Unfortunately, support for stored procedures in the Designer has its limitations as well, so Chapter 13 will show you how to achieve what you can't do with the Designer. Another notable feature of the Designer is that it will let you update the model from the database to add additional database objects that you did not need earlier or that have been added to the database since you created the model.
1.5.2.1. The Entity Data Model Wizard One of the EDM design tools is the Entity Data Model Wizard. It allows you to point to an existing database and create a model directly from the database so that you don't have to start from scratch. Once you have this first pass at the model, you can begin to customize the model in the Designer. This first release of the Entity Framework is much more focused on creating models from existing databases. Although it is possible to begin with an empty model, it's much more challenging to create the model first and then wire it up to an existing database.
NOTE Frequently, developers ask about the possibility of generating a database from the model. The current version of the Entity Framework Design Tools (in Visual Studio 2008 SP1) does not have this ability. However, the next version, which will be part of Visual Studio 2010, will include this feature.
1.5.3. Managing Objects with Object Services The Entity Framework's most prominent feature set and that which you are likely to work with most often is referred to as Object Services. Object Services sits on top of the Entity Framework stack, as shown in Figure 1-6 , and provides all the functionality needed to work with objects that are based on your entities. Object Services provides a class called EntityObject and can manage any class that inherits fromEntityObject . This includes materializing objects from the results of queries against the EDM, keeping track of changes to those objects, managing relationships between objects, and saving changes back to the database. In between querying and updating, Object Services provides a host of capabilities to interact with entity objects, such as automatically working with a lower level of the Entity Framework to do all of the work necessary to make calls to the database and deal with the results. Object Services also provides serialization (both XML and binary). You will see this pattern used in Chapters Chapter 20 through Chapter 22.
Figure 1-6. The Entity Framework stack
1.5.4. Change Tracking Once an entity object has been instantiated, either as a result of data returned from a query or by instantiating a new object in code, Object Services can keep track of that object. This is the default for objects returned from queries. When Object Services manages an object, it can keep track of changes made to the object's properties or its relationships to other entity objects. Object Services then uses the change-tracking information when it's time to update the data. It constructs Insert, Update, and Delete commands for each object that has been added, modified, or deleted by comparing the original values to the current values of the entity. If you are using stored procedures in conjunction with entities, it will pass the current
values (and any original values specifically identified) to those procedures.
1.5.5. Relationship Management Relationships are a critical piece of the EDM, and in Object Services, relationships are objects. If a SalesPerson has two Order s, there will be one relationship object between theSalesPerson and the first order and another object representing a relationship between the SalesPerson and the second order. This paradigm enables the Entity Framework to have a generic way of handling a wide variety of modeling scenarios. But as you will find, especially in Chapter 15 , which dives deeply into relationships, this also requires that you have a very good understanding of how these relationships work. Some of the rules of engagement when working with related data are not very intuitive, and you can write code that will raise plenty of exceptions if you break these rules. Chapter 15 will provide insight into relationships in the EDM so that you will be able to work with them in an expert manner.
1.5.6. Data Binding You can use entity objects in many .NET data-binding scenarios. In Windows Forms, you can use entities as a data source for data-bound controls or as the data source for BindingSource controls, which orchestrate the binding between objects and UI controls on the form. Chapter 8 provides a well-informed walkthrough for using entities with BindingSource controls to edit and update data.Chapter 20 focuses on separating the data access and other business logic from the user interface to provide better architecture for your applications. Chapter 8 also provides a walkthrough for data-binding entities in Windows Presentation Foundation (WPF) applications. For ASP.NET, there is a newDataSource control called the EntityDataSource that works in a similar way to the
SqlDataSource and LinqDataSource controls, allowing you to declaratively bind entity objects to your user interface. Chapter 11 is all about using the EntityDataSource . For layered applications, Chapter 21 focuses on pulling all of the data access tasks out of the ASP.NET user interface.
1.5.7. EntityClient EntityClient is the other major API in the Entity Framework. It provides the functionality necessary for working with the store queries and commands (in conjunction with the database provider) connecting to the database, executing the commands, retrieving the results from the store, and reshaping the results to match the EDM. You can work with EntityClient directly or work with Object Services, which sits on top ofEntityClient. EntityClient is only able to perform queries, and it does this on behalf of Object Services. The difference is that when you work directly with EntityClient , you will get tabular results (though the results can be shaped). If you are working with Object Services, it will transform the tabular data created by EntityClient into objects. The tabular data returned byEntityClient is read-only. Only Object Services provides change tracking and the ability to save changes back to the data store.
1.6. The Entity Framework in Web Services You can use the Entity Framework anywhere you can use ADO.NET, including web services and WCF services. Chapter 14 walks you through the process of providing services for entities, andChapter 22 revisits WCF services using much of the knowledge you will gain in between the two chapters. At the same time the Entity Framework was released, another new technology called ADO.NET Data Services (which you may know from its original code name, "Astoria") was also released. ADO.NET Data Services provides an automated way to expose data through an EDM, a LINQ to SQL model, or other particular interfaces, to allow wide access to your data using HTTP commands such as GET and PUT . Although this is a great way to expose your data when you don't need to have a lot of control over how it is used, I won't cover this topic in this book. Here you will learn to write services that are designed more specifically for an enterprise.
1.7. What About ADO.NET DataSets and LINQ to SQL? The Entity Framework is only part of the ADO.NET stack.DataSet s and DataReader s are an intrinsic part of ADO.NET, and LINQ to SQL was part of the original release of Visual Studio 2008.
1.7.1. DataSets DataSets and DataReader s are not going away. All of your existing investment will continue to function and you can continue to use this methodology of retrieving data and interacting with it. The Entity Framework provides a completely different way to retrieve and work with data. You would not integrate the two technologies—for example, using the Entity Framework to query some data, and then pushing it into a data set; there would be no point. You should use one or the other. As you learn about the Entity Framework, you will find that it provides a very different paradigm for accessing data. You may find that the Entity Framework fits for some projects, but not others where you may want to stick with DataSets. The Entity Framework uses DataReader s as well as theEntityDataReader , which inherits the sameDbDataReader as SqlDataReader. This is what a query withEntityClient returns. In fact, you'll find that the code querying the EDM with
EntityClient looks very similar to the code that you use to query the database directly with ADO.NET. It uses connections, commands, and command parameters, and returns a DbDataReader that you can read as you would any other DataReader, such as SqlDataReader. Some ADO.NET tools that are not available with the Entity Framework are query notification and ASP.NET's
SqlCacheDependency. Additionally, ADO.NET's SqlBulkCopy requires a DataReader or DataSet to stream data into the database; therefore, you cannot do client-side bulk loading with the Entity Framework. The Entity Framework does not have an equivalent to ADO.NET's DataAdapter.BatchUpdate . Therefore, when the Entity Framework saves changes to the database, it can send only one command at a time. A few things are easier withDataSet s than with the Entity Framework, such as unit testing and change tracking across processes. You'll find a discussion of each of these in the following section.
1.7.2. LINQ to SQL LINQ to SQL and the Entity Framework look similar on the surface. They both provide LINQ querying against a database using a data model. Why did Microsoft create two similar technologies? LINQ to SQL evolved from the LINQ project, which came out of folks working with language development. The Entity Framework was a project of the Data Programmability team and was focused on the Entity SQL language. By the time each technology had come along far enough that it was being shown to other teams at Microsoft, it was clear that Microsoft had two great new technologies that could target different scenarios. The Entity Framework team adapted LINQ to work with entities, which confused developers even more because LINQ to Entities and LINQ to SQL look so much alike. LINQ to SQL eventually was brought into Microsoft's Data Programmability team, and in November 2008 the team announced that because the technologies target the same problems, going forward they would focus on developing the Entity Framework while maintaining and tweaking LINQ to SQL. This is not a happy situation for many developers who have made an investment in LINQ to SQL. Although Microsoft has made no statements regarding deprecating this great and fairly new tool, it has said that it will provide a migration path from LINQ to SQL to the Entity Framework and will recommend the Entity Framework over LINQ to SQL. The last chapter of this book will highlight some differences between the Entity Framework and LINQ to SQL that will be more comprehensible once you have some knowledge about the Entity Framework.
1.8. Entity Framework Pain Points This book is about the Entity Framework version 1, which Microsoft released in July 2008 as part of Visual Studio 2008 Service Pack 1. Microsoft has a big vision for the Entity Framework and has made an explicit choice to get as much as it can into the Visual Studio 2008 SP 1 release. Although the Entity Framework is an impressive technology with enormous flexibility, a lot of functionality is not exposed in discoverable and easy-to-use ways. Additionally, as with any technology, there are features that some developers find impossible to live without, and they will most likely wait until version 2 to begin to put the Entity Framework into production. This book spends a lot of time looking into the depths of the APIs to show you how to get around some of these limitations, and attempts to point out potholes, hiccups, and omissions.
1.8.1. The Entity Framework Designer The Designer goes a long way toward giving you a visual means of working with the EDM, but not every capability of the EDM is easy to achieve with the model, and instead may require some work in the raw XML. Although most would agree that the features you need in order to code manually are those that will be used less commonly, a few do stand out.
1.8.1.1. Stored procedures The Designer supports a narrow use of stored procedures. Using the Designer, you can override the Entity Framework's automatic generation of Insert, Update, and Delete commands by mapping an entity to a set of stored procedures with two important rules. The first is that the stored procedure must line up with the entity. For inserts and updates, that means the values for the stored procedure parameters must come from an entity's property. The second rule is that you have to override the Insert, Update, and Delete commands, or no commands at all, so you'll need to map all three functions. In addition, the Designer supports read queries as long as the query results map directly to an entity. If you have a query that returns random data, you will need to manually create an entity for it to map to. That's not too hard in the Designer, but there's another requirement that will necessitate doing some work in the XML. Chapter 6 walks you through the scenarios that the Designer supports easily, andChapter 13 digs into the scenarios that will take more effort and walks you through the necessary steps.
1.8.1.2. Unsupported EDM types The EDM has a very rich set of modeling capabilities, which I demonstrate in Chapter 12 . But the Designer does not support all of these advanced modeling techniques, requiring you to handcode some of them in the XML. In most cases, you can continue to work with the model in the Designer even though you won't see these particular model types, though you can leverage them in your code. However, there are a few model types, such as the very useful complex type, that, when included in the XML, will make it impossible to open the model in the Designer. The Designer is well aware of these limitations, and at least provides an alternative view that displays a message explaining why the model can't be opened. You'll learn about this in Chapter 13.
1.8.1.3. Generating a database from the model The EDM is based on a data-driven design with the assumption that there is an existing database for the model to map back to. This makes a lot of sense if you are building an application for an existing database. Domain-driven developers prefer to create their object model first and have a database generated from that. The current designer does not support this capability. However, model first development will be possible in the next version of the Entity Framework tools, which will ship in Visual Studio 2010. In the meantime, developers in the community and at Microsoft are playing with a code generator called T4 Templates (Text Template Transformation Toolkit) to read the model and generate SQL script
files to generate database objects for you.
1.8.1.4. A host of little things As more developers use the new tools, they are finding other things that could make their lives easier and have contributed to an MSDN Forum thread titled "V2 Wish List." You can find a link to this thread on the Resources page of this book's website.
1.8.2. Challenges with Change Tracking Distributed Applications To put it mildly, using the Entity Framework in distributed applications can be challenging when it comes to the change tracking performed by Object Services, because the change-tracking information is not stored in the entities and instead is maintained by a separate set of Object Services objects. When an entity is transferred across a process, it is disconnected from the object that contains its change-tracking information. Those objects that own the tracking data are not serializable, so they can't easily be shipped across to the new process along with the entities. Therefore, when the entities arrive at the new process, they have no idea whether they are new or preexisting, or whether they have been edited or marked for deletion. There's no way to simply use the ObjectContext 's default method for saving changes to the database without doing additional work. I address and dissect this problem in a number of places throughout this book, and provide a variety of coding patterns to help you succeed at moving entities around in distributed applications such as services or layered ASP.NET applications. Starting with Chapter 9 , which focuses on Object Services, many of the chapters throughout this book provide detailed information regarding change tracking and working with entities across tiers, whether you use the ASP.NET Entity Data Source, as in Chapter 11 , or write a WCF service with Data Transfer Objects (DTOs), as in Chapter 22.
1.8.3. Domain-Driven Development On the cover of this book, you may have noticed the phrase "Building Data-Centric Apps with the ADO.NET Entity Framework." Entity Framework version 1 is data-centric in the features it implements. Domain-driven development begins with the model, not the database. Many developers who embrace the tenets of domain-driven design will find the Entity Framework to be too restrictive. However, some of the advocates of this point of view are working with the Entity Framework team to enable version 2 to expand its capabilities so that you can use it with this approach.
1.8.4. Unit Testing Although it is possible to build unit tests with Entity Framework classes, the fact that entities must either inherit from the
EntityObject class or implement some of the key interfaces of Object Services makes it impossible to decouple the entity classes from the mechanism that executes queries. Therefore, you cannot unit-test your Entity Framework code without causing the database to be accessed. Hitting the database while performing unit tests is not a favorable option for most developers. Hopefully you wouldn't dream of testing against a live database, but even with a copy you would need to deal with rollbacks and an assortment of other complications. Because of this, many developers have just written the Entity Framework off as "untestable." I've seen developers implement unit testing so that their tests automatically create then remove a new database on the fly. Mocking is another path to overcome this limitation. This book will not delve into the specifics of unit testing with the Entity Framework.
1.9. Programming the Entity Framework As you read through this book, you will gain experience in designing EDMs and using the Entity Framework to write applications, as well as dig deep into the APIs to learn how to manipulate entity objects and have granular control over much of their behavior. A lot of functionality is very accessible, and there's a lot of hidden power. You will learn what's under the covers so that you can realize the true benefits of the Entity Framework.
Chapter 2. Exploring the Entity Data Model The Entity Data Model (EDM) is the bridge between your application and your data store. It allows you to work with a conceptual view of your data rather than the actual database schema. .NET APIs provided by the Entity Framework use the EDM for every interaction with the data store, whether it is to retrieve or to save data. The Entity Framework tools generate classes from the model that enable you to work with objects described by the model. In this chapter, you will create a simple EDM using the ADO.NET Entity Data Model Wizard, and then you will inspect the model both in the Designer and by looking at its raw XML. This chapter will stick to the basics of the model so that you can become familiar with how an EDM is structured and how the most common elements relate to one another, to your code, and to the database. This chapter contains a lot of information, but because the Entity Framework is built around the EDM, you'll appreciate having a solid understanding of the model as you work your way through this book. Chapter 12 will explore the more complex aspects of the EDM, such as its different inheritance capabilities and how to customize models so that they can better reflect your business logic.
2.1. Why Use an Entity Data Model? Well-designed databases can pose a problem for developers. In the data world, a database is designed for maintainability, security, efficiency, and scalability. Its data is organized in a way that satisfies the demands of a good database administrator, yet provides challenges for the developer who needs to access that data. In the early 1970s, Dr. Peter Chen devised the Entity Relationship Model (ERM). This allowed software designers to define a conceptual data model that better described their business domain, and then map that model back to the actual database schema. The ERM was a concept played out on whiteboards. Developers used the conceptual model as the basis for designing their business objects, and then continued to write a lot of code to move data from the database to the model and back again.
NOTE The Entity Data Model is a concept. The Entity Framework has a particular implementation that is realized as the EDMX file at design time. At runtime, the EDMX file is broken up into three separate XML files. For the sake of clarity, this book will simply refer to the EDM or Entity Data Model when discussing the Entity Framework's implementation. But keep in mind that the EDM literally refers to the concept of using some type of model to represent your entities in an application. The EDM follows this concept, but in the Entity Framework, it moves the modeling into XML files that different programming models can use. The primary XML file contains the conceptual model, which is the actual EDM. A second XML file contains a representation of the database and a third, the mapping between the first two. At design time, all three files are bundled into a single EDMX file. The build process splits the EDMX out into the three metadata files that are used at runtime. The Entity Framework then provides a framework that allows developers to write .NET applications based on this model.
2.2. The EDM Within the Entity Framework As long as the EDM provides the conceptual schema, a representation of the database, a mapping file, and access to an Entity Framework-aware ADO.NET provider for the target database, the Entity Framework doesn't care what database is being targeted. It provides a common means of interacting with the database, common query syntax, and a common method for sending changes back to the database. Although the Entity Framework provides a very rich set of features for developers, its most important capabilities are the following: It automatically generates classes from the model and updates those classes dynamically anytime the model changes. It takes care of all of the database connectivity so that developers are not burdened by having to write lots of code for interacting with the database. It provides common query syntax for querying the model, not the database, and then translates these queries into queries that the database can understand. It provides a mechanism for tracking changes to the model's objects as they are being used in applications, and handles the updates to the database. In addition, because the model's classes are dynamically generated, minor changes to the model need not have a major impact on your application. Furthermore, modifying the model is much simpler than modifying your objects and the data access code on which they rely.
2.3. Your First EDM The Entity Framework revolves around the EDM, and therefore the EDM is where we'll begin.
NOTE This walkthrough will use a custom SQL Server database, ProgrammingEFDB1, which you can download from the book's website. Visual Studio 2008 provides Entity Framework connectivity to SQL Server and SQL Server Express. As mentioned in Chapter 1 , you can install additional providers to connect to other databases, such as MySQL, Oracle, and VistaDB. Let's start by creating a model from the sample database, ProgrammingEFDB1. This is a simple database with only two tables, a view, and some stored procedures, and therefore it's a great place to begin. As your understanding of EDMs grows, we can work with a more realistic database.
NOTE The tools of the Entity Framework version 1 are optimized for building models from existing databases. In the next version, which will be part of .NET 4.0/Visual Studio 2010, the tools will provide the ability to perform model-first design , whereby you can build a model from scratch and then create a database based on the model. 1. Create a new Console Application project by choosing that template from either the Visual Basic or the Visual C# project type (see Figure 2-1).
Figure 2-1. Creating a new Console Application project
1. Add a new item to the project by right-clicking on ConsoleApplication1 in the Solution Explorer, clicking Add, and then clicking New Item. 2. Select ADO.NET Entity Data Model from the Templates list and click ADD (see Figure 2-2).
Figure 2-2. Selecting ADO.NET Entity Data Model on the Add New Item page to create an EDM
1. On the Choose Model Contents page, select the Generate from Database option and click Next. 2. On the Choose Your Data Connection page, select ProgrammingEFDB1 from the drop-down list of available connections and click Next.
NOTE If you do not have ProgrammingEFDB1 set up as a database connection in Visual Studio, click New Connection to open the Connection Properties dialog and create a new connection to the database. 1. On the Choose Your Database Objects page, check the Tables and Views nodes.
NOTE Skip over the Stored Procedures checkbox for now; we'll come back to stored procedures in Chapter 6. This will select all of the tables and views in the database. Alternatively, you can expand any of the nodes and select the specific objects you want. This database has two tables (Contact and Address), one view (vOfficeAddresses ), and four stored procedures (CustomersbyState, InsertContact , UpdateContact, and DeleteContact ).
For this demo, you'll want the tables and the view.
1. Click Finish. The new model will be displayed in the Designer window, and its file, Model1.edmx , will appear in the Solution Explorer (see Figure 2-3).
Figure 2-3. Model1.edmx added to the project, and the model automatically opened in the Designer
2.4. The EDM in the Designer Window The Entity Designer window is a useful way to view a graphical representation of an EDM and its members. The Designer display of Model1.edmx shown in Figure 2-3 depicts an EDM that consists of three entities: aContact entity, an Address entity, and a vOfficeAddresses entity. The first two came from the tables in the database and the third from a view. The Designer also displays a line connecting Contact and Address that represents a one-to-many relationship between the two. Each entity has a number of scalar properties, and the entities with relationships additionally have navigation properties.
What to What? The two ends of a relationship are often described with shortcut syntax that defines how many entities can be on each end. This is referred to as describing the multiplicity of the end. The multiplicity options are 1 (One), * (Many), or 0..1 (Zero or One). The two ends are then combined to describe the relationship. For example, "1:*" means "One to Many." The classic example of this is one order and its many details. "0..1:*" means "'Zero or One' to Many." An example of this is a relationship between shippers and orders. One shipper may ship many orders, but only one shipper can be related to an order. However, it's possible that the shipper was not assigned to the order at first; therefore, it can be zero or one on the shipper end of the relationship.
Scalar properties are properties whose values are literally contained in the entity. Navigation properties are pointers to related entities. TheContact entity has an Address property that will enable the application to navigate from a Contact to any Address es related to thatContact. The Address entity has a Contact property that allows you to navigate from an Address to the single Contact associated with theAddress. You may have noticed that there is no foreign key represented in either entity of Model1 . How is it possible to join the entities in a relationship? As you can see in Figure 2-4, the actual Address table in the database does, in fact, have a
ContactID field that is used to join the tables. The EDM replaces that foreign key with the navigation property. Deeper in the model's metadata, the ContactID still exists. You'll see how that works a little later in the chapter.
Figure 2-4. The schema of the Contact and Address tables in the database
When working in the Entity Designer, you can see more information about each entity and each of its properties in the Visual Studio IDE's Properties window. For example, Figure 2-5 displays the properties of theContact's FirstName property. Here you can see that FirstName is a string that cannot be null.
Figure 2-5. The properties of the FirstName property
2.5. Entity Properties Each entity and each association of an EDM, as well as the model itself, have properties. Let's change some properties of the Contact entity in the Model1 EDM that you've created. Select the Contact entity to view its Properties window (seeFigure 2-6).
Figure 2-6. Viewing the Properties window for the Contact entity
In the Properties window, you can see that the entity not only has the name "Contact," which it took directly from the table name in the database, but also has an Entity Set Name property. An Entity Set is a container for a collection of entities of a single type. Therefore, the entity set named "Contact" will contain a collection of Contact entities. The wizard applied the table name to both the name and the entity set name for the Contact table because it doesn't have the ability to automatically make names plural where it would make sense to do so.
2.5.1. Editing the Entity Set and Navigation Property Names For Model1 to make sense, you should pluralize names of properties that represent multiple entities, for example, the Entity Set Name properties: 1. Change the Entity Set Name for the Contact entity to "Contacts".
2. Change the Entity Set Name for the Address entity to "Addresses". The third change to make concerns the navigation properties. The navigation properties also automatically adopt the name of their associated entities. The Contact entity may have more than one relatedAddress entity. Therefore, it makes more sense for that navigation property to be named "Addresses" and not "Address". You can edit the name of the Contact's Address navigation property in its Properties window or directly in the Designer. To make the change in the Designer, select the Address property and then click it a second time. This will put the name in edit mode, as shown in Figure 2-7.
Figure 2-7. Editing the name of a property directly in the Designer by selecting the property and then clicking a second time
Now the Contact entity is more logical, and writing code against it will be much easier.
NOTE Cleaning up the entity, property, and association names is a step that you should consider performing immediately after you create a new model with the ADO.NET Entity Data Model Wizard. In this way, as you begin to code against the model, the names of the objects will be logical. Additionally, if you change these names after you have begun to code, you will have to modify your code to reflect the changes. Although you can do much more with the Designer, it is time to open the model in its raw format so that you can really understand how it works. Be sure to save all of your work before moving on.
2.6. The Naked Model: Inspecting the Model's XML Now it's time to get down and dirty with the EDM. Only a portion of the model is visible in the Designer, which means you can learn a lot more by looking at it in its raw format. In places where the model has a counterpart in the Designer, you'll see both views. By default, the file will open in the Designer; therefore, you need to use a different method to open it in its raw format. In the Solution Explorer, right-click the Model1.edmx file. From the context menu that opens, select Open With, and then choose XML Editor and click OK. Visual Studio cannot display the model in Design view and in XML at the same time, so you will see a message asking whether it's OK to close the Design view of the model. Click Yes. For those who have the commonFear-of-XML syndrome, this may look at little daunting at first. Have no fear and just follow along. Gaining an understanding of the model from this perspective will give you a big advantage when it's time to create more complex and powerful models.
2.7. A Less Daunting Model View The EDMX file is composed of two main sections: the runtime information and the Designer information. The runtime section comprises three additional sections: one each for storage models, conceptual models, and mappings. The Designer section specifies where the various model elements should be placed visually in the Designer. Collapse all of the main sections of the model. You can do this quickly by right-clicking in the XML and choosing Outlining, then Toggle All Outlining. Now you will see only the main node—edmx: Edmx . You can expand that until your view matches Figure 2-8.
Figure 2-8. The main sections of the model
Now you can see the main sections of the model. The edmx:Designer element is metadata that tells the Designer how to position the entities. Feel free to explore that at a later time. The critical sections of the model are the runtime conceptual models, the storage models, and the mappings.
2.8. The Three Parts of the Model So far in the Designer you have seen only the conceptual portion of the model, but there are two more pieces of the EDMX. These enable the Entity Framework APIs to translate between the conceptual model and the actual data store. The StorageModel represents the schema of the database, and the mappings describe how to get from the entities and properties of the conceptual model to the tables and columns described in the storage model (see Figure 2-9).
Figure 2-9. The components of the Entity Framework's EDM
Why use the storage layer to represent the data store when you have the actual data store to work with? There are a number of reasons to use this piece of the model. The most important reason is that this provides loose coupling to the database; not every object in the database needs to be in the model, and as you will learn in Chapter 13 , it is possible to customize even the store layer to suit the needs of the model. Although the entire model is contained in a single file at design time, when the project is compiled it will create three separate files—one for each of these sections. The conceptual layer is saved to a file with a .csdl extension, which stands for Conceptual Schema Definition Language. The storage layer is saved to a file with an .ssdl extension (which stands for Store Schema Definition Language) and the mapping layer is saved to a file with an .msl extension (which stands for Mapping Specification Language). These files are used at runtime, which is why they are contained in a section called edmx:Runtime in the model.
NOTE By default, you will never see these physical files because they are embedded into the project assembly when the project is compiled. This is convenient for a lot of scenarios, though it is possible to change the model's Metadata Artifact Processing property to read "Copy to Output Directory".
A Schema by Any Other Name: Nicknames The three parts of the model have a variety of descriptions that you will see used in documentation, articles, training, and even this book. Here is a list of the various "nicknames": Conceptual Schema Definition Language (CSDL) Conceptual layer Conceptual schema Conceptual model C-side
Store Schema Definition Language (SSDL) Store/storage layer Store/storage schema Store/storage model Store/storage metadata Store/storage metadata schema S-side Mapping Specification Language (MSL) Mapping layer Mapping specification C-S side (referring to "conceptual to store")
Each section is controlled by its own schema file that lives deep within the .NET Framework files. One schema file controls the CSDL, another controls the MSL, and yet another controls the SSDL. Visual Studio's IntelliSense uses these schema files to help you as you're working directly with the XML, pointing out errors and presenting you with options. Compiler errors will also be displayed if the files don't fall in line with their schema rules.
Schemas for the Schemas If you're among the very curious you can take a look at the schema files that drive the rules for the CSDL, SSDL, and MSL sections in the model. The schema files for Visual Studio 2008 are located in C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas . The three files to look for are: System.Data.Resources.CSDLSchema.xsd System.Data.Resources.CSMSLSchema.xsd System.Data.Resources.SSDLSchema.xsd If you open these in Visual Studio, you will see that they are formatted as XML files and are easy to navigate.
2.9. CSDL: The Conceptual Schema Let's begin by taking a deeper look at the CSDL, the conceptual schema for the EDM. In the XML, use the + icons to expand the ConceptualModels section until you have exposed the Schema and the EntityContainer , as shown in Figure 2-10.
NOTE Sometimes the XML formatting is affected and a particular section might lose all of its hard returns, resulting in one very long line of code that is hard to decipher. To fix this, highlight the line and then, from the Visual Studio menu, select Edit formatting much more palatable.
Advanced
Format Selection. This will make the XML
Figure 2-10. Expanding the conceptual model, its schema, and the EntityContainer inside the schema
Now we will walk through the exposed portion of the model to see what's what.
2.9.1. Schema The outer element, Schema , defines the name of the entire model's namespace, which in this case is ProgrammingEFDB1Model . The namespace is defined by default to have the name of the database from which the model is derived, plus the word Model. The schema also defines an Alias, which by default is Self . This is just a nickname for the model and you can name it anything you like. There is also an xmlns namespace URI, which defines the origin of Microsoft's schema file.
NOTE You can also see and modify the model's namespace in the model's Properties window when the model is open in Design view.
2.9.2. EntityContainer Within the schema is an EntityContainer named ProgrammingEFDB1Entities (by default). Like the namespace, this is the pattern for the default EntityContainer name using the database name plus the word Entities . You can view and change this name in the model's Properties window when you have the model open in the Designer. The EntityContainer is a wrapper for EntitySets and AssociationSets. You'll recognize the Contacts and Addresses EntitySets that you renamed earlier. AssociationSet s reference the associations between the entities. We'll come back to AssociationSets after we've discussed the Association elements. As shown in Figure 2-11, the EntityContainer is the critical entry point for querying the model. It exposes the EntitySets, and it is the EntitySets against which you will write your queries. The EntitySets, in turn, give you access to their entities.
Figure 2-11. The relationship of the EntityContainer to its EntitySets and Entity objects
2.9.3. EntitySet An EntitySet is a container for a type of entity. Its two attributes are Name and EntityType. EntityType defines which entity the set contains using its strongly typed name. The entity's strongly typed name includes the model's namespace, as shown in the following code snippet:
An EntitySet is sometimes referred to as a "wrapper" for an entity because it is through the EntitySet that you have access to the individual entities when querying against the model. When you begin to query in the next chapter, you will see that you use code that translates to "find some entities in the Addresses EntitySet." The model instructs your query to return Address entity types.
NOTE As you will learn later in the book, the Entity Data Model allows for inherited types. Therefore, your model may have a Contact entity and a Customer entity, where the customer is a type of Contact. In this case, the Contacts EntitySet will serve as a wrapper for both the Contact entities and the Customer entities.
2.9.4. EntityType An EntityType is a data type in the model. You have already seen a Contact entity type and an Address entity type. In the XML schema, expand the Address entity type, which will look like Example 2-1, to get a closer look at it. It contains a Key element and a list of Property elements.
Example 2-1. The Address entity's XML
2.9.4.1. The Key element The Key element defines which properties comprise the identity key for the entity. The entity's key plays a critical role in the life cycle of an entity, enabling your application to keep track of an entity, perform database updates and refreshes, and more. You will learn more about this in Chapter 9 . In the Designer, you can specify the key in the Properties window of the entity. The key for the Address entity uses only a single property, AddressID . It is possible to have keys composed of multiple properties. These are called composite keys and are similar to composite keys in databases.
2.9.4.2. The Property elements Take note of the Property elements. Not only do they have names, but also they are defined by their data type and a variety of "facets" that further describe them. The data types that define these properties are called simple types . These are primitive types in the Entity Framework object model that closely line up with the data types in the .NET Framework. The Entity Framework's primitive types, however, are used only to define the entity property. They do not have their own properties. They are truly simple. You can view and edit most of this information in the Properties window, as shown in Figure 2-12 . You can also see properties in the Properties window that are not shown in the XML.
Figure 2-12. The Address entity with its Street1 property selected and the Street1 details shown in the Properties window
NOTE
The Unicode, MaxLength, and FixedLength properties are not exposed in the Designer. In fact, the Entity Framework will completely ignore them. The attributes are used by other consumers of the EDM, such as ASP.NET Dynamic Data Controls. You can use them yourself when working at a lower level with Entity Framework, for example, with the MetadataWorkspace, which you will learn about in Chapter 15. Also, properties that are set to their default values are not explicitly written out in the XML. This is the case for a number of the properties of Address.Street1, including ConcurrencyMode, Default Value, Getter, and Setter. The EntityKey property is not a facet of Street1 but is used to create the EntityKey element described earlier. If you look at the properties of AddressID, you'll see that its EntityKey property is True.
NOTE The Getter and Setter properties define the scope of each property. By default, all of the properties are public, allowing anyone to read or write to them. Changing the values of Getter and Setter will impact the property declarations. You will explore these further in Chapter 17. Chapter 18 digs further into concurrency, and there you will learn about the ConcurrencyMode property.
Seeing the Schema Validation in Action To see a schema's schema rules in action, try editing the XML manually. For example, start entering a new element inside the . IntelliSense will provide a list of options within the property. Alternatively, break something! For example, change the spelling
Address EntityType
of an element name—perhaps change EntityType to ElephantType . The XML will provide visual clues to indicate that something is amiss, and the Errors List will list warnings regarding any invalid elements. Don't forget to undo these changes!
2.9.4.3. The navigation properties The navigation properties of the entities are tightly bound to the associations that are represented by the lines between the entities, as you saw earlier in Figure 2-3 . We'll dig further into the subject of navigation properties after we discuss associations.
2.9.5. Associations Associations define the relationships between entity types. The association doesn't define the relationship completely, however. It defines the endpoints (i.e., the entities that are involved in the relationship) and their multiplicity. In the example model, there is only one association, which is between the Contact entity type and the Address entity type, telling us that there is a relationship between the two. The name of this association was taken from the predefined relationship in the database when the wizard first created the model. Like any other element in the model, you can edit the name of the association if you prefer more readable names, or if you have naming conventions that you need to follow. Let's first look at the association's properties in the Design view. If you are following along, close the XML and open the model in the Designer. Figure 2-13 shows the Properties window for the association between Contact and Address.
Figure 2-13. Association properties
The association lists both ends. The first end is the Contact entity type, and again it is fully qualified using the model's namespace, ProgrammingEFDB1Model . It is assigned a role called "Contact", which acts as the name of an end so that elements elsewhere in the model can point to it. This end also tells us that there will be only one contact in a relationship between Contact and Address. The second end will be an Address entity type, and there can be many addresses in this relationship. After "reading" the association, you can see that there is a one-to-many relationship between Contact and Address . A single contact might have a home address, a work address, and even other addresses. However, an address will only ever be associated with a single contact. In the real world, it is possible for an address to be associated with multiple people—for example, family members or roommates or employees at a single organization. That would involve a many-to-many relationship, which we will explore in Chapter 7. As with entities, an association defines the name of the AssociationSet that contains this type of association. You could also make this name plural, but doing so is not as critical as pluralizing the EntitySet names because you won't be interacting with the AssociationSets in code. Example 2-2 shows the association in the XML, which provides the same elements as you see in the Properties window.
Example 2-2. The association between Contact and Address
The OnDelete element was inferred based on a constraint in the database that tells the database that if a Contact is deleted, all of its Address es should be deleted as well. We will discuss this in more detail later in this chapter, and in even greater detail in Chapter 15, which focuses on relationships and associations.
2.9.6. AssociationSet Along with EntitySets, the EntityContainer contains AssociationSets. Just as the EntitySet is a container for entity types, the AssociationSet is a container for an association. Therefore, it should not be surprising to see that the association in the following code snippet is a container for the FK_Address_Contact association:
Although it makes sense to have a container for an entity because you could have many contact entities to work with, how would there be a collection of associations? When you are working with entity objects, the associations between the entities are also objects. If you have a single contact with multiple addresses in memory, there would be one FK_Address_Contact association object for each relationship. Figure 2-14 shows two association objects that are used to define relationships between a single contact and two addresses.
Figure 2-14. Two association objects defining relationships between a single contact and two addresses
NOTE Notice that the XML description of the AssociationSet binds each endpoint of the association (by referencing the role) to theEntitySet . This now creates a thread through the model so that the APIs will be able to move through an EntitySet and discover various entities that are related to each other. When I first realized how the role was being used in the AssociationSet metadata, something finally clicked for me and I was able to see the big picture of the model. It was almost as though the last piece of the puzzle had just been put into place and I could see the clear image for the first time. I hope I've been able to bring you there.
2.9.7. NavigationProperty Finally, we can look at the navigation properties in the Address and Contact entity types. Now that I've explained associations, navigation properties should be much easier to comprehend. Although it is easily possible for entities to have more than one NavigationProperty , in this particular model we have only one in each entity type. Figure 2-15 shows the Properties window for the Contact NavigationProperty of the Address entity.
Figure 2-15. The Contact NavigationProperty of the Address entity
When you're working with the Address in code, the navigation property will appear as just another property. Although the other properties are referred to as "scalar" properties, meaning that they are values, the navigation property describes how to navigate to a related entity. A critical property of the navigation is its Association . This tells the navigation which association in the model contains information regarding how to navigate to the related entity (or entities in the case of Contact.Addresses). As I explained earlier, that association defines the relationship between the Address and Contact entity types. The FromRole and ToRole attributes tell the Entity Framework that when it looks at this association, it needs to navigate from the endpoint named Address to the endpoint called Contact. This, in turn, will allow you to navigate from the Address entity type to its associated Contact entity type in your code. As with entities, the Designer shows some properties that are used for code generation: Getter, Setter , and Documentation. Additionally, Multiplicity and Return Type are read-only properties located in the Designer's Properties window to help you better understand navigation. The Contact 's multiplicity is "1 (One)", telling you that when you navigate to Address.Contact , you will get an instance of a contact (a single contact object). A navigation property that returns a single object is referred to as a navigation reference. When looking at this same navigation property in the XML you will not see these read-only properties, and because the Setter, Getter, and Documentation properties are using the defaults, they are not listed either, as shown in the following code snippet:
2.9.8. Navigation Properties That Return Collections The Contact property of an Address entity returns a single instance of a contact. What about the Addresses property of a Contact entity? Figure 2-16 shows the Addresses property.
Figure 2-16. The Addresses NavigationProperty
When navigating from Contact to Addresses, the Addresses endpoint defined in the FK_Address_Contact association has a multiplicity of "* (Many)." Therefore, the Entity Framework expects a collection inside the Addresses property. In the Designer, the Return Type property of Addresses is a Collection of Address types. This type of navigation property is called a navigation collection. In code, the Contact.Addresses property will return a collection of Address entities, even if there is only one address in the collection. If there are no addresses for a particular person, the collection will be empty. The collection that is exposed in the Addresses navigation property is not a collection from the System.Collections namespace, but rather an EntityCollection . The is a completely unique class in the Entity Framework. So, although it is simpler to say "a collection of addresses," it is important to pay attention when you are working with an EntityCollection versus a type that inherits from System.Collections.ICollection , as they are not interchangeable. EntityCollection
2.9.9. Where Are the Foreign Keys? Remember that the ContactID column in the Address table in the database does not exist in the Address entity type. Where did it go, and how is the navigation possible with the missing foreign key? This is the last mystery concerning navigation properties, and we will solve it when we dig into the MSL portion of the EDM. Continuing with our simple model, it's time to look at another piece, the SSDL, which you will need to understand before we discuss the MSL. It is important to note that in this simple model, the conceptual layer has not been customized. It mirrors the schema of the database, which is a very good place to begin to learn about the EDM. Further on in the book, you will learn about customized models and begin to leverage the real power of the EDM.
2.10. SSDL: The Store Schema The StorageModels section of an EDMX file is a schematic representation of its associated data store. The elements of this file are similar to those of the CSDL file. Figure 2-17 shows the complete SSDL from the ProgrammingEFDB1 EDMX file, although not every section is expanded.
NOTE The EDM Design tools include a feature that allows you to update the model from the database. It is available in the context menu that you get when you right-click in the EDM Designer. You'll work with this feature in Chapter 6 to bring a database's stored procedures into the model.
Figure 2-17. Expanding the StorageModels section to explore the store layer of the model
For consistency, the tables and columns are called EntityType and Property . Frequently, you will see these referred to in documentation as tables and columns, and even as such in the visual tools. Although you are working with a relational database in this model, future versions of the Entity Framework will allow you to have different types of data stores—for example, an XML file. SSDL, therefore, uses terminology that will work for whatever type of relational data you may throw at it in the future. Note the following ways in which the SSDL differs from the CSDL: Schema element:
The namespace has ".Store" appended to it so that it's clear that this schema is for the data store, not the conceptual layer of the model. There is a ProviderManifestToken attribute. The value in the example represents the version number of SQL Server 2008, which is the database being used for this model. The Entity Framework uses this bit of information, so it is required. The values are determined by the provider that you are using (in this case, SqlClient ) and what values it exposes for the token. The xmlns namespace is a folder within the xmlns namespace used for the CSDL. Again, this particular parameter is static. Entity container: The name of the EntityContainer is "dbo," which was derived from the database schema name. Entity type: The entity type names are the actual names of the tables in the database. The property types are the provider data types—in this case, SQL Server data types. The identity columns are attributed with StoreGeneratedPattern="Identity" , meaning that the database will generate the values for these columns and they happen to be Identity columns. The other options are "None" (the default) and "Computed".
2.10.1. Association and AssociationSet You've seen the Associations and AssociationSet s in the CSDL, but they exist in the SSDL as well. In Example 2-3, the SSDL Association and AssociationSet have been expanded and you can see a pattern that is similar to that of their CSDL counterparts. The Association
's description displays how the database defines the primary key/foreign key relationship between Contact and Address.
Example 2-3. SSDL Association and AssociationSet
The generic term Association in the SSDL is referring to the primary key/foreign key relationship defined in the database. Figure 2-18 shows the origin of this in the database's Address table.
Figure 2-18. The primary key/foreign key relationship between Address and Contact in the database
The ADO.NET Entity Data Model Wizard infers AssociationSet s for any SSDL associations, which, like their CSDL counterparts, act as wrappers for the associations. The SSDL AssociationSet s will be used for mapping.
2.10.1.1. ReferentialConstraint The ReferentialConstraint element serves a number of purposes. It specifies the direction of the relationship using the Principal and Dependent role elements. In the example, Address is dependent upon Contact . This also translates to defining the primary key/foreign key relationship, and we finally see the foreign key in the Address
table identified: it is the ContactID . This is another piece of the puzzle of how the association and the navigation property work in the conceptual model. The
ContactID
property doesn't exist anywhere in the CSDL, but it is specified here in the SSDL. The MSL will show us how they are linked.
The last purpose of the ReferentialConstraint element is to stipulate that a row in the Address table cannot exist without a reference to a row in the People table. This rule exists in the database, but because of the ReferentialConstraint , the Entity Framework will also check this rule. The Entity Framework APIs will check to see whether the data passes this rule before any attempt is made to send the data to the database. If your code creates an address without associating it with a person and then tries to save this change to the database, the data will fail the constraint check. You will learn more about this later in the book, but for now it is important to understand that this won't happen at the time when the address is created in code. The check happens when the code attempts to save changes back to the database. Other rules and constraints in the database can be described in the store schema, such as noting whether the database will perform a cascading delete. If you check back at the CSDL's association in Example 2-2 , you will see that this ReferentialConstraint doesn't exist. The CSDL enforces that constraint in a different way. The multiplicity for the Person entity type in that relationship is "1", not "0..1". As noted earlier, "1" means "one and only one," whereas "0..1" means "zero or one." If application code attempts to add an address that is not attached to a person, an exception will be thrown. You will learn more about the ins and outs of constraints and validation in Chapter 15.
2.11. MSL: The Mappings The last section of the model to look at is theMappings section. In the EDM, the mapping layer sits between the conceptual and store layers and not only provides the map from the entity properties back to the tables and columns in the data store, but also enables further customization of the model. You can view the mappings in the Designer's Mapping Details window. To follow along, close the XML view of the model and open the model in the Designer by double-clicking the model's EDMX file in the Solution Explorer. To see the Mapping Details window, right-click the Contact entity and select Table Mapping from the menu. You can dock the window if you do not want it to float on top of the Designer. The Contact 's mapping information will be displayed in the Mapping Details window, as shown in Figure 2-19.
Figure 2-19. The Contact's mapping information as displayed in the Mapping Details window
Figure 2-19 shows visually how the Contact entity maps to the Contact table in the store layer. This is defined by "Maps to Contact", which refers to the table name in the SSDL. In other words, the Contact entity maps to the Contact table. Because the Contact entity has not been customized, the mapping is straightforward—there is a one-to-one mapping between the conceptual layer and the store layer. Beneath the table selection for the Contact table, you can see that the columns from the table (on the left) are mapped to the entity's properties on the right. By default, the Designer will match identical names to each other, which is a great help. You can also see that the columns include the provider type ( int , nchar, and datetime from SQL Server), whereas the properties include the Entity Framework's primitive types ( Int32, String, and DateTime). You can use the Add a Condition and Add a Table or View placeholders to further customize the model. We will cover this subject in Chapter 12. Open the model in the XML Editor again and expand the section; you'll see that there is one big difference in how the mapping is described under the covers. The mapping, as shown in Example 2-4 , is being made from the EntitySet , not the actual entity. When you add inherited types into the mix, you may also be mapping Customer s who are a type of Contact . When you map the EntitySet you cover all of the entity types in an inheritance hierarchy. Therefore, the mapping needs to be done to the EntitySet , not a specific entity.
Example 2-4. The XML view of the Contact entity mapping to the Contact table
2.11.1. The MSL Elements Let's back up a bit and take a look at the Mappings section before drilling deeper into the mapping for the Contact entity. We've inspected the CSDL and SSDL already, so you'll notice that the MSL has elements that look familiar, though they are specific to mapping.
2.11.1.1. Mapping The first thing to notice is that the parent element isn't a schema but a mapping, with its own xml namespace and a Space attribute of C-S , telling you that it is mapping from C (the conceptual layer) to S (the store layer). You'll find that when discussing mapping, authors and presenters will use the terms C-side and S-side . They are referring to the conceptual end of the mapping and the store end of the mapping, respectively.
2.11.1.2. EntityContainerMapping All of the mappings are within the EntityContainerMapping element. This element describes which SSDL and CSDL will be used for the mapping by identifying the same container names that the SSDL and CSDL listed previously: ProgrammingEFDB1ModelStoreContainer and ProgrammingEFDB1Entities . Next, the mappings are grouped by EntitySets and AssociationSet s. Because this is a very simple model, the mappings are easy to read.
2.11.1.3. EntitySetMapping You had a look at the EntitySetMapping already. Its elements are described as follows.
2.11.1.3.1. EntityTypeMapping The mapping for each EntityType is defined in an EntityTypeMapping element that defines which EntityType is being mapped. Note the syntax used: IsTypeOf() with the fully qualified name of the type. IsTypeOf is a .NET Framework method. In this particular example, IsTypeOf is not actually required—the model would have the same meaning if the TypeName value was simply ProgrammingEFDB1Model.Contact. Chapter 12 provides examples of entity inheritance and shows how IsTypeOf can impact the meaning of mappings.
2.11.1.3.2. MappingFragment The EntityType mapping element contains a MappingFragment . This doesn't seem logical with our example, but as you will see in Chapter 12 , it is possible to do something called entity splitting whereby one entity is composed of properties that map to columns in multiple tables. In that case, each table that you are mapping to will be represented within a single MappingFragment. The StoreEntitySet refers to the EntitySet for the table listed in the SSDL.
2.11.1.3.3. ScalarProperty The ScalarProperty mappings map the property name of the entity type in the CSDL to the column name of the table. In the case of our simple model, the property names and the field names are identical, which makes this particular mapping pretty straightforward.
2.11.1.4. AssociationSetMapping In the AssociationSetMapping , we can finally see how the associations and navigation properties work with respect to the "hidden" foreign keys. As the EntitySetMapping s are used to map entities, AssociationSetMapping s are used to map associations. Figure 2-20 displays the MappingAssociation for the CSDL's FK_Contact_People association in the Designer. You can open this by right-clicking on an association in the Designer and selecting Table Mapping.
NOTE If the Mapping Details window is already open, it is not necessary to right-click objects to see their mappings. Just select the object (entity or association), and its mappings will appear in the already visible window.
Figure 2-20. The association's mappings in the Designer's Mapping Details window
The AssociationSetMapping maps the FK_Contact_People association in the CSDL to the Address table in the SSDL ("Maps to Address"). Remember that the association is defined as having the Contact entity on one end of the relationship and the Address entity on the other end.
NOTE It might be helpful to remember that we made the EntitySet names plural in the CSDL but not in the SSDL. The SSDL EntitySet names match the entity names exactly because both types of names are derived directly from the database table names. The mapping then specifies that the ContactID from the Contact end of the relationship (this refers to the name of the role in the association) maps to thecontactID column in the Address table. So, the big mystery is finally revealed! You can see that the mapping wires up the navigation from the Contact entity to the Address entity even though there is no ContactID
property in the Address entity.
Although the ContactID enables the mappings to find all of the addresses for one person, we also need to map the AddressID back to the addressID in the table so that we can discern one address record from another. As you can see in Example 2-5 , the behind-the-scenes XML doesn't reveal much more.
Example 2-5. AssociationSet mapping
There's more to come on associations and mappings in Chapter 15.
2.12. Database Views in the EDM One type of database object that we haven't explored yet in the EDM is the database view. The wizard pulled one database view into the model. Database views are seen by the EDM as read-only, so the entity created from this view is, by default, also read-only. To the EDM, "read-only" means the Entity Framework doesn't have the ability to automatically build Insert, Update, and Delete commands. However, it is possible to use stored procedures to make these entities updatable. In Chapter 6 , you'll learn how to map stored procedures to entities. Nothing in the Properties window of thevOfficeAddresses entity indicates that the database view is read-only. If you were to dig through the model, you would find that something in the SSDL, called a DefiningQuery , contains the T-SQL from the database that defines the view.
NOTE When you originally built the model with the EDM wizard, you may have seen some warnings in the Error Window about this view. The wizard will discover that there is no primary key for the view and will infer an entity key from any non-nullable properties in the entity. The warning message informs you of this and the same message is embedded into the EDMX file. Chapter 13 will dig more deeply intoDefiningQuery . For now, remember that the view comes into the model as an entity and is read-only by default, but you can update it through function mappings. That will carry you far.
2.13. Code Generation from EDM to Classes The Entity Framework automatically creates a set of classes from the model. These classes are what you will work with when you query the model, and objects will be returned that are based on these classes. Each time a change is made to the model and the model is then saved, the Entity Framework's code generator kicks in and the classes are re-created. You will get many opportunities to learn about and inspect these classes throughout the rest of the book. You will also learn how to customize these classes and you'll learn about customized code generation.
2.14. Summary This chapter introduced you to the Entity Data Model and to a bit of the functionality of the Design tools. You created your first EDM and looked under the covers to gain an understanding of its most common components. You made some minor customizations and explored the mappings in the Designer and in the raw XML. As explained previously, the EDM shines when you can begin to take advantage of the fact that it is highly customizable. Now that you have a solid understanding of the EDM, you are prepared to learn about advanced mappings and customization, which we will explore in Chapter 12 . But for now, this model provides enough to get started with querying, which you will begin in the very next chapter.
Chapter 3. Querying Entity Data Models You can query Entity Data Models in a variety of ways. Some ways you will choose for personal preference and others you will choose so that you can leverage particular benefits. In this chapter, you will finally get to write some code and retrieve some data by writing queries using the two query syntaxes that the Entity Framework provides: LINQ to Entities and Entity SQL. In addition, you will learn about query syntax versus method-based syntax, including the Entity Framework's query builder methods. By the end of the chapter, you will have gained a high-level understanding of all of the query options and their basic uses. In further chapters, you will write more complex queries; the foundation you will receive from this chapter will make that task much easier. In addition, at the end of this chapter you'll find an extremely important section on query execution. Understanding the possibility of inadvertently executing a query in your Entity Framework applications can help you to avoid performance problems caused by your application unknowingly and unnecessarily making calls to the database.
3.1. Query the Model, Not the Database Here is where you will experience the difference between writing queries against a data model rather than the database. In this chapter, you will learn how to construct queries against the EDM that you created inChapter 2, and you will learn to let the Entity Framework take it from there. The Entity Framework will process your queries and will leverage the ADO.NET provider—in this case, System.Data.SqlClient —to turn the EDM query into a query the target database will comprehend. After the database has executed the query, the results will be turned into objects that are based on the entities in the model. These returned objects are an important piece of the querying process, but surely you want to start querying, so first we'll query and then we'll take a peek under the covers.
3.2. Your First EDM Query In Chapter 2 , you created an EDM inside a Console Application. Here you'll create your first queries in that same project, so if you've closed it, open it and let's get started. The code in this section will execute the simplest form of a query, which happens to be a shortcut to a ObjectQuery , and then will display the results in a console window. 1. Open the Module1.vb or Program.cs file. 2. Add the method in Example 3-1 beneath the Main method. IntelliSense will assist you as you type. After you've written a few basic queries, you'll make the code a little more efficient.
Example 3-1. Querying Contacts and writing out their names
Private Sub QueryContacts() Using context As New ProgrammingEFDB1Entities Dim contacts = context.Contacts For Each contact In contacts Console.WriteLine("{0} {1} {2}", _ contact.Title.Trim, contact.FirstName.Trim, _ contact.LastName.Trim) Next End Using Console.Write("Press Enter...") Console.ReadLine() End Sub
static private void QueryContacts() { using (var context = new ProgrammingEFDB1Entities()) { var contacts = context.Contacts; foreach (var contact in contacts) { Console.WriteLine("{0} {1} {2}", contact.Title.Trim(),contact.FirstName.Trim(), contact.LastName); }
} Console.Write("Press Enter..."); Console.ReadLine(); }
3. Add the following code into the Main method:
QueryContacts
QueryContacts();
4.
Press F5 to run this bit of code. When the code hits the ReadLine() method, all of the names are listed in the console window. You have just executed your first query against an EDM and seen the objects that result.
5. Press the Enter key to finish running the app. Now you'll run the query again, but this time you'll look at some of what's going on: 1.
Set a breakpoint at the end of theFor Each/foreach block. For VB the breakpoint isNext , and for C# it's the closing brace (}).
2.
Press F5 to run the code again.
3.
When the debugger reaches the breakpoint, hover your mouse pointer over the wordcon and you will see that it is a Contact entity (see Figure 3-1).
Figure 3-1. The query results containing Contact entities at runtime
1. Next, hover your mouse pointer over the word contacts in that same statement and you'll see that its type is a
System.Data.Objects.ObjectQuery of Contact types. System.Data.Objects is the Entity Framework's API for creating and managing entity objects. The ObjectQuery is what the Entity Framework uses to construct and execute queries that will return objects. Once the ObjectQuery has been executed, it contains results, which were all of the contacts you saw listed in the console. Because you asked only for the EntitySet and did not request any filtering, all of the contacts were retrieved from the database when the query was executed. Although this doesn't really look like a query, it is—it's just a very simple one. You'll take a closer look at this after the next query. 2.
You can continue the application or stop it by pressing Shift-F5.
3.2.1. A More Query-Like Query The preceding query used a shortcut that produced a query for you. But it didn't really feel like a query. Now you'll write an actual query using LINQ to Entities. Remove the breakpoint that you set in the previous steps. In the line of code that created the contacts memory variable, replace context.Contacts with the query in Example 3-2, which retrieves a subset of the contacts.
Example 3-2. A LINQ to Entities query
Dim contacts=From c In context.Contacts Where c.FirstName = "Robert"
var contacts = from c in context.Contacts where c.FirstName == "Robert" select c;
NOTE You'll find many differences between VB and C# syntax when writing LINQ queries. Besides the casing, notice that VB does not require that you explicitly use the Select operator, whereas C# does. Run the application again and you will see that only a small number of contacts are listed and they all have Robert as their first name.
3.2.2. Where Did the Context and Classes Come from? Since you just dove right into the code, you might have a few questions. For instance, where did the Contact type come from? How did you go from an XML file (the EDMX file) to strongly typed .NET objects? Why is context.Contacts a query, and what is that context anyway? One of the features of the EDM Designer tools is that the Designer automatically performs code generation based on the model. If you set the Solution Explorer to Show All Files, you'll see an extra code file attached to the model, as shown in Figure 3-2.
Figure 3-2. An extra code file attached to the model
Expand the .edmx file in the Solution Explorer to see the generated code file. Open the file to see what's in there.
NOTE Because the file is generated automatically, you don't want to edit it directly. You'll learn how to customize the classes in this file in Chapter 10. The generated code file contains four classes. Figure 3-3 shows these classes in Visual Studio's Class Designer view. The first is ProgrammingEFDB1Entities , which has taken the model'sEntityContainer name. The others are for each entity—Address, Contact, and vOfficeAddresses.
Figure 3-3. The four classes in Visual Studio's Class Designer view
3.2.2.1. The ObjectContext class, ProgrammingEFDB1Entities When you looked at the XML view of the model inChapter 2, you saw an EntityContainer that contained theEntitySets and AssociationSet s. The ProgrammingEFDB1Entities class represents that EntityContainer and inherits from an Entity Framework type called ObjectContext. This is whycontext is used for the variable in the example.ProgrammingEFDB1Entities has three properties—Addresses, Contacts, and vOfficeAddresses—which are the EntitySets defined in the model. The three AddTo methods were created by the code generator to support the entity classes and theObjectContext itself. Looking more closely at theContacts property, you can see that it returns anObjectQuery of Contact types:
Public ReadOnly Property Contacts() As _ Global.System.Data.Objects.ObjectQuery(Of Contact)
public global::System.Data.Objects.ObjectQuery Contacts
This is why context.Contacts in the first example is a query even though you didn't write any query-specific code.
3.2.2.2. The entity classes The three entities defined in the model are the source for the three entity classes. Each class inherits from the Entity Framework's EntityObject class and has properties based on the properties defined in the model, including the
Contact.Addresses and Address.Contact navigation properties (seeFigure 3-4).
Figure 3-4. The entity classes in the Class Designer
But there's something new in there, ContactReference, which is another way to access theContact property. You'll learn more about Reference properties in detail in Chapter 15 . These classes have more members, but as they are not relevant to the querying you'll do in this chapter, we will dissect them later in the book. Dig deeper: don't be afraid to poke around in the generated code file, but remember that any changes you make will be overwritten anytime the model is modified and saved.
3.3. LINQ to Entities Queries The LINQ to Entities query syntax is easier to learn and to use than Entity SQL, and possibly already familiar to you if you have been using LINQ elsewhere in Visual Studio 2008. LINQ to Entities will very likely cover a large portion of your query needs. We'll start with this first. LINQ is a new language enhancement that was added to Visual Basic and C# in Visual Studio 2008. LINQ stands for Language INtegrated Query, and LINQ to Entities is one of its implementations.
NOTE LINQ was originally written to query in-memory CLR objects, but there are now many implementations of it. You just used an implementation created to work with entity objects. Visual Studio 2008 also includes LINQ to SQL, an implementation that queries directly against SQL Server databases. Many third parties are also writing LINQ implementations. It is possible to get very creative with LINQ queries, and you will easily find a number of books devoted entirely to LINQ. When you're starting out it's helpful to understand the basic structure. The query you wrote in Example 3-2 is a LINQ to Entities query.The most obvious sign of integration in LINQ queries is that as you typed your query, you had the benefit of IntelliSense assisting you—for example, providing LastName as an option for the c variable. That was because when you identified the Contacts EntitySet at the beginning of the query, the compiler was able to determine that the items in that collection are Contact items. When you typed c later in the query in the SELECT and WHERE clauses, IntelliSense was able to present a list of Contact properties in the IntelliSense suggestions.
Why Does LINQ Start with FROM? LINQ queries begin with the FROM clause, rather than the SELECT clause that most of us are familiar with in other query languages. When LINQ was being created, query statements did begin with SELECT . However, the developers at Microsoft quickly realized that identifying the type that is being used up front enabled IntelliSense to provide meaningful suggestions as the rest of the query was constructed. According to Microsoft's Y. Alan Griver, who was very involved with the LINQ project during its early stages, the Microsoft developers jokingly referred to this syntax as "Yoda speak" when they altered the syntax for the sake of IntelliSense.
In the query, c is just a random variable name that lets you reference the thing you are working with further on in the query. It's referred to as a control variable . The control variable provides another means by which IntelliSense and the compiler are able to make LINQ more powerful for developers.
3.3.1. ObjectQuery and LINQ to Entities You've seen that ProgrammingEFDB1.Contacts returns an ObjectQuery of Contact types. This is evident in the generated code as well as at runtime. There is a small twist on this that you may have noticed. At design time, when you hover your mouse pointer over the contacts variable that the LINQ to Entities query returns, the DataTip doesn't say that contacts is an ObjectQuery. Instead, it says that contacts is an IQueryable. is a LINQ query type. At design time, the compiler recognizes the LINQ query and does its best to tell you its return type. The compiler doesn't realize that because it is a LINQ to Entities query, it will be processed by the Entity Framework and will result in an ObjectQuery. ObjectQuery implements IQueryable , so the two IQueryable
are very closely related. IQueryable
contains metadata about the query, such as the query expression and the provider being used. ObjectQuery is an IQueryable with additional query details
that are specific to Entity Framework queries. At runtime, you'll see evidence of this again if you open the contacts query variable in the QuickWatch window (see Figure 3-5).
Figure 3-5. ObjectQuery implementing LINQ's IQueryable
Once an IQueryable (and an ObjectQuery because it implements IQueryable ) has been executed, it contains its query metadata as well as the new query results.
NOTE Don't let this confuse you when LINQ to Entities displays its type as IQueryable in some places and ObjectQuery in others. The results are described as an "enumerable type," based on the class IEnumerable, which is similar to a Collection. An IEnumerable allows you to enumerate or iterate through each item in the collection as you did in the preceding code sample (i.e., in For Each/foreach ). A Collection is an enhanced IEnumerable. Whereas an IEnumerable is read-only, the more familiar Collection class allows you to perform additional actions, such as adding or removing items from the group.
Terminology: Collection Versus IQueryable and IEnumerable It is important to be familiar with the terms IQueryable and IEnumerable because they are used frequently when discussing LINQ (not just LINQ to Entities) and Entity Framework queries. Although the phrase "this query returns a collection" is easier for developers to understand, the phrase "this query returns an IQueryable/IEnumerable" is more technically correct.
3.4. Entity SQL Queries That Return Objects A LINQ to Entities query implicitly creates an ObjectQuery , whether written in Visual Basic or C#. There is one other way to create an ObjectQuery and that is by using the Entity Framework's Object Services (in the System.Data.Objects namespace) directly. When you create an ObjectQuery directly, you will use the Entity Framework's T-SQL-like query language, called Entity SQL, to build the query expression. To see how this works, modify your example with the following steps: 1.
Replace (or comment out) the line of code containing the LINQ to Entities query with the code inExample 3-3.
Example 3-3. Querying with Entity SQL
Dim qStr = "SELECT VALUE c " & _ "FROM ProgrammingEFDB1Entities.Contacts AS c " & _ "WHERE c.FirstName='Robert'" Dim contacts = context.CreateQuery(Of Contact)(qStr)
var qStr = "SELECT VALUE c " + "FROM ProgrammingEFDB1Entities.Contacts AS c " + "WHERE c.FirstName='Robert'"; var contacts = context.CreateQuery(qStr);
2. Hover your mouse pointer over the word contacts and you will see that at design time it is an ObjectQuery (Of Contact) . 3.
Run the app again and the results will be the same as before.
3.4.1. Why Another Way to Query? Why would you need another means of querying the EDM in addition to LINQ to Entities? The main reason lies in the use of the string-based query that you passed into the CreateQuery method. If you have constructed SQL queries before, this syntax looks familiar but not quite right. This is the specialized query language for the Entity Framework, and it's called Entity SQL. The return type of this query at design time is an ObjectQuery (Of Contact) , not IQueryable. An ObjectQuery has methods and properties that are not available to an IQueryable
. But as you will learn later in this book, because the IQueryable inherits from ObjectQuery , it is possible to cast the IQueryable to an ObjectQuery and then
access those properties and methods. This means that even if you choose to use LINQ to Entities, you will still get to benefit from these properties and methods.
3.4.2. Entity SQL Entity SQL was actually the first syntax devised for querying entities. LINQ was being developed as a language extension by the VB and C# language teams, and eventually it became obvious that LINQ would be a fabulous addition to the Entity Framework, which is how LINQ to Entities came to be. Entity SQL has its roots in SQL because it makes sense to start with something that is well known. However, because entities are different from relational data, Entity SQL deviates from SQL to provide the necessary capabilities for querying the EDM.
How Is Entity SQL Different from T-SQL? The Entity Framework documentation has a topic called "How Entity SQL Differs from Transact-SQL." It provides a list of differences with an extended explanation for each difference. For example, Entity SQL supports the inheritance and relationships found in an EDM, whereas in T-SQL you must use joins to work with relationships. Databases do not even have the concept of inheritance; therefore, T-SQL doesn't support that either.
Looking more closely at the Entity SQL query string you built earlier, you'll notice that, like LINQ to Entities, it defines a variable for use in the query: c . In LINQ this is referred to as a control variable , but in Entity SQL it is just called a variable. Figure 3-6 deconstructs the query string without the WHERE clause. The variable is defined using the AS keyword and is referenced in the SELECT clause. The VALUE
keyword specifies that you want to return a collection of single items; in this case, it will be Contact entities.
Figure 3-6. Deconstructing a simple Entity SQL query
The VALUE clause is required if you are selecting a single item, which can be an entity, a single property, or even an entity collection, as shown in the following code snippet: SELECT VALUE c FROM ProgrammingEFDB1Entities.Contacts ... SELECT VALUE c.FirstName FROM ProgrammingEFDB1Entities.Contacts ... SELECT VALUE c.Addresses FROM ProgrammingEFDB1Entities.Contacts ...
If you are selecting multiple items, you cannot use VALUE, as shown here: SELECT c, c.Addresses FROM ProgrammingEFDB1Entities.Contacts .... SELECT c.LastName,c.Title FROM ProgrammingEFDB1Entities.Contacts ...
If you forget to use VALUE, an InvalidOperationException will be thrown at runtime telling you that .NET is unable to cast a System.Data.Object.MaterializedDataRecord to the type that you specified in the ObjectQuery. If you include VALUE with multiple items, an EntitySqlException will be thrown that specifically tells you the following: "SELECT VALUE can have only one expression in the projection list."
It will even tell you the line number and column number of the problem.
NOTE Chapter 18 delves more deeply into Entity Framework exceptions. Without the VALUE clause, the results will be wrapped in rows and you will have to dig into the rows and columns to get at the data. Similar to the LINQ query, you are selecting FROM a collection. In this query, that collection is the entity set, Contacts; but it is necessary in Entity SQL to specify the EntityContainer as well. Again, c is a random variable name the query used to represent contact items within the Contacts entity set. The WHERE clause in the Entity SQL uses SQL-like syntax, as in the following: WHERE c.FirstName='Robert'
eSqlBlast: Entity SQL Query Helper I could not have figured out some of these more complex Entity SQL queries without the help of a tool called eSqlBlast, written by Zlatko Michailov (in his free time), who was the original program manager for Entity SQL. The tool is available on MSDN's Code Gallery, along with a number of other tools for the Entity Framework, at http://code.msdn.com/adonetefx/ . With eSqlBlast, you can test queries and receive immediate feedback, whether it is an error message describing a problem with the query, or the results along with the generated SQL for the database. I have used the tool in its early stages and look forward to seeing it evolve.
3.4.2.1. Entity SQL canonical functions The Entity SQL language is very robust and offers a lot of functionality. Although it would be impossible to cover all of the operators and functions the language supports, you will see many of them used throughout this book and you can get the full list by looking at the Entity SQL documentation in the MSDN Library. Entity SQL supports a large set of canonical functions , which are functions that all data providers should support. It also enables data providers to include their own specific functions. The .NET Framework provider for SQL Server, written by Microsoft, offers approximately 75 specific functions that you can use in Entity SQL queries when the target database is SQL Server; some of these overlap with the canonical functions. The provider additionally provides the primitive types and their facets as well as the internal logic for mapping between the EDM and SQL Server. Other providers that are written for the EDM will have their own lists of additional functions and features that are supported.
NOTE If you are familiar with T-SQL, you'll be happy to know that one of the canonical functions is Trim() , which means you won't have to use the silly LTRIM(RTRIM()) combo anymore.
3.4.3. The Parameterized ObjectQuery ObjectQuery
allows you to create parameterized queries. Similar to other query languages, you use an @ placeholder in the string, and then define its value in a
parameter. To use a parameterized query, you need to explicitly instantiate an ObjectQuery, rather than using the CreateQuery method of the ObjectContext . Using this method, you also need to pass the ObjectContext as a parameter when you instantiate an ObjectQuery. Then you add parameters to the ObjectQuery prior to execution. To see how this works, you can rewrite the query you've been writing to enable dynamic changes to the query, like this:
queryString = _ "SELECT VALUE c FROM ProgrammingEFDB1Entities.Contacts AS c " & _ "WHERE c.firstname=@firstName"
Dim contacts As New ObjectQuery(Of Contact)(queryString, context) contacts.Parameters.Add(New ObjectParameter("firstName", "Robert"))
queryString = "SELECT VALUE c FROM ProgrammingEFDB1Entities.Contacts AS c " + "WHERE c.firstname=@firstName";
var contacts = new ObjectQuery(queryString, context); contacts.Parameters.Add(new ObjectParameter("firstName", "Robert"));
Although it may seem tempting, you cannot use parameters to replace property names in the query string. In other words, if you tried to create the Entity SQL string SELECT @myproperty FROM ProgrammingEFDB1Entities.Contacts AS c and you created a parameter that set @myproperty to c.LastName , the SQL that results would look like this: SELECT 'c.LastName' FROM ProgrammingEFDB1.Entities.Contacts AS c
This is invalid SQL and will throw an error.
ObjectParameter
s do not allow you to specify the length of the parameter. As with other methods of querying the Entity
Framework, this causes inefficiencies with your database's query plan caching. I will discuss this problem further in Chapter 16.
3.5. Method-Based Syntax Queries for LINQ and Entity SQL So far, the LINQ to Entities and Object Services queries you have seen have been written as standard query expressions. Both LINQ to Entities and Object Services provide a way to write queries as methods, rather than as operators and functions (as in LINQ) or as a string (as in Entity SQL). Each query language has a method syntax that you can use, but each exists for opposite reasons. The C# and Visual Basic implementations of LINQ sit on top of query methods in .NET 3.5. Your LINQ expressions are translated into these query methods, but you can use them directly if you like. The Entity Framework processes Entity SQL directly; however, a method-based syntax is available that will construct Entity SQL expressions for you.
3.5.1. LINQ Method-Based Queries Although Visual Basic and C# understand LINQ syntax, the CLR does not. One of the first things to happen when .NET processes LINQ queries is that it translates the query into a set of method calls on the collection being queried. All of the standard query operators (WHERE, SELECT, JOIN , etc.) have associated methods in .NET 3.5. You can write your queries using the method syntax directly, if you prefer. Many developers do happen to prefer this, although many others would rather use the expression syntax. The MSDN documentation says, "In general, we recommend query syntax because it is usually simpler and more readable; however, there is no semantic difference between method syntax and query syntax." Therefore, using one over the other is a matter of style and personal choice. To write method-based queries, you will need to leverage a new feature introduced in .NET 3.5, called lambdas . Lambdas are inline methods that you can pass into a method to evaluate the method. If you are new to LINQ and lambdas and have never used anonymous delegates (which don't even exist in Visual Basic), this will make more sense after you've seen some examples. Let's use the Where clause to compare working with a method rather than an operator. A standardWhere clause is written as Where LastName='Hesse'. The Where() method requires the condition LastName='Hesse' as a parameter. You would write this condition very differently in C# and Visual Basic.
Wrapping Your Head Around Lambdas There's no question that lambda expressions are a little confusing at first; but once you get the hang of them, they make perfect sense and can help you write some very efficient code. Admittedly, my Visual Basic background prepared me a little less for lambdas than if I had been programming in C++ or more frequently in C#. Some great articles are available that can help you learn more about lambda expressions. For C# developers, the excellent MSDN Magazine article by Anders Hejlsberg (a.k.a. The Father of C#), "The Evolution of LINQ and Its Impact on the Design of C#" (http://msdn.microsoft.com/msdnmag/issues/07/06/CSharp30/ ), has a great explanation of lambdas. For VB developers, the great MSDN Magazine article by Timothy Ng, "Basic Instincts: Lambda Expressions" (http://msdn.microsoft.com/msdnmag/issues/07/09/BasicInstincts/), puts lambdas into perspective.
Here we'll take a look at the query you used in the previous examples, now written using method-based queries. In Visual Basic, the expression begins with Function , to indicate that you are performing a function on a variable control; then it states the condition. The control variable, c in this example, is named on the fly:
Dim contacts = context.Contacts _
.Where(Function(c) c.FirstName='Robert')
The C# LINQ to Entities query using the method-based syntax looks very different:
var contacts = context.Contacts .Where(c => c.FirstName=='Robert');
C# lambda expressions begin by identifying the control variable, followed by=> (the lambda) and then the expression,
[controlVariable].FirstName=='Robert'. In the Where clauses, the expression that returns a Boolean is called apredicate . The query will return all of the contacts for which the expression evaluates to True. Try it out: 1.
Replace your existing query with one of the method queries. You will see that IntelliSense is still pretty helpful, even when writing the lambdas.
2.
Press F5 to run the application. The results will be the same as before.
3.5.1.1. Chaining methods You can combine LINQ query methods to build more useful expressions. This is referred to as chaining . To try this, add an OrderBy method to the previous query. Notice that the lambda expression forOrderBy does not need to evaluate a condition to see whether it is true or false, as does the Where method. It only needs to return a property:
Dim contacts = context.Contacts _ .Where(Function(c) c.FirstName="Robert") _ .OrderBy(Function(foo) foo.LastName)
var contacts = context.Contacts .Where((c) => c.FirstName == "Robert") .OrderBy((foo) => foo.LastName);
NOTE When a method's signature requests a predicate, remember that this refers to an expression that returns a
Boolean; otherwise, the lambda only needs to be a function, as in the OrderBy method. You'll see that in Visual Basic, the signatures of all methods refer to this as a function. The C# methods specifically refer to predicates in the methods that require an expression that returns a Boolean. You can view the signatures of the various LINQ to Entities methods in the MSDN documentation topic "Supported and Unsupported Methods (LINQ to Entities)." Although you can easily use the same variable name throughout compound methods, the variables don't represent the same instance. In the preceding LINQ query, I named the variables differently to highlight how the compiler evaluates the query. LINQ actually evaluates the query one method at a time. First it evaluatescontext.Contacts. Then it applies theWhere method to those results. Finally, it applies the OrderBy method to the results of theWhere method. The c in the Where method refers to the items returned by context.Contacts. The foo in the OrderBy method refers to theIQueryable that is returned by context.Contacts.Where(....). Evaluating one method at a time does not mean processing one method at a time. LINQ to Entities will evaluate this query one method at a time and then will create a store command based on the complete method, unless you are also using methods that must be performed on the client side. It does not execute each method separately. Here is the T-SQL that results from the preceding query:
SELECT [Extent1].[ContactID] AS [ContactID], [Extent1].[FirstName] AS [FirstName], [Extent1].[LastName] AS [LastName], [Extent1].[Title] AS [Title], [Extent1].[AddDate] AS [AddDate], [Extent1].[ModifiedDate] AS [ModifiedDate] FROM [dbo].[Contact] AS [Extent1] WHERE N'Robert' = [Extent1].[FirstName] ORDER BY [Extent1].[LastName] ASC
3.5.2. ObjectQuery's Query Builder Methods It's possible to use Entity SQL with method syntax as well, although a limited number of methods are available—13 in fact, including Where and Select . These methods are calledquery builder methods . Query builder methods will do as their name suggests: build an ObjectQuery with the correct Entity SQL expression for you. Although the query builder methods may look like some of the LINQ methods, they are definitely different. The compiler can tell when you are using a query builder method based on the parameter expression, which will contain either a lambda expression for LINQ queries or an Entity SQL expression.
NOTE Since you have explored only WHERE and SELECT so far while learning about the different ways to query, we'll hold off on listing methods and operators until the following chapter, which has many queries. Here is the latest query using Entity SQL as the method parameters:
Dim contacts = context.Contacts _ .Where("it.FirstName = 'Robert'") _ .OrderBy("it.LastName")
var contacts = context.Contacts .Where("it.FirstName = 'Robert'") .OrderBy("it.LastName");
The most common question regarding these expressions is "Where did it come from?" it is the default alias for the control variable. There is no opportunity to define the control variable as you have had to do with all of the other queries we have looked at so far, though it is possible to define your own for nested queries, as you'll see in the next example. When debugging, you can inspect theCommandText property of the contacts ObjectQuery to see that the query builder did indeed build the Entity SQL for you as shown in Example 3-4 . It's a little more complex than what you might have written yourself. This is a result of the query builder's need to be flexible. Additionally, it does not specify the EntityContainer name in the expression, something that you can't get away with when building the Entity SQL yourself.
Example 3-4. The Entity SQL built by the query builder methods
SELECT VALUE it FROM (SELECT VALUE it FROM ([Contacts]) AS it WHERE it.FirstName = 'Robert') AS it ORDER BY it.LastName
Even when used as a method parameter, Entity SQL provides the same benefits over LINQ to Entities as it does when being used in a query expression. If you need to dynamically build the strings for the expressions or if you need to leverage provider-specific functions, once again Entity SQL is the way to go. Another interesting difference is that using query builder methods with Entity SQL expressions removes any syntax differences between Visual Basic and C#. Whether you use LINQ predicates or Entity SQL predicates, at compile time the Entity Framework will be able to determine which query compilation path to choose by looking at the predicate.
3.5.2.1. Specifying the control variable As you can see, you also can combine query builder methods. The control variable is always it by default, but you could actually break the query apart and explicitly change the names of any control variables except for the first one:
Dim contacts = context.Contacts _ .Where("it.FirstName = 'Robert'") contacts.Name = "cons" Dim orderedContacts = contacts.OrderBy("cons.lastname")
var contacts = context.Contacts.Where("it.FirstName = 'Robert'"); contacts.Name = "cons"; var orderedContacts = contacts.OrderBy("cons.lastname");
The preceding example demonstrated an additional feature, calledcomposable queries . A query was defined (contacts ) and then another query was written using that query. The first query is not executed separately. It is compiled into the second query.
3.6. The Shortest Query Remember the first query in this chapter?
Dim contacts = context.Contacts
In this case, context.Contacts refers to the Contacts property of the entity container. If you look back at the code generated from the model, you can see that context.Contacts returns the following query:
MyBase.CreateQuery(Of Contact)("[Contacts]")
this._Contacts = base.CreateQuery("[Contacts]");
This is an ObjectQuery of Contact types, but it doesn't use an Entity SQL expression. This is a shortcut that works only when you are not applying any functions to the query, or filters, sorting, or any of the possible query functions. When you pass in just the name of the EntitySet , the Entity Framework will do the rest of the work. You can use this shortcut yourself as well, but it is no different from just calling context.Contacts.
Combining LINQ Methods and Query Builder Methods Because their methods are evaluated incrementally, it is possible to combine LINQ query methods and the query builder methods. Then you can get the variety of methods, strong typing, and IntelliSense provided by LINQ, plus the ability to build dynamic expressions and use provider functions, among other benefits of Entity SQL. However, there's a catch. You can add LINQ methods to an ObjectQuery or query builder methods, but the only query builder method that you can add to a LINQ expression is Include.
3.7. EntityClient: The Lowest-Level Method for Returning Streamed Data Through EDM Queries There is still one additional way to query the EDM: viaEntityClient. EntityClient differs from LINQ to Entities and Object Services because it does not materialize objects. Instead, it streams data back to the requesting application as rows and columns in an EntityDataReader , which implements DbDataReader. If you have experience with ADO.NET,EntityClient is comparable toSqlClient, OracleClient , and other client providers; these clients return SqlDataReader, OracleDataReader, and so forth, which also implement the
DbDataReader. A data reader represents data in rows and columns. With the familiarDataReader s, each "cell" contains a scalar value—in other words, a primitive type such as a string or an integer. For example:
Column 1
Column 2
Column 3
Row 1
1
John
Doe
Row 2
2
Jessica
Rabbit
Row 3
3
Cecil
De Mille
EntityDataReader s are designed to represent the relationships that exist in an EDM; therefore, scalar data is not enough. An EntityDataReader has the ability to return data as shaped results. In anEntityDataReader , the cells in the preceding example could contain not only scalar values, but also an entire DbDataReader , a DbDataRecord (a single row from a DbDataReader), or even an EntityKey object. You sawEntityKey as a property of an entity in the EDM you built in Chapter 2; the EntityKey class is a full class implementation based on that property, which you will learn more about in Chapter 9.
EntityClient uses Entity SQL for its query syntax and contains methods and properties that will be familiar if you have worked with ADO.NET previously, including connections, commands, parameters, and transactions. The next example will give you a chance to work withEntityClient . Following the example is an explanation of the code. 1.
Add the following namespace declarations to the beginning of the code file:
Imports System.Data.EntityClient
using System.Data.EntityClient;
2. Add the method in Example 3-5 to your existing code (Module.vb or Program.cs ) to perform the same query you wrote earlier with LINQ to Entities and Object Services. This time you will be using the EntityClient provider.
Example 3-5. Querying with EntityClient
Private Sub EntityClientQueryContacts() Using conn As EntityConnection = _ New EntityConnection("name=ProgrammingEFDB1Entities") conn.Open() Dim queryString = _ "SELECT VALUE c " & _ "FROM ProgrammingEFDB1Entities.Contacts AS c " & _ "WHERE c.FirstName='Robert'" Dim cmd As EntityCommand = conn.CreateCommand() cmd.CommandText = queryString Using rdr As EntityDataReader = _ cmd.ExecuteReader(CommandBehavior.SequentialAccess) Do While rdr.Read Dim firstname = rdr.GetString(1) Dim lastname = rdr.GetString(2) Dim title = rdr.GetString(3) Console.WriteLine("{0} {1} {2}", _ title.Trim, firstname.Trim, lastname) Loop End Using conn.Close() Console.Write("Press Enter...") Console.ReadLine() End Using End Sub
static void EntityClientQueryContacts() { using (EntityConnection conn = new
EntityConnection("name=ProgrammingEFDB1Entities")) { conn.Open(); var queryString = "SELECT VALUE c " + "FROM ProgrammingEFDB1Entities.Contacts AS c " + "WHERE c.FirstName='Robert'"; EntityCommand cmd = conn.CreateCommand(); cmd.CommandText = queryString; using (EntityDataReader rdr = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess)) { while (rdr.Read()) { var firstname = rdr.GetString(1); var lastname = rdr.GetString(2); var title = rdr.GetString(3); Console.WriteLine("{0} {1} {2}", title.Trim(), firstname.Trim(), lastname); } } conn.Close(); Console.Write("Press Enter..."); Console.ReadLine(); } }
3.
Call this new method from theMain method.
NOTE You may want to comment out the call toQueryContacts so that only the new method is run. 4.
Press F5 to test the new method.
The results will be the same as the previous two queries. There is a bit to explain regarding the code for calling theEntityCommand.
3.7.1. EntityConnection and the Connection String With other client providers, the connection connects directly to the data store. However, theEntityConnection provides a connection to the EDM. When you created the model with the ADO.NET Entity Data Model Wizard, you may remember seeing the odd connection string in the wizard's page where you selected the connection. An EntityConnection string consists of pointers to the compiled EDM schema files as well as a database connection string.
The wizard wrote the EntityConnection string into the app.config file. You can open this file from the Solution Explorer and see that the ConnectionString named ProgrammingEFDB1Entities is composed of three parts: the metadata, provider, and provider connection string. The metadata contains file path pointers to the three schema files that are created from the model when the project is built. The data provider refers to the SqlClient provider that is being used to connect to the SQL Server database in this example. And finally, the provider connection string is a standard database connection string:
metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl; provider=System.Data.SqlClient; provider connection string= "Data Source=MyServer; Initial Catalog=ProgrammingEFDB1; Integrated Security=True; MultipleActiveResultSets=True"
NOTE The * in the metadata indicates that the files are embedded into the assembly file of the project that contains the model and its classes. This is the default, although you can specify that the files be saved to the filesystem. You'll learn more about this in Chapter 7.
EntityConnection provides for an easy way to reference the connection string in theapp.config file, which is to set a name property to the same name of the connection string: for example,"name=ProgrammingEFDB1Entities". As you saw in Example 3-5, the quotes are required.
3.7.2. EntityCommand Creating the EntityCommand is no different from creating any other provider command and setting itsCommandText . The CommandText here is the Entity SQL expression defined in the variable,queryString .
3.7.3. ExecuteReader With EntityClient, the SequentialAccess CommandBehavior is required for theExecuteReader method. With other
DbDataReader s, rows must be accessed sequentially, but the columns within the rows need not be. This rule exists to control memory consumption.
3.7.4. Forward-Only Access to the Fields DbDataReader s are streams of data and are, by definition, forward-only. This also means that the columns must be read in this way, which makes the next bit of code in Example 3-5 a little cumbersome. In the string concatenation, you want to combine the fields to read Title FirstName LastName . But this is not the order of the fields returned in the DataReader. Title is the fourth column in the row, whereasFirstName is the second column and LastName is the third; therefore, you cannot read theTitle data first, and instead must read the fields in the order in which they are streaming. That is why this method creates the variables prior to building the string—so the data can be extracted in sequential order. Once the variables exist, you can build the string. This is an important lesson to remember, regardless of how you plan to use the streamed data returned by the EntityClient.
3.8. Translation to Database Queries Although we will explore query processing in detail later in the book, you may already be wondering what kind of query the Entity Framework is sending to your database. The Entity Framework will break down the LINQ or Entity SQL query into a command tree and, with the help of the EDM and the database provider, will create another command tree that is specific to the database. You can imagine how flexible the API needs to be to pull this off, no matter what query you write. Although the examples so far have been simplistic, it is possible to write very complex LINQ to Entities or Entity SQL queries. The Entity Framework needs to be able to deal with anything you throw at it. Therefore, queries may not look exactly the same as you might write them directly in your database's query syntax, because they are being constructed in a somewhat formulaic manner. Sometimes the queries may look more complex but have no negative impact whatsoever on performance. But don't expect this to always be the case. You can see the actual T-SQL built from your query in a few different ways. ObjectQuery has a ToTraceString method that will let you see the store command. You would have to add this to your code to see it, however; we'll do this in a later chapter. A simpler way for now (for those of you who are working with SQL Server Professional and above versions) is to use the SQL Profiler tool in SQL Server. Here is the T-SQL rendered from the LINQ to Entities and Entity SQL queries that returned Contacts named Robert:
SELECT [Extent1].[ContactID] AS [ContactID], [Extent1].[FirstName] AS [FirstName], [Extent1].[LastName] AS [LastName], [Extent1].[Title] AS [Title], [Extent1].[AddDate] AS [AddDate], [Extent1].[ModifiedDate] AS [ModifiedDate] FROM [dbo].[Contact] AS [Extent1] WHERE [Extent1].[FirstName] = 'Robert'
Both queries result in the same T-SQL because they are fairly simple queries.
3.8.1. Pay Attention to the .NET Method's Impact on Generated SQL As you write queries, it's a good idea to keep an eye on the generated SQL, since you can write queries in different ways and get the same results, but perhaps not generate the same store command. A good example of this is when you combine .NET methods with LINQ. TheString class has a handy method named
StartsWith that evaluates the first character in a string:
From c In context.Contacts _ Where c.LastName.StartsWith("S")
from c in PEF.Contacts where c.LastName.StartsWith("S") select c
Even though T-SQL has a handyLEFT function, because of the way .NET evaluatesStartsWith it is not interpreted as
LEFT. Instead, you get the followingWHERE clause in SQL Server: WHERE (CAST(CHARINDEX(N'S', [Extent1].[LastName]) AS int)) = 1
The use of CAST has a negative performance impact in SQL.
Even some of the T-SQL filtering operators are not very good for the performance of SQL queries. A database administrator will tell you that the LEFT and SUBSTRING operators are equally bad because without a specific index on the database table, those operators cannot perform efficient queries. Chapter 16 spends more time on these performance issues and makes some recommendations for writing queries to get better performance from your database.
Visual Basic actually has a LEFT method that you can use in LINQ:
From c In PEF.Contacts _ Where Left(c.LastName, 1) = "S"
This query does cause the T-SQL LEFT function to be used:
WHERE N'S' = (LEFT([Extent1].[LastName], 1))
Unfortunately, C# does not have a comparable function. The best choice for C# is to use Substring:
from c in PEF.Contacts where c.LastName.Substring(0, 1) == "S" select c
This reults in the following WHERE clause:
WHERE N'S' = (SUBSTRING([Extent1].[LastName], 0 + 1, 1))
What About SQL Injection Attacks? LINQ and Entity SQL protect your database from SQL injection attacks. SQL injection attacks can be used to insert commands into your queries that can display data or impact your database by leveraging your connection to the database and any permission your connection may have. This is a common threat to database applications, and you can find plenty of information in books and on the Web about how to avoid them. LINQ to Entities queries are always resolved as parameterized queries, thus avoiding SQL injection. In Entity SQL, most SQL injections won't even evaluate as correct Entity SQL syntax, and therefore cannot be executed. However, someone could attempt to inject Entity SQL. Chapter 16 addresses security in the Entity Framework, and you can read more about this topic there.
3.9. Avoid Inadvertent Query Execution You may have noticed when debugging some of the queries in this chapter that next to the Results property it says "Expanding will process the collection." This is a very important concept to be aware of and it impacts all LINQ queries (including in-memory queries and LINQ to SQL) as well as ObjectQuery queries. Whether you do it in debug mode or in code, every time you do anything to force the enumeration of a query, the query will be processed again. In the Entity Framework, this means that even if you have already done something to enumerate the query (e.g., bound it to a control, run it through a foreach iteration, called ToList() on the query, etc.), anytime you repeat one of these methods that forces execution it will go back to the database, run the query again, bring back the results again, and then merge the results into the cache that's in memory.
ToList is a convenient way to force query execution and provide a variable to work with. That variable will be a System.Collections.Generic.List(Of T) (List in C#) of whatever type the query returns. Another method you can use is ObjectQuery.Execute , which will also force execution.Execute returns a
System.Data.Objects.ObjectResult(Of T) ( in C#). ObjectResult has some special functionality that makes it the right choice for data-binding scenarios; you'll see ObjectResult in use in later chapters where you will be doing data binding in various applications. Execute takes a MergeOption parameter that specifies how the query results should be merged into existing entities; you'll learn more about MergeOption in Chapter 9. You will see ToList and other methods used throughout this book to avoid accidentally repeating query execution.
3.10. Summary In this chapter, you learned about the many different ways to query an EDM, LINQ to Entities, and the ObjectQuery with Entity SQL, LINQ methods, query builder methods, and streaming data with EntityClient . Along the way, you learned about many of the fundamentals that will make it easier for you to construct intelligent queries. In Chapter 9 , you will spend some time comparing how these queries are processed so that you can see the different paths the various query methods embark on as they are resolved. Chapter 16 will cover the performance differences between the various query methods and will demonstrate ways to impact performance directly. Although this chapter focused on a single simple query with a twist here and there, the next chapter will delve more deeply into querying, demonstrating ways to retrieve more complex data using all of the methods you are now familiar with.
Chapter 4. Exploring EDM Queries in Greater Depth In Chapter 3 , you wrote the same basic query over and over and over again. Hopefully, you'll agree that this was a great way to get exposure to the many different ways of writing queries against the Entity Framework. There is a lot more to querying the Entity Data Model, however. You'll need to learn about the flexibility you have for expressing complex queries, projecting data, composing and nesting queries, and writing parameterized queries. There are also nuances regarding what type of data is returned based on how you construct your queries. Sometimes you will get objects, as we saw in the examples in Chapter 3 , but other times you will get unknown objects (anonymous types). It is also possible for Object Services queries to return rows and columns. You'll need to know when to expect these varied forms of data to be returned. Covering all of this exhaustively would require thousands of pages. Therefore, the goal of this chapter is to expose you to the critical features and some of the possibilities. You will learn how to project specific values (rather than entire objects) in queries, how to query across relationships, how to write nested queries and joins, and how to control when trips are made to the database. Along the way, I will introduce and explain additional new concepts to ensure that you truly understand the workings of the Entity Framework. Throughout the rest of the book, you will see variations on queries that take advantage of even more interesting techniques. A number of resources provide many specific examples of queries. Here you will learn some of the more common query tasks so that you will know enough to write queries without constantly having to search for a perfect example of what you are trying to accomplish. Don't expect that you will never have to rely on the "query by example" technique, though! We all do at one time or another. It is also useful to check out resources such as the 101 LINQ Examples on MSDN (for VB and for C#) and the Entity Framework Samples, which provide a great variety of query examples with helpful commentary.
More Query Samples This chapter is filled with many queries, but there are so many possibilities for querying with LINQ or Entity SQL that you will certainly benefit from checking these other great resources:
MSDN's 101 C# LINQ Samples http://msdn.microsoft.com/en-us/vcsharp/aa336746.aspx/
MSDN's 101 Visual Basic LINQ Samples http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx/
MSDN's Entity Framework Query Samples http://code.msdn.microsoft.com/EFQuerySamples/
4.1. Same Model, Friendlier Name In this chapter, we'll continue to use the same model, Model1 , which you worked with in the previous chapters. Writing Entity SQL queries with that model's long EntityContainer name, ProgrammingEFDB1Entities , is more than a bit cumbersome. This was the default name that the ADO.NET Entity Data Model Wizard gave to the entity container, which it based on the database name. Since you will be writing more Entity SQL expressions in this chapter, you may want to modify the EntityContainer name so that you have something simpler to type. The Designer makes this easy to do. Open the model in the Designer and click the background of the model. This will cause the model's properties to show up in the Properties window. Change the EntityContainerName to PEF. This change will have an impact in three places in your application:
PEF will be the new EntityContainer name for the model. PEF will be the new name of theEntityContainer class that you will instantiate to perform queries. PEF will be the new name of the connection string in theapp.config file. You should double-check that the change was made in all three locations.
NOTE You will also need to change any existing code references that use this model from the longer name to the new short name. If you are using Find & Replace, you will be better off replacing instances in "Current Document". This new name is not a recommended naming convention, but a convenience for the sake of writing numerous Entity SQL queries in this chapter.
Some New Lingo Here is a list of terms used in this chapter (and throughout the book) that may be new to you:
Projection Selecting specific properties or expressions in a query, rather than the entity being queried. For example: from c in context.contacts select c.firstname +c.lastname, c.contactID.
Deferred loading Returning to the database to grab data related to what a query has already returned. For example: when working with query results, load the addresses from the database for a particular contact. When deferred loading happens automatically (implicitly), it is calledlazy loading.
Eager loading At the time of query execution, including data related to the data that is being queried. For example: when querying contacts, eager-load their addresses. The contacts and addresses will be retrieved in a single query.
Navigating Moving from an entity to its related data. For example: navigate from a contact to its addresses using the contact.Addresses property.
4.2. Projections in Queries So far, the queries you have seen return an entire object, comparable to writing SELECT * in a SQL query. By returning an entire object in your query, you will get all of the benefits associated with the entity classes—most importantly, the ability to keep track of changes to an entity class for database updates. However, you may be getting much more data than you want to work with. Not only might this be a drag when you are programming, but it also means that you are forcing the database to do more work than necessary, causing more data to be transferred than necessary, and asking Object Services to materialize objects when that may not be necessary. If you are concerned about the performance of your applications, all of this extra effort will make your application processing inefficient. Often in SQL, you will select particular columns to return from a table SELECT ( Firstname, Lastname FROM
Contact ) or from a set of joined tables. This is referred to as projection . With LINQ or Entity SQL queries you can shape your results by picking particular properties or expressions rather than entities. You can also select properties from related data. In the previous queries, you returned an entire object but used only theTitle , FirstName, and LastName properties. You can rewrite those queries to return only these three properties.
4.2.1. Projections in LINQ to Entities In the QueryContacts method, modify the query by replacingSelect con with the projected properties, as shown in Example 4-1.
Example 4-1. Simple LINQ to Entities query
Dim contacts = From c In context.Contacts _ Where c.FirstName= "Robert" _ Select New With {c.Title, c.LastName, c.FirstName}
var contacts = from c in context.Contacts where c.LastName.StartsWith("S") select new { c.Title, c.FirstName, c.LastName };
4.2.1.1. VB and C# syntax differences
You may have noticed the syntax differences between VB and C# projections. This is not particular to LINQ to Entities, but it is common for all implementations of LINQ. C# requires that you useselect new {…} when projecting. Visual Basic is more lenient. The most explicit syntax for VB is Select New With {…} as in Example 4-1 , though you could write the query in this simpler format:
From c In context.Contacts _ Where c.FirstName= "Robert" _ Select c.Title, c.LastName, c.FirstName
NOTE There are plenty of other nuances to LINQ projections in both languages. For example, you can project into predefined types. In addition, C# projections always create immutable (read-only) results, whereas VB allows the creation of immutable and mutable results. You can learn more about projecting with LINQ in the MSDN Library and from the many great resources that focus on LINQ.
4.2.2. LINQ Projections and New Language Features A number of new language features have made it easier for developers to implement LINQ projections. We'll examine several of these in this section, including anonymous types and implicitly typed local variables. If you hover your mouse pointer over the contacts variable, the DataTip will show you what the query returns. It's an
IQueryable of an "anonymous type," rather than an IQueryable of contact types. The DataTips and debuggers in Visual Basic and C# often show different information. In this case, the difference is interesting, as you can see in Figures Figure 4-1 and Figure 4-2.
Figure 4-1. The DataTip in Visual Basic, which shows the new contacts variable to be an IQueryable(Of )
Figure 4-2. The DataTip in C#, which shows even more details regarding the anonymous type
4.2.2.1. Anonymous types
What is this anonymous type that the LINQ to Entities projection is returning? The anonymous type is a new language enhancement introduced in Visual Basic 9 and C# 3.0 that allows the compilers to work with types that were not previously defined. Anonymous types are generally used for on-the-fly types that won't be used elsewhere in the application. They relieve you from having to define a class for every type, even if the type is to be used only briefly. Yet the returned anonymous type is still strongly typed, which means you can easily interact with it in the code following its creation. The sidebar Wrapping Your Head Around Lambdas includes a link to an article by Anders Hejlsberg. The article also contains a great introduction to anonymous types. Anonymous types are a powerful new feature that you can use throughout .NET, but they have special importance for LINQ queries because of their ability to allow projections that can return anything a developer can dream up. So, this query returned an anonymous type that doesn't have a name, but has the properties Title , FirstName, and
LastName . If you are still modifying the earlier query method, you can see a bit of this magic by removing the Console.WriteLine method and retyping it. The anonymous type is strongly typed and recognized by IntelliSense. Pretty cool!
Anonymous Types and Updates Although later chapters will introduce the concepts of tracking changes to entities and performing updates to the database, it is important to keep in mind that this takes place only with complete entities. Anonymous types do not participate in change tracking or updates. The same is true of the data that results from randomly projected properties in an ObjectQuery . Pay attention to projections to make note of where complete entities are returned versus anonymous types.
4.2.2.2. Implicitly typed local variables Another new compiler trick that you have been taking advantage of in the code samples so far is the use of implicitly typed local variables. In C# you use them with a new keyword, var, and in VB you use them with the existingDim keyword. It is possible to declare variables without identifying their types. They will infer the type based on the value that is being set. Hasn't it always seemed redundant to say something likeDim str as String="this is some text"or int MyInt=123 ? With implicitly typed local variables, Dim str="this is some text"and var MyInt=123 are enough. In the case of replacingint with var the benefit is not very obvious. Had that type beenMyCustomType, suddenly var looks pretty convenient.
NOTE This shortcut is not always a good thing, as it removes some of the explicitness of your code. I wrote a blog post on DevSource.com titled "How Visual Studio 2008 made me even lazier" (http://blogs.devsource.com/devlife/content/net_general/how_visual_studio_2008_made_me_even_lazier.html/ ). There is an interesting discussion in the comments about the pros and cons of implicit typing. Where implicitly typed local variables really shine, however, is with LINQ query projections, because there's no way to say "Dim contacts as a thing with a Title, a FirstName, and a LastName." Instead, you can write "Dim contacts (and just look at the other side of the equals sign to figure out what this is)." In this context, Dim in VB andvar in C# essentially translate to "thing."
Run the application and you'll see that, once again, the results are the same as they were previously.
NOTE When you learn more about Object Services and all of the functionality it provides to objects returned by queries against the EDM, you will better understand the significance of returning anonymous types rather than entire entity objects defined by the EDM.
4.2.2.3. Implicit and explicit anonymous type creation You can project into anonymous types in a number of ways. For instance, it is possible to give a name to an anonymous type, such as ContactName in Example 4-2.
Example 4-2. Naming a projected anonymous type in LINQ
From c In context.Contacts _ Where c.FirstName = "Robert" _ Select ContactName = New With {c.Title, c.LastName, c.FirstName}
from c in context.Contacts where c.FirstName == "Robert" let ContactName = new {c.Title, c.LastName, c.FirstName} select ContactName
C# does not allow naming in theSELECT statement; it has another operator,LET, for this purpose.
NOTE There are so many ways to do projection and use anonymous types in LINQ queries. Here you are seeing just a small slice of what you can achieve, so be sure to look to the dedicated LINQ resources to expand your understanding. Naming the anonymous type is more useful if this new type is a property of the projected results. In Example 4-3 , a projection is used to project much more than some strings. It creates a new type with another anonymous type as the first property and the addresses of the contact as the second.
NOTE I'm projecting the Addresses property here to highlight the projection. You'll learn more about working with related data later in this chapter. When you name the anonymous type, the property that results will have the name specified in the query. Notice that the property name is used later in the query for the Order By operator and when working with the results.
Example 4-3. Anonymous types as properties
Dim contacts = From c In context.Contacts _ Where c.FirstName = "Robert" _ Select _ ContactName = New With { _ c.Title, c.LastName, c.FirstName}, _ c.Addresses _ Order By ContactName.LastName For Each contact In contacts Dim name = contact.ContactName Console.WriteLine("{0} {1} {2}", _ name.Title.Trim, name.FirstName.Trim, _ name.LastName.Trim) Next
var contacts = from c in context.Contacts where c.FirstName == "Robert" let foo= new { ContactName = new {c.Title, c.LastName, c.FirstName}, c.Addresses } orderby foo.ContactName.LastName select foo; foreach (var contact in contacts) {
var name = contact.ContactName; Console.WriteLine("{0} {1} {2}", name.Title.Trim(), name.FirstName.Trim(), name.LastName.Trim()); }
NOTE Unlike the ContactName anonymous type in this query, theAddress entities that this query returns will participate in the change tracking and database updates.
4.2.3. Projections with LINQ Query Methods To project using LINQ's method-based query syntax, you would use theSelect method and then identify the properties you want in its parameter. The method-based query syntax requires the explicit syntax for creating an anonymous type in the predicate. In VB, that definitely starts to get a bit clunky. (See Example 4-4.)
Example 4-4. Projecting using LINQ's method-based syntax
context.Contacts _ .Where(Function(c) c.FirstName = "Robert") _ .Select(Function(c) New With {c.Title, c.LastName, c.FirstName})
context.Contacts .Where((c) => c.FirstName == "Robert") .Select((c) => new {c.Title, c.LastName, c.FirstName})
4.3. Projections in Entity SQL You can use projections with Entity SQL queries in both Object Services and EntityClient queries. Only LINQ queries can return anonymous types. Object Services has its own way of resolving projections when the resultant type doesn't exist. Because EntityClient does not attempt to materialize objects from the results, this is not a concern with
EntityClient queries. When projecting with Object Services, the query will return data records. These are the same
System.Data.Common.DbDataRecords returned by EntityClient queries. First let's look at the code inExample 4-5 and then at the query results. Here is where you will begin to benefit from having changed the name of the EntityContainer to PEF.
Example 4-5. Projection with Entity SQL
Dim queryStr = "SELECT c.FirstName, c.LastName, c.Title " & _ "FROM PEF.Contacts AS c " & _ "WHERE c.FirstName='Robert'" Dim contacts = PEF.CreateQuery(Of DbDataRecord)(queryStr)
var queryStr = "SELECT c.FirstName,c.LastName, c.Title " + "FROM PEF.Contacts AS c " + "WHERE c.FirstName='Robert'"; var contacts = context.CreateQuery(queryStr);
Notice that in the Entity SQL string, the keywordVALUE is gone. That's because the projection is selecting multiple values. Also, note that the type being passed into the CreateQuery method is now a DbDataRecord . In the introduction to EntityClient in Chapter 3, you learned that aDbDataRecord represents a record in a
DbDataReader . Therefore, you will need to interact with these results in the same way you did when using the EntityClient example. There is one very nice difference, however. The results are not being streamed; they have been materialized into the DbDataRecord . Therefore, you can access the column data that results in any order you want. That's why the query string selected FirstName, LastName, and then Title . When you build the code to display the results, shown inExample 4-6 , you'll see that it's OK to useTitle first.
Example 4-6. Enumerating through the DbDataRecord returned by a LINQ projection
For Each c In contacts Console.WriteLine("{0} {1} {2}", _ c.Item("Title").ToString.Trim, _ c.Item("FirstName").ToString.Trim, _ c.Item("LastName").ToString.Trim) Next
foreach (var item in contacts) { Console.WriteLine("{0} {1} {2}", item["Title"].ToString().Trim(), item["FirstName"].ToString().Trim(), item["LastName"].ToString().Trim()); }
NOTE In Example 4-6 , I used an alternative way of pulling data from aDbDataRecord . Item takes a string parameter (the column name) or an integer (the column position), whereas the GetString, GetInt , and other related methods take only an integer as a parameter.
4.3.1. DbDataRecords and Nonscalar Properties Most of these examples project strings, though you saw one example with LINQ for Entities where an address's EntityCollection was projected. How would you interact with aDbDataRecord that contains an entity or another object in its columns? The Entity SQL expression in Example 4-7 selects the entire Contact entity as the first property of the results and the contact's addresses as the second property.
Example 4-7. Projecting objects with Entity SQL
Dim queryStr = "SELECT c,c.Addresses " & _ "FROM PEF.Contacts AS c " & _ "WHERE c.FirstName='Robert'" Dim contacts = context.CreateQuery(Of DbDataRecord)(queryStr) For Each c In contacts Dim contact = CType(c.Item(0), Contact) Console.WriteLine("{0} {1} {2}", _ contact.Title, _ contact.FirstName, _ contact.LastName) Next
var queryStr = "SELECT c,c.Addresses " + "FROM PEF.Contacts AS c " + "WHERE c.FirstName='Robert'"; var contacts = context.CreateQuery(queryStr); foreach (var c in contacts) { var contact = (Contact)(c[0]); Console.WriteLine("{0} {1} {2}", contact.Title, contact.FirstName, contact.LastName); }
In Example 4-6 , you had to explicitly cast the items of the results toString types. In this case, because you know the first item will contain a Contact type, you can cast the column toContact and then work directly with that strongly typed object. You can do the same with the collection of Address types in the second column.
4.3.2. Projecting with Query Builder Methods Example 4-8 shows an example of using a query builder method to do projection. In the projection, you use the it alias to access the properties.
Example 4-8. Using query builder methods to project data
Dim contacts = context.Contacts _ .Where("it.FirstName='Robert'") _ .Select("it.Title, it.FirstName, it.LastName")
var contacts = context.Contacts .Where("it.FirstName='Robert'") .Select("it.Title, it.FirstName, it.LastName");
Projection with query builder methods also returnsDbDataRecord s. You'll need to access the results through the data record's items, as with the other examples.
4.4. Querying Across Associations One of the big benefits that the EDM lends to querying is that the relationships are built into the model and you won't have to construct joins very often to access related data. Additionally, when using LINQ for the queries, the related data is presented via IntelliSense, which makes it very discoverable. Using the model, let's take a look at some more queries, this time digging into the associations. The model has only one association betweenContact and Address . The association provides two navigations—one from
Contact to all of its related addresses and one fromAddress to its related contact.
Navigations Point to a Related End When working with navigation properties in EntityObject s, the property begins as a related end. This is an Entity Framework interface. At runtime, the Entity Framework determines whether the related end should be an EntityObject or an EntityCollection. When the end is an entity, the Entity Framework takes two steps. First it populates the property (e.g., Address.Contact ) with an actual object. Next it creates a second property to contain some metadata about the relationship. This property has the name of the navigation property plus the word Reference, as in ContactReference. When the end is an EntityCollection, the runtime will create anEntityCollection object and fill it with the related data. In the case of Contact.Addresses , the Addresses property will then be an EntityCollection of
Address types. No additional Reference property is created. Chapter 15 drills much more deeply into this topic.
In many cases, you will not be returning a collection of a single previously defined type. When the type doesn't preexist, LINQ to Entities will return an anonymous type. Because an ObjectQuery can't return anonymous types, in these cases it will return the DbDataRecord type that you learned about inChapter 3 in the EntityClient discussion. You can easily do projection drilling into related entities, although drilling into a collection is different from drilling into a reference entity. For example, you can't request Contact.Addresses.Street in a query. Contact to Addresses is a one-to-many relationship and Addresses is a collection, not a single entity.Street is not a property of theAddresses
EntityCollection. However, you could select Address.Contact.LastName , because you would be navigating to a single entity. There is only one contact per address; therefore, there is no question regarding from which entity the query should retrieve the LastName.
4.4.1. Navigation to an EntityReference Recall that navigating to the "one" end of a one-to-one or many-to-one relationship is referred to as a navigation reference. The entity you are pointing to is referred to as an EntityReference or EntityRef.
NOTE Chapter 15 will drill further into EntityRefs andEntityCollections, and how they are surfaced as navigation properties.
The LINQ query in Example 4-9 returns an anonymous type containing an address and its related contact.
Example 4-9. Projecting into an EntityRef with LINQ to Entities
Dim newTypes = From a In context.Addresses _ Where a.CountryRegion = "UK" _ Select New With {a, a.Contact}
var newTypes = from a in context.Addresses where a.CountryRegion == "United Kingdom" select new { a, a.Contact };
Figure 4-3 displays the complex type that results in the debugger, where you can see that one property is the address record and the other is the contact.
Figure 4-3. The query results, which contain a complex type with the address and its contact
When working with the results, you'll have to drill into the new type's properties (the Address and the Contact ) and from there you'll have to drill into their properties, as shown in Example 4-10.
Example 4-10. Accessing the properties of an anonymous type
For Each newtype In newTypes
Console.WriteLine("{0} {1} {2}", _ newtype.Contact.LastName, _ newtype.a.Street1, newtype.a.City, ) Next
foreach (var newtype in newTypes) { Console.WriteLine("{0} {1} {2}", newtype.Contact.LastName, newtype.a.Street1, newtype.a.City); }
NOTE The first property is nameda because it is using the variable name given in the query. If you want to be sure the property is called Address you can use that instead of the simplera, or use a LINQ feature to rename the property.
Select New With {.Address = a, a.Contact}
C#: select new {Address= a, a.Contact };
Then you can work with newtype.Address in the data that results. Example 4-11 demonstrates how to express this same query (fromExample 4-9) using Entity SQL.
Example 4-11. Projecting into an EntityRef with Entity SQL
SELECT a,a.Contact
FROM PEF.Addresses AS a WHERE a.CountryRegion='Canada'
When working with these results you can cast the data in the first position to an address and the data in the second position to a contact, as you did in Example 4-7. Although this may suit many scenarios in your applications, you may prefer to project individual properties to create a type that is not complex. Example 4-12 shows such a query using LINQ to Entities. This projection returns a new type with three properties. The first is an Address entity; the second and third are strings. Again, the property names are based on the query defaults— a, FirstName , and LastName.
Example 4-12. Projecting into an EntityRef and returning unshaped data
Dim newTypes = From a In context.Addresses _ Where a.CountryRegion = "UK" _ Select New With {a, a.Contact.FirstName, _ a.Contact.LastName} For Each newtype In newTypes Console.WriteLine("{0} {1} {2} {3}", _ newtype.a.Street1, newtype.a.City _ newtype.FirstName, newtype.LastName) Next
var newTypes = from a in context.Addresses where a.CountryRegion == "United Kingdom" select new { a, a.Contact.FirstName, a.Contact.LastName }; foreach (var newtype in newTypes) { Console.WriteLine("{0} {1} {2} {3}", newtype.FirstName, newtype.LastName, newtype.a.Street1, newtype.a.City); }
4.4.2. Filtering and Sorting with an EntityReference You can filter and sort based on a property of anEntityReference even if you are not selecting the related data. For example, you can select all addresses for contacts with a particular last name. The LINQ to Entities query in Example 4-13 sorts by Contact.LastName and filters on the Contact.AddDate field even though AddDate is not part of the results.
Example 4-13. Filtering and sorting on reference properties
From a In context.Addresses _ Where a.Contact.AddDate > New Date(2009, 1, 1) _ Order By a.Contact.LastName _ Select New With {a, a.Contact.FirstName, a.Contact.LastName}
from a in context.Addresses where a.Contact.AddDate > new System.DateTime(2009, 1, 1) orderby a.Contact.LastName select new {a, a.Contact.FirstName, a.Contact.LastName};
In Entity SQL, the same query would be represented as follows:
SELECT a,a.Contact.FirstName,a.Contact.LastName FROM PEF.Addresses AS a WHERE a.Contact.AddDate>DATETIME'2009-01-1 00:00' ORDER BY a.Contact.LastName
NOTE That very odd syntax for the date in the WHERE clause parameter is Entity SQL's way of expressing aDateTime value. See the next sidebar for more information.
Entity SQL Literals
Literals are a way to express a value. You can easily express a string by wrapping quotes around it. But how can you express a date in Entity SQL? The DateTime literal solves this problem. You can express January 1, 2009, in Entity SQL as
DATETIME'2009-01-1 00:00' . Other types that use literal keywords in Entity SQL areTime, GUID, BINARY, and DATETIMEOFFSET . Check the MSDN Library topic "Literals (Entity SQL)" for more details about expressing values in Entity SQL.
4.4.3. Navigating to Entity Collections Querying with related data is straightforward when the related data is a single entity, but what about when the navigation property is an EntityCollection such as Contact.Addresses? Let's start with a simple scenario that you have seen a few times already in this chapter: returning a contact and its collection of addresses. To highlight the difference between the original properties and the results, the EntityCollection in the new type is given a random name, as shown in Example 4-14.
Example 4-14. Projecting an EntityCollection with LINQ
Dim contacts = From c In context.Contacts _ Select New With {c, .Foos = c.Addresses}
var contacts = from c in context.Contacts select new {c, Foos = c.Addresses};
This query creates a new anonymous type with two properties. The first is the Contact and the second is theEntityCollection of Addresses related to thatContact. You can then enumerate through the results, and then, for each result, enumerate through the collection inside the Address property, as shown in Example 4-15.
Example 4-15. Enumerating over shaped data that includes an EntityCollection
For Each contact In contacts Console.WriteLine("{0}: Address Count {1} ", _ contact.c.LastName.Trim, contact.Foos.Count) For Each address In contact.Foos Console.WriteLine(" City= {0}", address.City) Next Next
foreach (var contact in contacts) { Console.WriteLine("{0}: Address Count {1} ", contact.c.LastName.Trim(), contact.Foos.Count); foreach (var foo in contact.Foos) { Console.WriteLine(" City= {0}", foo.City); } }
You may not want the addresses to be represented as a separate property in the resulting data, but there are many ways to shape query results and right now the goal is to get a view of what these queries can achieve.
4.4.4. Projecting Properties from EntityCollection Entities If you wanted to select particular properties such asStreet and City from each Address of each Contact , the method you should use to do the query depends on what shape you want the results to be.
4.4.4.1. Shaped results You could shape the data similar to the previous example, but instead of a set of complete address entities as the Foos property, you can project some of the address properties. This would result in a set of anonymous types, named StreetsCities instead of Foos, in the second property. You can achieve this with a nested query, a feature we'll look at more closely later in the chapter. For now, you can see in the query in Example 4-16 that the third property,StreetsCities , contains the results of querying theContact's Addresses.
Example 4-16. Projecting values from an EntityCollection
From c In context.Contacts _ Select New With {c.FirstName, c.LastName, _ .StreetsCities = From a In c.Addresses Select a.Street1, a.City }
from c in context.Contacts select new {c.FirstName, c.LastName, StreetsCities = from a in c.Addresses select new { a.Street1, a.City } }
The anonymous type that is returned has the properties FirstName and LastName, along with a collection of anonymous types with Street and City properties. The debugger screenshot in Figure 4-4 displays the new type.
Figure 4-4. The newly shaped anonymous type
4.4.4.2. Flattened results Another way to project into the addresses is to merely turn the query around. That is, query the addresses and their contact data to flatten the results, as shown in Example 4-17, so that the data is no longer shaped.
Example 4-17. Flattening the related data
From a In context.Addresses _ order By a.Contact.LastName _ Select New With {a.Contact.FirstName, a.Contact.LastName, _ a.Street1, a.City}
var contacts = from a in context.Addresses orderby a.Contact.LastName select new {a.Contact, a.Contact.LastName, a.Street1, a.City};
This will result in a single type with four properties, but contacts with multiple addresses will appear multiple times, as you can see in this section of the results. For instance, Katherine Harding and Keith Harris each have two results:
Hanson, John: 825 W 500 S, Bountiful Harding, Katherine: 52560 Free Street, Toronto Harding, Katherine: 25 Flatiron Blvd, Vancouver Harrington, Lucy: 482505 Warm Springs Blvd., Fremont Harris, Keith: 3207 S Grady Way, Renton Harris, Keith: 7943 Walnut Ave, Renton Harui, Roger: 9927 N. Main St., Tooele Hass, Ann: Medford Outlet Center, Medford
4.4.5. Filtering and Sorting with EntityCollections Although you can easily use related data in projections or for filtering, sorting, and other operations, it is important to keep in mind that when the related data is in a collection, you need to leverage operations that can be performed on a set of data. For example, if you want to find contacts with addresses in the United Kingdom (represented as UK in the database), you can use the ANY method in LINQ to Entities (seeExample 4-18) or the EXISTS operator in Entity SQL to search the contact's addresses. The LINQ query uses a predicate to provide the condition for ANY.
Example 4-18. Filter condition provided by an EntityCollection with LINQ
From c In context.Contacts _
Where c.Addresses.Any(Function(a) a.CountryRegion = "UK") _ Select c
from c in context.Contacts where c.Addresses.Any((a) => a.CountryRegion == "UK") select c;
Entity SQL's EXISTS is not as facile as theANY method. You'll need to pass a subquery intoEXISTS so that it knows what to search. Look closely at the subquery in Example 4-19. It is queryingc.Addresses , which is the collection of addresses that belongs to the value being returned in the main query. The subquery is able to take advantage of the navigation from a contact to its addresses.
Example 4-19. Filtering across a navigation with Entity SQL
queryString = "Select VALUE c " & _ "FROM PEF.Contacts as c " & _ "WHERE EXISTS(SELECT a from c.Addresses as a " & _ "WHERE a.CountryRegion='UK')"
4.4.6. Aggregates with EntityCollections Aggregates perform calculations on a series of data—count, sum, average, min, max, and so on. You may not want the entire collection of addresses, but rather some aggregated information about that collection.
4.4.6.1. Aggregates in LINQ to Entities It's relatively simple to aggregate data with LINQ, using one of the aggregate methods such as Count , which you can add as a method on a collection. The Count method will return the count of the items in the collection (seeExample 4-20).
Example 4-20. Using the Count aggregate method in LINQ to Entities
From c In context.Contacts _
Select New With {c.LastName, c.Addresses.Count}
from c in context.Contacts select new {c.LastName, c.Addresses.Count};
Other types of aggregates, such asMax , require a specific value to aggregate. You can supply that value using a lambda expression, as shown in Example 4-21.
Example 4-21. Using an aggregate method with a lambda in LINQ
From c In context.Contacts _ Select New With {c.LastName, _ .MaxPC = c.Addresses.Max(Function(a) a.PostalCode)}
from c in context.Contacts select new { c.LastName, MaxPC = c.Addresses.Max((a) => a.PostalCode)};
It's important to name the property returned by the aggregate function because LINQ is unable to derive one based on the method. If you forget to do this, both VB and C# will give a compiler error explaining the problem.
NOTE Visual Basic has an Aggregate operator for LINQ that you can use in place ofFROM in your LINQ queries. Check the MSDN Library topic "Aggregate Clause (Visual Basic)" for more information.
4.4.6.2. Aggregates in Entity SQL Working with aggregates in Entity SQL is not as simple as it is in LINQ to Entities. For example, LINQ is able to count the
elements in a collection and doesn't care whether the collection contains values or objects. But Entity SQL can perform aggregates on only a collection of values, and even then on only certain types of values. This actually mirrors how SQL Server uses aggregates. Therefore, with Entity SQL you can't write Count(c.Addresses) , but rather you need to pass a value, such as AddressID, in to the Count function. To do this, you can use a subquery againstc.Addresses that returns a collection of AddressIDs. You can then COUNT the results of that query, as shown inExample 4-22.
Example 4-22. Using the Count aggregate function in Entity SQL
Select c, COUNT(Select VALUE a.AddressID FROM c.Addresses as a) FROM PEF.Contacts as c
The other aggregates work in the same way. Example 4-23 shows the MAX query written with Entity SQL.
Example 4-23. Using the MAX aggregate function in Entity SQL
SELECT c.LastName, MAX(SELECT VALUE a.PostalCode FROM c.Addresses AS a) FROM PEF.Contacts AS c
You can even use an aggregate in a subquery, as in Example 4-24.
Example 4-24. An aggregate in a subquery
SELECT c.LastName, (SELECT VALUE MAX(a.PostalCode) FROM c.Addresses as a) FROM PEF.Contacts AS c
In this example, the second column of the query results does not contain the string value of the PostalCode . It contains the results of a query, and therefore it is a collection of string values. If you want to read the PostalCode s, you can iterate through the collection using code similar to Example 4-15, or use a SET operator.
4.4.7. Entity SQL SET Operators Like aggregates, SET operators work with a set of values. TheANYELEMENT operator is a SET operator that will randomly pick an element from a collection. As shown in Example 4-25 , you can even use this with collections that contain only one element, such as the MAX PostalCode column in Example 4-24.
Example 4-25. Using the ANYELEMENT operator against a set of data
SELECT c.LastName,
ANYELEMENT(SELECT VALUE max(a.PostalCode FROM c.Addresses AS a) FROM PEF.Contacts AS c
The results of this query will now contain a string in the second position, not a collection. The SET operators in Entity SQL areANYELEMENT, EXCEPT, FLATTEN, INTERSECT, EXISTS and NOT EXISTS, IN and NOT IN , OVERLAPS, SET, and UNION. There is also an ELEMENT operator that has not yet been implemented but is reserved. If you attempt to use it in the first version of the Entity Framework, you will get an exception that explains that ELEMENT cannot be used yet.
NOTE Take some time to explore these operators in the documentation and in code to get a feel for where and when you might want to use them.
4.4.8. Aggregates in LINQ Methods and Query Builder Methods The LINQ aggregates are methods, not query operators. Therefore, they work very naturally with the LINQ query methods. Example 4-26 uses the Max aggregate as one of two projected values to be returned.
Example 4-26. A LINQ method syntax query using an aggregate
context.Contacts _ .Select(Function(c) New With {c.LastName, _ .MaxCode = c.Addresses.Max(Function(a) a.PostalCode)})
context.Contacts .Select((c) => new { c.LastName, MaxCode = c.Addresses.Max((a) => a.PostalCode) });
This query does two interesting things with the lambdas. First it uses a lambda expression to specify what values should be projected: LastName and MaxCode. Once the variable, c , has been declared, the function projects an anonymous type consisting of LastName as the first property andMaxCode as the second.MaxCode is defined by using theMax aggregate on the Addresses collection of the contact.
The query builder methods do not provide aggregate methods. However, you can use an Entity SQL query as the argument of the SELECT query builder method to perform the aggregate. Remember that the collection being queried in the subquery is based on the main query's control variable, referred to with the
it alias by default. Example 4-27 is the same query fromExample 4-26, but expressed with query builder methods.
Example 4-27. An Entity SQL query builder method using an aggregate
context.Contacts _ .Select("it.LastName, " & _ "( MAX(SELECT VALUE a.PostalCode FROM it.Addresses AS a))")
context.Contacts .Select("it.LastName, " + "( MAX(SELECT VALUE a.PostalCode FROM it.Addresses AS a))");
4.5. Joins and Nested Queries Although associations in the EDM minimize the need for joins in queries, sometimes a relationship may exist but there is no association to represent the relationship. In these and other cases, you can use nested queries or joins to bring the data together.
Should You Even Use Joins? Zlatko Michailov, the Entity SQL program manager at Microsoft, writes in his blog: "A well defined query against a well defined entity data model does not need JOIN. Navigation properties in combination with nesting sub-queries should be used instead. These latter constructs represent task requirements much more closely than JOIN does."—http://blogs.msdn.com/esql/ (November 1, 2007)
LINQ to Entities provides aJOIN operator as well asGROUPJOIN. Entity SQL provides a variety of options in theJOIN
FROM clause, including inner joins, as well as left, right, and full outer joins. It also enables joining multiple collections separated by commas.
4.5.1. Joins The vOfficeAddresses entity in the current model has all of the contact properties except for the contact'sTitle . Because there is no association between vOfficeAddresses and Contact, you will need to useJOIN to combine the
vOfficeAddresses entity properties with theTitle property.
NOTE You could, of course, add the association to the model in this case, but then there would be no lesson here, would there? Example 4-28 shows the syntax of a LINQJOIN.
Example 4-28. JOIN syntax for LINQ
FROM [variableA] IN collectionA JOIN [variableB] IN collection ON variable.commonproperty EQUALS variableB.commonProperty SELECT .....
Example 4-29 shows how to combine data fromContact entities and vOfficeAddresses entities using the JOIN.
Example 4-29. A LINQ to Entities query using a JOIN
From con In context.Contacts _ Join add In context.vOfficeAddresses _ On con.ContactID Equals add.contactID _ Select New With {add.FirstName, add.LastName, _ con.Title, add.Street1, add.City, add.StateProvince}
from c in context.Contacts join oa in context.vOfficeAddresses on c.ContactID equals oa.ContactID select new { oa.FirstName, oa.LastName, c.Title, oa.Street1, oa.City, oa.StateProvince };
This provides an inner join where only entities with matchingContactID s are returned. Any contacts with no match in the vOfficeAddresses will not be returned. vOfficeAddresses with no match in Contacts will not be returned either. Example 4-30 shows the syntax of an Entity SQLJOIN.
Example 4-30. JOIN syntax for Entity SQL
SELECT variableA, variableB FROM collection as variableA JOIN Collection as variableB ON Property = Property
NOTE Entity SQL has the ability to do cross joins. You can express them explicitly; however, JOIN a without an ON clause will implicitly become a cross join, pairing every entity in the first collection with every entity in the second collection. So, watch out! Example 4-31 demonstrates the sameJOIN query expressed in Entity SQL.
Example 4-31. An Entity SQL query using JOIN
SELECT c.Title,oa.FirstName, oa.LastName, oa.Street1, oa.City, oa.StateProvince FROM PEF.Contacts as c JOIN PEF.vOfficeAddresses as oa ON c.ContactID = oa.ContactID
4.5.2. Nested Queries Both LINQ and Entity SQL provide the ability to nest queries, and you have already seen some examples of this. When you write a query, anywhere a value is expected you can use another query in its place, as long as that query returns an acceptable type. You can use a nested query in place of an expression or a collection, as you will see in the following examples. The goal of the previous JOIN queries was to return properties from aContact entity combined with properties from the
vOfficeAddresses entities where the ContactID matches.
4.5.2.1. Using a nested LINQ query as a projection Example 4-32 shows how to express the previous query in LINQ using a nested query instead of aJOIN . The query in Example 4-32 uses a nested query (which is highlighted) combined with theFirstOrDefault method in place of a projected value to return results from vOfficeAddresses.
Example 4-32. Nested query in place of a SELECT expression in LINQ
From a In context.vOfficeAddresses _ Select New With {a.FirstName, a.LastName, _ .Title = (From c In context.Contacts _ Where c.ContactID = a.ContactID _ Select c.Title).FirstOrDefault, _ a.Street1, a.City, a.StateProvince}
from a in context.vOfficeAddresses select new { a.FirstName, a.LastName, Title = (from c in context.Contacts where c.ContactID == a.ContactID
select c.Title).FirstOrDefault(), a.Street1, a.City, a.StateProvince }
There are a few notable twists to this query. The first should be familiar: an anonymous type is not able to automatically name the return from the nested query. Therefore, it is given the name "Title". The second twist is that the subquery returns an IQueryable of String , not just a string, which is why theFirstOrDefault method is appended to the query.
4.5.2.2. Using a nested LINQ query as the collection to be queried You can also use the nested query in place of the collection being queried. The nested query merely returns another collection to be queried. For example, rather than querying all vOfficeAddresses , you could create a subquery that returns only
vOfficeAddresses in Ontario and then query against that.Example 4-33 is simplistic and could easily be expressed without the nested query. The technique can be useful when you are attempting to express queries that are much more complex.
Example 4-33. Nested query in place of a target collection in LINQ
Dim contacts = From add In _ (From oa In context.vOfficeAddresses _ Where oa.StateProvince = "Ontario" Select oa) _ Select ...
var contacts = from add in (from oa in context.vOfficeAddresses where oa.StateProvince == "Ontario" select oa) select ...
You can benefit from using nested queries to help with complicated queries by separating the nested query from the main query. Example 4-34 ties a subquery to a variable and then uses that variable in another query. This can make the code much more readable. When the query is executed, the Entity Framework will create a single query from the combined expressions.
Example 4-34. Breaking a nested query out of the main query in LINQ
Dim subquery = From oa In context.vOfficeAddresses _ Where oa.StateProvince = " Ontario" Select oa Dim contacts = From add In subquery _ Select ....
var subquery = from oa In context.vOfficeAddresses where oa.StateProvince == " Ontario" select oa var contacts = from add In subquery select ....
NOTE You can't separate out a nested query that's inside a projection, as inExample 4-32 , because its filter condition is dependent on the main query.
4.5.2.3. Nested queries in Entity SQL With Entity SQL, the nested query works in the same manner, using the query in place of an actual value, though there's no need to name the property it represents (see Example 4-35).
Example 4-35. Nested query in place of a SELECT expression in Entity SQL
SELECT oa.FirstName, oa.LastName, ANYELEMENT(SELECT VALUE c.Title FROM PEF.Contacts as c WHERE c.ContactID=oa.ContactID), oa.Street1, oa.City, oa.StateProvince FROM PEF.vOfficeAddresses as oa
The query in Example 4-36 demonstrates replacing the queried collection with a nested query.
Example 4-36. Nested query in place of a FROM expression in Entity SQL
SELECT oa.FirstName, oa.LastName FROM (SELECT VALUE oa FROM PEF.vOfficeAddresses AS oa WHERE oa.StateProvince='Ontario') AS add
You can easily break this up for readability, because you are merely building strings, and you can concatenate the queries, as shown in Example 4-37.
Example 4-37. Breaking up a nested query in Entity SQL
subQuery = "SELECT VALUE oa " & _ "FROM PEF.vOfficeAddresses AS oa " & _ "WHERE oa.StateProvince='Ontario'" queryString = _ "SELECT add.FirstName, add.LastName FROM (" & subQuery & ") as add"
subQuery = "SELECT VALUE oa " + "FROM PEF.vOfficeAddresses AS oa " + "WHERE oa.StateProvince='Ontario'" queryString = _ "SELECT add.FirstName, add.LastName FROM (" + subQuery + ") as add"
An Order operator in a subquery will be ignored. The main query controls ordering.
4.6. Grouping Both LINQ and Entity SQL provide operations for grouping data. You can use grouping in connection with aggregates or to shape data. LINQ to Entities has aGroup operator (literally Group By in Visual Basic and Group in C#) and a GroupBy method (with eight overloads). Entity SQL provides a GROUP BY operator and aGroupBy query builder method. The results of the grouping can use automatic naming, and in other cases can be explicitly named. In addition, an INTO
GROUP clause is required in Visual Basic. C# has an optionalINTO clause. The constructs for VB and C# are quite different and it's easiest to explain them with examples. Example 4-38 shows the simplest form of grouping in LINQ for both Visual Basic and C#.
Example 4-38. Simple grouping in LINQ to Entities
From c In context.Contacts Group By c.Title Into Group
from c in context.Contacts group c by c.Title into mygroup
The result of this query is anIQueryable of anonymous types. The anonymous types contain two properties: one contains the title and the other contains the collection of contacts that go with that title. The VB query automatically named the property containing the title as "Title", as shown in Figure 4-5.
Figure 4-5. The VB result, which contains a Title property and a Group property that contains three contacts
By default, C# uses the wordKey as the name of the grouping property and doesn't name the property that contains the
grouped records, as you can see in Figure 4-6.
Figure 4-6. Default C# grouping
VB allows you to specify the property name rather than use the default. In Visual Basic, to change the Title property of the preceding query to MyTitle, you would use the syntaxGroup By MyTitle=Title. In VB, the Group property is available to access the group. You can rename this as well. For example, Into MyGroup =
Group renames the property to MyGroup.
4.6.1. Naming Properties When Grouping The optional INTO clause in C# allows you to specify a group name, but this is not exposed as a property. You specify the name with INTO so that you can perform further functions on the group. Note that in C#, using the INTO clause requires that you also use the SELECT clause. The key property is then accessible as a property of the group. With the group specified, it is now possible to explicitly name the properties in C#. LINQ queries in Visual Basic will imply a
SELECT statement if it is not used. In this case, the query will still return Title and MyGroup by default without specifying SELECT . Of course, you can shape the data further by specifying your own output with an explicit SELECT operator. Example 4-39 demonstrates these changes to the previous queries.
Example 4-39. LINQ Group By with explicitly named groups and targets
From c In context.Contacts _ Group By c.Title Into MyGroup = Group
from c in context.Contacts group c by c.Title into MyGroup
orderby MyGroup.Key select new {MyTitle = MyGroup.Key, MyGroup};
4.6.2. Chaining Aggregates Visual Basic provides a simple way to use aggregates in grouping queries, by specifying one or more aggregates in the INTO clause separated by commas. In Example 4-40 , your result will contain the propertiesMax and Count.
Example 4-40. Chained aggregates in VB LINQ
From con In context.Contacts _ Group By con.Title Into MyGroup = Group, _ Max(con.AddDate), Count()
In C#, you need to explicitly project these properties in the Select clause using methods and predicates, as shown in Example 4-41.
Example 4-41. Combining aggregates in C# LINQ
from c in context.Contacts group c by c.Title into MyGroup orderby MyGroup.Key select new {MyTitle = MyGroup.Key, MyGroup, Max = MyGroup.Max(c => c.AddDate), Count = MyGroup.Count()}
4.6.3. Filtering on Group Conditions There is so much more that you can do with grouping in LINQ. For now, we'll take a look at one more variation: filtering on the grouping condition. The Title fields in the sample data contain Mr., Mrs., Ms., Sr., and a few other titles. Also, some contacts have no title.
Perhaps you would like to group on title, but exclude empty titles. To filter what is being grouped, such as "only group contacts with something in the Title field," you can apply the filter to the control variable,Title , to make sure it contains a value. You may, however, want to filter on a property of theGroup. With LINQ you can continue to use theWHERE operator, as shown in Example 4-42.
Example 4-42. Filtering on a Group property with LINQ
From c In context.Contacts _ Group By c.Title Into MyGroup = Group, Count() _ Where (MyGroup.Count() > 150)
from c in context.Contacts group c by c.Title into MyGroup where MyGroup.Count() > 150 select new { MyTitle = MyGroup.Key, MyGroup, Count = MyGroup.Count()};
In LINQ, you will also need to be aware of variables going out of scope, as in the query shown in Example 4-43, which won't compile. The a in Group by a.CountryRegion is out of scope because by this point in the query, you are working with the anonymous type returned by the Select statement. And theSelect does need to go before theGroup By.
Example 4-43. An out-of-scope variable preventing this query from compiling
From a In context.Addresses _ Select a.Contact.FirstName, a.Contact.LastName, a.CountryRegion _ Group By a.CountryRegion Into MyGroup = Group, Count() _ Where (MyGroup.Count() > 150)
You can avoid this problem by naming the anonymous type, and then grouping by a field within the name, as shown in Example 4-44.
Example 4-44. Naming variables to keep them from going out of scope
From add In PEF.Addresses _ Select c = New With {add.Contact.FirstName, add.Contact.LastName, _ add.CountryRegion} _ Group By c.CountryRegion Into MyGroup = Group
from a in context.Addresses let c= new {a.Contact.FirstName, a.Contact.LastName, a.CountryRegion} group c by c.CountryRegion into MyGroup where (MyGroup.Count() > 150) select MyGroup;
Both the Visual Studio documentation and the ADO.NET Entity Framework documentation and samples can provide you with an astounding array of data shaping that you can perform with Group By/groupby in LINQ, and even then there are still many more.
NOTE See the sidebar More Query Samples for links to these resources.
4.6.4. Grouping in Entity SQL LINQ will spoil you with its grouping capabilities. Like SQL, Entity SQL comes with a lot of rules so that you can convert queries into a command tree and then into the provider's query syntax. For example, in SQL the most commonly encountered rule is that every expression in the SELECT must either be accounted for in the GROUP BY clause or be wrapped in an aggregate. The same is true in Entity SQL, which prevents you from being able to select entire objects in the SELECT clause. However, it is still possible to return entire objects and shape data in Entity SQL by putting the GROUP BY operator into a nested query. First take a look atExample 4-45, which shows some
simple grouping in Entity SQL.
Example 4-45. A simple GROUP BY example in Entity SQL
SELECT c.Title, COUNT(c.Title) FROM PEF.Contacts as c GROUP BY c.Title
The two projected expressions in the SELECT are covered by either theGROUP BY or an aggregate (COUNT). The query returns the following:
[blank] 6 Mr. 255 Ms. 177 Sr. 3 Sra. 2
To group on an expression that is evaluated, such as "Title" + c.Title , the grouping must be explicitly named and that name needs to be used as a projected expression. Example 4-46 shows the Entity SQL syntax for creating an expression and grouping on it in the same query. The expression, EvalTitle , is built in theGROUP BY clause and is used by name in theSELECT.
Example 4-46. Grouping by a calculated expression
SELECT evalTitle,count(c.Title) FROM PEF.Contacts as c GROUP BY "Title: " +c.Title as evalTitle
4.6.4.1. Returning entities from an Entity SQL GROUP BY query Now, let's take a look at how you can return full objects from Entity SQL when using GROUP BY . The trick is in using nested queries. To reproduce the LINQ query that grouped by Title and returned each title with its collection of contacts, you can use a nested query as an expression in the SELECT statement as shown in Example 4-47 . It seems as though the query does not have to follow the rule of being part of the GROUP BY clause or the target of an aggregate.
Example 4-47. An Entity SQL GROUP BY query that returns entities
SELECT TCon.Title, (SELECT c FROM PEF.Contacts as c
WHERE c.Title= TCon.Title) FROM PEF.Contacts as TCon GROUP BY TCon.title
The nested query returns a collection of contacts whose Title property equals the current title being returned by the group. Although this looks like it might do some scary things on the server with respect to the generated SQL, the SQL is similar to the SQL created as a result of the first LINQ query in this section on grouping.
4.6.4.2. Filtering based on group properties You saw that LINQ uses theWHERE clause to filter within a group. In Entity SQL, you can use theHAVING clause for this purpose, as shown in Example 4-48.
Example 4-48. Entity SQL's HAVING clause, which helps with filtering
SELECT groupCon.Title,count(groupCon.ContactID) FROM PEF.Contacts as groupCon GROUP BY groupCon.title HAVING count(groupCon.ContactID)>150
This returns only the title groups that contain more than 150 contacts. The results will be as follows:
Mr. 255 Ms. 177
Like everything else this chapter has covered so far, we have only skimmed the surface of GROUP BY in Entity Framework queries. You will see more uses throughout this book and can find more details (and plenty of rules) in the documentation. The rest of this chapter will explain some important concepts that have been exposed by the queries you've seen so far.
LINQ Compiled Queries and Entity SQL Cached Queries One of the expensive processes of executing queries is in the query compilation. This is when the query is transformed into the proper query to be sent along to the database. LINQ to Entities has a feature called precompilation whereby you can compile a query in advance and access that compiled version as needed. Even if some of the query parameters change, such as searching for LastName="Smith" and then searching for LastName="Holbert" , the precompiled query will be used. This has a huge impact on performance, and Microsoft recommends that you use precompilation for any queries that might be called repeatedly. Entity SQL has the ability to cache its queries and does this by default. The performance benefit is similar to that of using precompiled LINQ queries. Chapter 16 explores both of these features.
4.7. Shaped Data Returned by Queries Whether you write a query that returns entities, anonymous types,DbDataRecord s, or DbDataReader s, you can return shaped data. You've seen this in several of the previous queries, with a variety of shaped results. How you use this data depends on how the data is shaped. Let's take a further look at the results of some of the earlier queries. The LINQ and Object Services queries that returned entities defined in the model are not shaped. They are purely a collection of individual entities.
NOTE Queries can also return graphs of data. In a graph, the related data is contained in the entity, and is not separated out to another field in the results. You'll see examples of this later in this chapter. For instance, Example 4-18 returned an IQueryable of contacts. Example 4-14 , however, returned an anonymous type with two properties. The first property was a Contact entity and the second was a collection ofAddress entities related to that Contact. The code in Example 4-15 enumerated over that data, albeit in a somewhat boring way, to demonstrate what the data looked like. It showed the contacts and the addresses but did not truly demonstrate the relationship between the two. Example 4-49 executes the same query and then enumerates through the anonymous types that result. This time, however, the code accesses the Addresses as a navigation property of theContact.
Example 4-49. LINQ query creating shaped results
Dim contacts = From a In context.Addresses _ Where a.CountryRegion = "Canada" _ Select New With {a, a.Contact} For Each contact In contacts Console.WriteLine("{0}: {1} Addresses", _ contact.Contact.LastName, _ contact.Contact.Addresses.Count()) For Each address In contact.Contact.Addresses Console.WriteLine(" {0} {1}", address.Street1, address.City) Next Console.WriteLine() Next
var contacts = from a in context.Addresses where a.CountryRegion == "Canada" select new { a, a.Contact }; foreach (var contact in contacts) { Console.WriteLine("{0}: {1} Addresses", contact.Contact.LastName, contact.Contact.Addresses.Count()); foreach (var address in contact.Contact.Addresses) { Console.WriteLine(" {0} {1}", address.Street1, address.City); } Console.WriteLine(); }
The WriteLine method doesn't access the a property of the anonymous type, but instead navigates to the addresses through the Contact property of the anonymous type. As the Contact and Address entities are materialized, the Entity Framework recognizes that they are related to each other and wires them up so that you can navigate between them. The Address objects have a Contact object in their
Contact property and the Contact objects have Address objects in their Addresses property. This is a very high-level explanation of an important function of the Entity Framework's Object Services API, which you will learn plenty about throughout the book. There is an interesting thing to be aware of with respect to how the Entity Framework connects the related entities in the scenario laid out in Example 4-49 . If you look at the following sample of the output, you can see that two addresses belong to the contact "Alderson". Both addresses are in Montreal. But the first instance says that Alderson has only one address. Not until the code has reached the second address is the contact aware that two addresses exist in its Addresses navigation collection:
City: Nepean, LastName: Allison LastName: Allison, # Addresses: 1 .....Nepean City: Montreal, LastName: Alderson LastName: Alderson, # Addresses: 1 .....Montreal City: Montreal, LastName: Alderson LastName: Alderson, # Addresses: 2 .....Montreal .....Montreal City: Montreal, LastName: Steelman LastName: Steelman, # Addresses: 1 .....Montreal
The second address isn't recognized initially because it hasn't been materialized as an object yet. As the code enumerates through the query results for the first time, the objects are created from the query results as each contact or address is reached. Once the second address is encountered and turned into an object, its relationship to the contact is identified. We will explore the object life cycle more deeply in a later chapter, but this should give you some idea for now about what's going on in this example.
4.7.1. Shaped Data from Entity SQL As you've seen already, projections in Object Services result inDbDataRecord s, as opposed to the anonymous types that LINQ returns. However, even in these DbDataRecord s, you can still find complete entities and navigate through their associations. You can express the query inExample 4-49 in Entity SQL, as shown in Example 4-50.
Example 4-50. Entity SQL resulting in addresses with their contacts
SELECT a,a.Contact FROM PEF.Addresses AS a WHERE a.CountryRegion='Canada'
This query results in an ObjectQuery of DbDataRecord s that are structured as rows and columns. Each row in this result has two columns (also called fields). An Address entity is contained in the first field and aContact entity is contained in the second field. Figure 4-7 shows the first column of one of theDbDataRecord s in the results. The item is anAddress entity. The second column contains a Contact entity. So, even though it is aDbDataRecord , it still can contain known objects.
Figure 4-7. The first column of each DbDataRecord result, which contains an Address entity
The code in Example 4-51 inspects the Address entity in the first field and theContact entity in the second field. As with the earlier LINQ example, the contacts will not be aware of all of the related addresses until each address has been enumerated over. Also, although the Item property returns objects, thanks to implicitly typed local variables in VB 9 and C# 3, the compiler is able to know that c.Item("add") returns an Address entity and c.Item("Contact") returns a
Contact entity. With the strongly typed variables and the IntelliSense that results, it is easy to work with the objects.
Example 4-51. Enumerating through and reading the shaped data from an ObjectQuery
For Each record In addresses Dim address = CType(record.Item("a"), Address) Console.WriteLine("{0},{1}", _ address.City.Trim, _ address.Contact.LastName) Dim con = CType(record.Item("Contact"), Contact) Console.WriteLine("{0},{1}", con.LastName.Trim, _ con.Addresses.Count()) For Each a As Address In con.Addresses Console.WriteLine("....." & a.City) Next Console.WriteLine() Next
foreach (var record in addresses) { var address = (Address)(record["a"]); Console.WriteLine("{0},{1}", address.City.Trim(), address.Contact.LastName.Trim()); var con = (Contact)(record["Contact"]); Console.WriteLine("{0},{1}", con.LastName.Trim(), con.Addresses.Count()); foreach (Address a in con.Addresses) { Console.WriteLine("....." + a.City);
} Console.WriteLine(); }
The output of this example matches the output of the query inExample 4-49.
4.8. Deferred Loading and Eager Loading Queries So far, all of the queries that involved returning related data have explicitly asked for that data in the query itself. The Entity Framework will only return data that you explicitly ask for. If your query asks only for contacts, the Entity Framework will not make an assumption that just because contacts have addresses, it should return the addresses anytime you query for contacts. Consider a typical model for sales information. A contact is related to a customer; a customer has sales orders; each sales order has line items; each line item relates to a product; each product comes from a vendor and is also related to a category. Can you imagine if you queried for contacts, and without expecting it, the entire contents of the database were returned—because it was all related? It is possible to get related data after the fact. For example, if you queried for a selection of contacts, as you work with those contacts in code you can request the contacts' addresses without performing another complete query. This is referred to asdeferred loading , and although it does not require you to create and execute a new query, it does require the explicit step of calling a Load method.
4.8.1. Deferred Loading Entity Collections with Load The following LINQ to Entities query returns anObjectQuery of Contact entities:
From c In context.Contacts Select c
From c in context.Contacts select c
Because the query does not explicitly request the addresses, the Addresses.Count for every single contact will be zero. What you need to do is tell the Entity Framework to get the addresses for the current contact, as shown in the following code:
For Each contact In contacts contact.Addresses.Load Console.WriteLine(contact.Addresses.Count) Next
foreach (var contact in contacts) { contact.Addresses.Load(); Console.WriteLine(contact.Addresses.Count); }
When Load is called, Object Services will execute a query to retrieve all of the addresses for that contact. In the preceding example, after Load is called, the value ofCount will be correct and all of theAddress entities for that contact will be available.
4.8.1.1. Performance considerations with deferred loading There is a big performance consideration here. For each contact, the code is forcing an extra round trip to the database. If the query returns 40 contacts, one more trip is made to the database for each contact when Load is called. That's 40 round trips to the server in addition to the original trip made to execute the first query. This can be extremely inefficient and might also get you into big trouble with the IT pros in your company.
Load is a great choice in cases where you want to inspect the contact and then load addresses for only particular contacts. Perhaps you want to list all contacts, but for contacts that were added after a particular date you need to see how many addresses are in the database. The code in Example 4-52 demonstrates this scenario, where you may determine it is more efficient to make a small number of database trips rather than preloading addresses for every contact.
Example 4-52. Loading addresses for some of the contacts
For Each contact In contacts Console.WriteLine(contact.LastName) If contact.AddDate > CDate("1/1/2008") Then contact.Addresses.Load() End If Next
foreach (var contact in contacts) { Console.WriteLine(contact.LastName); if (contact.AddDate > System.Convert.ToDateTime("1/1/2008")) contact.Addresses.Load(); }
4.8.1.2. Loading the EntityReference You can also perform deferred loading forEntityReference navigation properties—for example,Address.Contact . However, rather than load from the Contact property, you must load from the additional property that was created:
ContactReference. The Entity Framework seesAddress.Contact as merely a Contact entity, and theContact class does not have the Load method. It is theContactReference property that has the knowledge of how to load the related information. Each EntityReference navigation property will have a related property with the wordReference appended to its name. As a reminder, the additional property is created when the classes are generated from the model. Example 4-53 shows how to loadContactReference data for particular addresses after the addresses have already been queried.
Example 4-53. Loading the Contact with ContactReference.Load
Dim addresses = From a In context.Addresses Select a For Each address In addresses If address.CountryRegion = "UK" Then address.ContactReference.Load() End If Next
var addresses = from a in context.Addresses select a; foreach (var address in addresses) { if (address.CountryRegion == "UK") address.ContactReference.Load(); }
4.8.2. Using the Include Method to Eager-Load In cases where you know you will need all of the addresses up front, it may be more efficient to retrieve them as part of the original query. Although you have seen how to do this with projection by including the addresses in the SELECT clause, the Include method is another way to achieve this and may be preferable for a variety of reasons.
Include is a query builder method and you can apply it to anObjectQuery. Because context.Contacts is an ObjectQuery , you can use Include even within a LINQ query, as shown inExample 4-54.
Example 4-54. The Include method in a LINQ to Entities query
From c In context.Contacts.Include("Addresses") _ Where c.LastName = "Smith" _ Select c
from c in context.Contacts.Include("Addresses") where c.LastName= "Smith" select c
The argument for Include is a string that is the name (or names) of the navigation properties to bring back along with the contacts. This is referred to as eager loading or eager fetching. You can use Include only when returning an ObjectQuery of a single entity type. You cannot use it with projections, and if you do project, Include will be ignored. In the sample model, there is only one navigation property forcontact, which is Addresses . Imagine a sales model with a number of entities and a variety of navigations. You could query customers and eager-load the orders and all of the orders' details by querying Customers.Include("Orders.OrderDetails") . The string is called aquery path because it defines the path that the query should navigate through the model. Additionally, you could eager-load the orders and the customers' addresses by chaining the Include methods like this:
Customers.Include("Orders.OrderDetails").Include("Addresses")
4.8.2.1. How is the data shaped with Include? Data shaping is one of the interesting benefits ofInclude. The previous Contacts.Include("Addresses") query returns a set of Contact entities. This does not have the same effect as projection, which would have to returnDbDataRecord s. Figure 4-8 shows the query results in the debugger's QuickWatch window. You can see that the results are strictly a set of Contact entities. Where are the addresses?
Figure 4-8. The result of the Include with no projections, which returns only the primary entity of the query
Figure 4-9 drills into one of the contacts, and you can see that both of this contact's addresses are there. The Include brings in the related data, and unlike the issue you saw with the addresses not being attached to Mr. Alderson from Montreal until all of the addresses had been enumerated through, all of these addresses are present as soon as you get to the contact.
Figure 4-9. The result of the Include with projections, which returns the contact's related Addresses in the query
4.8.2.2. Accessing properties from an Include in the query You can filter and sort, even when anInclude is involved, though there is one caveat: you cannot limit which records are included. You will always get the entire EntityCollection referenced in the Include.
NOTE Remember that if you do projection in your query,Include will be ignored. You can, however, use the properties of the Include entities in many of the same ways you can use properties of any related data when querying. Example 4-55 uses the CountryRegion field of Address to limit which contacts are retrieved. But be sure you are clear on the results. This will return contacts that happen to have any of their addresses in the United Kingdom. If a contact has multiple addresses and only one of them is in the United Kingdom, you will still get all of those addresses.
Example 4-55. Limiting which contacts are retrieved
From c In context.Contacts.Include("Addresses") _ Where c.Addresses.Any(Function(a) a.CountryRegion = "UK")
from c in context.Contacts.Include("Addresses") where c.Addresses.Any((a) => a.CountryRegion == "UK") select c
4.8.3. Using Include with an ObjectQuery How would you apply Include when creating an ObjectQuery directly rather than using LINQ to Entities?
Include is a query builder method and you can use it in the same manner as other query builder methods. You can add it to CreateQuery methods or to an ObjectQuery returned by a CreateQuery. Example 4-56 shows how to apply Include when using CreateQuery.
Example 4-56. The Include method in an Object Services query with Entity SQL
Dim str = "SELECT VALUE c FROM PEF.Contacts as c " Dim contacts = context.CreateQuery(Of Contact)(str).Include("Addresses")
var str = "SELECT VALUE c FROM PEF.Contacts as c "; var contacts = context.CreateQuery(str).Include("Addresses");
The same rule applies for projections when using Entity SQL withInclude . If you project in your query,Include will be ignored. It is able to work only when complete entities are involved.
Pay attention to JOIN queries. If you use Include in a query that also has aJOIN, the
Include will be discarded—no warnings, no compiler errors. Try a nested query instead, but validate your results.
When using the Include method to eager-load entity references, use the navigation property for that property name (Contact), not the EntityReference property (ContactReference) as with the followingObjectQuery:
Dim querystring = "SELECT VALUE add " & _ "FROM PEF.Addresses as add" Dim addresses = PEF.CreateQuery(Of Address)(querystring) _ .Include("Contact")
var querystring = "SELECT VALUE add " + "FROM PEF.Addresses as add" var addresses = PEF.CreateQuery(querystring) .Include("Contact")
Just as you saw when usingInclude to load entity collections, an entity object will be returned, not an anonymous type, and the entity reference is loaded.
4.8.4. Pros and Cons of Load and Include You have some things to consider when choosing between theLoad and Include methods. Although the Load method may require additional round trips to the server, the Include method may result in a large amount of data being streamed back to the client application and then processed as the data is materialized into objects. This would be especially problematic if you are doing all of this work to retrieve related data that may never even be used. As is true with many choices in programming, this is a balancing act that you need to work out based on your particular scenario. The documentation also warns that using query paths withInclude could result in very complex queries at the data store because of the possible need to use numerous joins. The more complex the model, the more potential there is for trouble. You could certainly balance the pros and cons by combining the two methods. For example, you can load the customers and orders with Include and then pull in the order details on an as-needed basis withLoad. The correct choice will most likely change on a case-by-case basis.
NOTE There are a few methods for retrieving a single entity without even writing a query. They are called GetObjectByKey and GetObjectStateEntry . These methods have counterparts calledTryGetObjectByKey and
TryGetObjectStateEntry . The methods exist in another Entity Framework class, and you will learn more about GetObjectByKey later in this chapter and the rest inChapter 17.
4.9. Retrieving a Single Entity All of the queries so far have returned sets of data. What if you wanted to retrieve a single entity or a single result? The queries return IQueryable s or ObjectQuery and you need to dig into those to get at the actual data, which might be entities, anonymous types, or DbDataRecord s. This is reasonable if you are returning multiple items, but what about cases where you query for one particular item—for example, the contact whose ContactID is 63 —and you don't want to have anIQueryable returned, but just the item? LINQ to Entities has a pair of methods,First and FirstorDefault , which will return the first item in the result set. These methods are not specific to LINQ to Entities, but come from LINQ and may be familiar to you already. Example 4-57 shows two techniques for using these methods. In the first technique, a query is defined and in another line of code the First method is called. This will cause the query to be executed and the contact entity to be returned. The second technique appends the First method directly to the query. Even thoughFirst is a method, you can combine it with the query operator syntax by wrapping the query in parentheses. In this case, the query is executed immediately and the contact is returned.
Example 4-57. Querying with the First method
Dim contacts = From c In context.Contacts Where c.ContactID = 63 Dim contact = contacts.First Console.WriteLine(contact.LastName) Dim singleContact = _ (From c In context.Contacts Where c.ContactID = 24 Select c).First Console.WriteLine(SingleContact.LastName)
var contacts = from c in context.Contacts where c.ContactID == 63 select c; var contact = contacts.FirstOrDefault(); Console.WriteLine(contact.LastName); var singleContact = (from c in context.Contacts where c.ContactID == 24 select c).First(); Console.WriteLine(SingleContact.LastName);
There's a potential problem here. If there are no items,First will throw an InvalidOperationException with the message "Sequence contains no elements". FirstorDefault protects you from the exception by returning the default, which is generally a null (Nothing in VB). In Example 4-58, FirstorDefault is used to avoid an exception being thrown.Contact in this case will beNothing/null after the query is executed.
Example 4-58. Using FirstorDefault to avoid an exception
Dim contact = _ (From c In context.Contacts Where c.ContactID = 7654321).FirstOrDefault
var contact = (from c in context.Contacts where c.ContactID == 7654321 select c).FirstOrDefault();
NOTE If you were wondering about the Single and SingleOrDefault LINQ methods, these are not supported by LINQ to Entities.
4.10. Retrieving a Single Entity with GetObjectByKey The ObjectContext.GetObjectByKey method and its counterpart,TryGetObjectByKey , provide a way to query for an object without having to construct and execute a query. However, this has a notable twist. The runtime will first look in the existing instantiated objects to see whether the object has already been retrieved. If it is found, this is what will be returned. If not, the query to the data store will be executed automatically and the object will be returned.
GetObjectByKey takes an EntityKey type that defines what object to retrieve based on itsEntitySet , its key property name, and the value of that property. For example, EntityKey("PEF.Contacts","ContactID",5) defines an object in the Contacts EntitySet with a ContactID value of 5. Once the EntityKey has been created, GetObjectByKey(myEntityKey) will return the object either from memory or from the database. TryGetObjectByKey uses the .NET Try pattern to avoid returning an exception if the object is not found in memory or in the database. You will see both of these used many times in later chapters, and you will learn all about the EntityKey class in Chapter 9.
4.11. Entity SQL's Wrapped and Unwrapped Results There is one last concept to highlight before finishing this chapter and moving on: understanding when Entity SQL queries will return rows containing values, or just values. By default, queries using Entity SQL (ObjectQuery and EntityClient queries) return rows. The rows are contained in the ObjectQuery that results, or in theEntityClient's DbDataReader . When the data is pulled out of the row as part of the query process, this is referred to as unwrapping. Then, rather than a row, theObjectQuery and DbDataReader will contain the returned value. You just saw the use of the First and FirstorDefault methods to return a single object, rather than anIQueryable , which would then need to be enumerated through to get at the object. Conceptually, Entity SQL queries that unwrap results are doing the same. Unwrapping is possible only when a single value is returned in theObjectQuery or DbDataReader . An Entity SQL query will return rows with the same number of columns as items listed in the projection, regardless of what type the item is—a string, an entity, or even a collection. Take, for example, a simple projection of names as shown in Table 4-1, or a projection that returns shaped data. Table 4-2 shows rows, each containing three strings and anEntityCollection. Each row in the results of Table 4-3 contains an entity and anEntityCollection. Note that the rows in the tables represent a DbDataRecord type.
Table 4-1. A simple projection of names Column 1
Column 2
Column 3
Row 1
Mr.
John
Doe
Row 2
Sr.
Pablo
Rojas
Row 3
Mrs.
Olga
Kolnik
Table 4-2. Rows containing three strings and an EntityCollection
Row 1
Column 1
Column 2
Column 3
Column 4
Mr.
John
Doe
Address entity Address entity
Row 2
Sr.
Pablo
Address entity
Rojas
Address entity Row 3
Mrs.
Olga
Address entity
Kolnik
Address entity
Table 4-3. Rows containing an entity and an EntityCollection
Row 1
Column 1
Column 2
Contact entity
Address entity Address entity
Row 2
Column 1
Column 2
Contact entity
Address entity Address entity
Row 3
Contact entity
Address entity Address entity
Because neither Object Services nor EntityClient can return anonymous types, the only way to return these multicolumn rows is to wrap them in rows where the values are contained in columns. Once you have the result set in memory, you can extract the entities or values programmatically and interact with them as you have done in this chapter and the previous chapter. However, consider a query with only one value being returned in each row. By default, you will still get DbDataRecord a , and that value will be the first and only column of the row (see Table 4-4).
Table 4-4. Contact entities that are contained within rows Column 1 Row 1
Contact entity
Row 2
Contact entity
Row 3
Contact entity
By adding the VALUE keyword (SELECT VALUE … ), you're signaling that you want the value to be unwrapped. With Object Services, this will result in an ObjectQuery of Contact entities. As you have seen, you must specify the proper type for the ObjectQuery. This could be one of theEntityObject types defined in your model, or some other type, such as a string or an integer. Look at the difference in how you need to work with the results when the contact is wrapped (Example 4-59) and unwrapped (Example 4-60 ). When it's wrapped you still need to cast the value in the first column (Item(0) ) to a contact before you can work with the contact, even though it's the only value in the result.
Example 4-59. Wrapped contact
Dim esql = _ "SELECT c FROM PEF.Contacts as c WHERE c.FirstName='Robert'" Dim wrappedContacts = context.CreateQuery(Of DbDataRecord)(esql) For Each record In wrappedContacts Dim contact = CType(record.Item(0), Contact) Console.WriteLine(contact.LastName) Next
var esql = "SELECT c FROM PEF.Contacts as c WHERE c.FirstName='Robert'"; var wrappedContacts = context.CreateQuery(esql); foreach (var record in wrappedContacts) { var contact = (Contact)(record[0]); Console.WriteLine(contact.LastName);
Example 4-60. Unwrapped contact
Dim esql = _ "SELECT VALUE c FROM PEF.Contacts as c WHERE c.FirstName='Robert'" Dim unwrappedContacts = context.CreateQuery(Of Contact)(esql) For Each contact In unwrappedContacts Console.WriteLine(contact.LastName) Next
var esql = "SELECT VALUE c FROM PEF.Contacts as c WHERE c.FirstName='Robert'"; var unwrappedContacts = context.CreateQuery(esql); foreach (var contact in unwrappedContacts) Console.WriteLine(contact.LastName); }
4.11.1. Entity SQL Rules for Wrapped and Unwrapped Results Here are some rules to remember for Entity SQL queries: Use SELECT when projecting more than one value. When querying with SELECT, the ObjectQuery type must be a DbDataRecord . You can use SELECT VALUE when projecting a single value or entity. When querying with SELECT VALUE, the ObjectQuery type must be the same type as the value being returned. Breaking any of these rules will result in a runtime exception when the Entity Framework attempts to generate the store's SQL from the Entity SQL or when the data is returned and the Entity Framework is trying to align the returned type with the type defined for the ObjectQuery.
4.11.2. Digging a Little Deeper into EntityClient's Results Because EntityClient streams results and does not materialize records, you won't get entity objects. However, the data that results will be shaped based on the entity shape, and therefore, as you saw in some of the earlier examples, you can cast the results back to the appropriate entity. You can also cause the results to be wrapped or unwrapped. Remember that DbDataRecord s can contain nested DbDataRecord s, or even nested DbDataReader s, which is how it's possible to shape the results. Here are a variety of different queries and the results to expect in EntityClient: Query projecting two simple values:
SELECT c.FirstName,c.LastName FROM PEF.Contacts AS c
Each row of the DataReader that results is aDbDataRecord with two columns . Each column contains a string. Query projecting a single value that is an entity:
SELECT VALUE c FROM PEF.Contacts AS c
Each row of the DataReader that results is aDbDataRecord with one column. The column contains an
IExtendedDataRecord, which is a type of aDbDataRecord. There is one column for every property in a Contact entity, filled with the relevant data. Complex query projecting an entity and a collection of entities:
SELECT c, c.Addresses FROM PEF.Contacts AS c
Each row of the DataReader that results is aDbDataRecord . There are two columns: the first contains an
IExtendedDataRecord with one column for each property of theContact entity, and the second contains a whole DbDataReader that implements IExtendedDataRecord. This allows the data to be cast to an EntityCollection of address types. Query projecting a single entity using SELECT VALUE:
SELECT VALUE c FROM PEF.Contacts AS c
Each row of the DataReader that results is anIExtendedDataRecord. There is one column for every property of the Contact entity, filled with the relevant data. Query projecting a single simple type usingSELECT VALUE:
SELECT VALUE c.LastName FROM PEF.Contacts AS c
Each row of the DataReader that results is a string.
NOTE The ADO.NET documentation has a great example of reading a DbDataReader and handling any of these data types as you hit them. Look for the MSDN Library topic "How to: Execute an Entity SQL Query Using EntityCommand (Entity Framework)."
4.12. Summary With LINQ to Entities, Entity SQL, Object Services, andEntityClient , the Entity Framework provides myriad possibilities for querying data and shaping results. Although it would take a few hundred more pages to ensure that you have seen an example of almost any type of query you may want to write, these past two chapters should leave you very prepared to venture forth. In the next few chapters, you will learn about updating the data that you have queried and taking advantage of stored procedures. Beginning with Chapter 8 , you will start to write some solutions and be able to leverage many of these types of queries.
Chapter 5. Modifying Entities and Saving Changes So far, we have focused on many ways to retrieve data from the database through the Entity Data Model (EDM). This is only part of the Entity Framework story and the beginning of the life cycle of an entity. Once you have retrieved entities you can modify them, delete them, or even add new ones and then save all of these changes back to the database. In this chapter, we'll take a high-level look at the default way in which the Entity Framework tracks these changes and performs updates. This will prepare you for most of what you will be learning throughout the rest of the book, whether you are using high-level data-binding features or working deeply under the covers.
NOTE In the next chapter, you'll learn about overriding the default behavior for both querying and updating by using stored procedures.
5.1. How ObjectContext Manages Entities In Chapters Chapter 3 and Chapter 4, you used an ObjectContext, the ProgrammingEFDB1Entities class, which inherits from ObjectContext , to create and execute queries. You also worked with the objects that were returned by those queries, whether they were entities, anonymous types, or objects within a DbDataRecord . The nature of this interaction was to iterate through the objects and extract a few properties to display in the console windows. Those objects were created by an internal process calledobject materialization , which takes the returned data and builds the relevant objects for you. Depending on the query, these could be EntityObjects, anonymous types, or
DbDataRecords. By default, for anyEntityObjects that are materialized, theObjectContext creates an extra object behind the scenes, called an ObjectStateEntry. It will use theseObjectStateEntry objects to keep track of any changes to their related entities. If you execute an additional query using the same context, more ObjectStateEntry objects will be created for any newly returned entities and the context will manage all of these as well. The context will keep track of its entries as long as it remains in memory. The ObjectContext can track only entities. It cannot keep track of anonymous types or nonentity data that is returned in a DbDataRecord .
NOTE You will learn about object materialization, ObjectStateEntry , change tracking, and more in great detail inChapter 9.
5.1.1. Remembering Original Values and Keeping Track of Changes ObjectStateEntry takes a snapshot of an entity's values as it is first created, and then stores the original values and the current values as two separate sets. ObjectStateEntry also has an EntityState property whose value reflects the state of the entity (Unchanged , Modified, Added, Deleted). As the user modifies the objects, theObjectContext updates the current values of the related ObjectStateEntry as well as its EntityState. As you learn more about the Entity Framework, you'll discover how to locate and inspect the details of an ObjectStateEntry.
NOTE The object itself also has an EntityState property. As long as the object is being managed by the context, its
EntityState will always match theEntityState of the ObjectStateEntry . If the object is not being managed by the context, its state is Detached. Entities have two different types of properties: scalar properties and navigation properties. ObjectStateEntry keeps track of only the scalar values of its related entity. The navigations are tracked in a very different way that is out of scope for this high-level overview but that you will learn quite a lot about in Chapter 9 as well as in Chapter 15 , which focuses on relationships and associations. As the scalar properties are changed—for example,Contact.LastName—the new value of LastName is stored in the ObjectStateEntry's set of current values for that contact, and if theObjectStateEntry.EntityState value was Unchanged at the time of the modification, its value will be set toModified.
5.2. The SaveChanges Method ObjectContext has a single method, SaveChanges , which persists back to the database all of the changes made to the entities. A call to SaveChanges will check for any ObjectStateEntry objects being managed by that context whose EntityState is not Unchanged , and then will use its details to build separateInsert, Update, and Delete commands to send to the database. We'll start by focusing on entities that have come into the context as a result of queries and have been modified. Example 5-1 shows a simple ObjectQuery to retrieve the first contact from theContacts EntitySet. Remember that
context.Contacts is a method that will return anObjectQuery of Contact types. The example then uses the LINQ method First to pull back only the first result. The FirstName and ModifiedDate properties are given new values, and then SaveChanges is called.
Example 5-1. Querying for a contact, editing, and then saving back to the database
Using context As New PEF Dim contact = context.Contacts.First() contact.FirstName = "Julia" contact.ModifiedDate = DateTime.Now context.SaveChanges End Using
using (PEF context = new PEF()) { var contact = context.Contacts.First(); contact.FirstName = "Julia"; contact.ModifiedDate = DateTime.Now; context.SaveChanges(); }
Looking at the SQL Profiler, you can see the following parameterizedUpdate command, which was sent to the database when SaveChanges was called:
exec sp_executesql N'update [dbo].[Contact] set [FirstName] = @0, [ModifiedDate] = @1 where ([ContactID] = @2)
',N'@0 nchar(5),@1 datetime2(7),@2 int', @0=N'Julia',@1='2008-11-22 18:12:18.7210000',@2=1
This command updates the Contact table, setting theFirstName and ModifiedDate properties for theContact whose
ContactID is 1. The values are passed in via parameters, and the last parameter,@2 , shows the value used for the ContactID. By comparing the original values in the ObjectStateEntry for that Contact entity to the current values, the
ObjectContext determined that only theFirstName and ModifiedDate properties had changed, and therefore those are the only values that it sends into the command. It uses the value of the property that is marked as the EntityKey, ContactID , to identify which row to update. Let's see what happens when we have more than one entity. Example 5-2 queries for all of those Roberts once again, including their addresses, and returns a List of the entity graphs: Contacts with Addresses . The example then randomly selects one of these contacts and changes its
FirstName to Bobby. Another contact is selected and theStreet property of the first Address is edited. Finally, SaveChanges is called.
Example 5-2. Editing various entities and calling SaveChanges
Dim contacts = context.Contacts.Include("Addresses") _ .Where(Function(c) c.FirstName="Robert").ToList() Dim contact = contacts(3) contact.FirstName = "Bobby" contact = contacts(5) Dim address = contact.Addresses.ToList(0) address.Street1 = "One Main Street" context.SaveChanges
var contacts = context.Contacts.Include("Addresses") .Where(c =>c.FirstName=="Robert").ToList(); var contact = contacts[3]; contact.FirstName = "Bobby"; contact = contacts[5]; var address = contact.Addresses.ToList()[0]; address.Street1 = "One Main Street"; context.SaveChanges();
Initially, 12 contacts and 13 addresses were retrieved. Let's look at the SQL commands sent to the database when
SaveChanges is called: exec sp_executesql N'update [dbo].[Address] set [Street1] = @0 where ([addressID] = @1) ',N'@0 nchar(15),@1 int',@0=N'One Main Street',@1=2418 exec sp_executesql N'update [dbo].[Contact] set [FirstName] = @0 where ([ContactID] = @1) ',N'@0 nchar(5),@1 int',@0=N'Bobby',@1=288
The first command sent to the database updates the singleAddress that was modified, and only itsStreet value and identity, AddressID , are included. Next, the command to update the contact was sent. None of the other entities was modified, so the ObjectContext doesn't bother to construct or send any commands for those entities. The call to
SaveChanges is very efficient in this aspect.
ObjectContext learned everything it needed to know to create these commands, not by looking at the Contact and Address objects that it was managing but by looking at the ObjectStateEntry objects that it was maintaining for each of the 12 Contact and 13 Address entities. ObjectContext first checked the EntityState to see whether anything needed to be processed. Because the EntityState for the untouched entities wasUnchanged , it ignored them. For the two that were Modified , it compared the original values to the current values to determine what properties needed to be included in the Update command. When the update completes, the two entities will be refreshed so that theirEntityState is Unchanged , and the original values will be set to match the current values.
5.2.1. From Entity Framework Command to Native Command In between the call to SaveChanges and the execution of SQL commands in the database, the Entity Framework did a lot of work under the covers to construct the command. The process is similar to how the commands and queries are compiled and converted into store queries. As noted earlier, the first step in the process is to inspect all of the ObjectStateEntry objects for the entities that the context is managing. Those that have an EntityState of Unchanged are ignored. TheModified entities that you worked with earlier, as well as any that are Added or Deleted , are processed by the context. As the commands are built, the model's metadata (conceptual, store, and mapping layers) are read and the mapping information is used to translate the entities and their properties into table and column names. The mappings also provide the knowledge to move from model relationships to database foreign keys. The ADO.NET provider, such as SqlClient , does the final job of constructing the appropriate native command. You'll look more closely at this process in later chapters.
5.3. Adding New Entities Now that you have an idea of how edits are handled, let's look at entities that are newly created in your application and then sent to the database as part of SaveChanges . In Example 5-3 , a new address is created in memory. Then, after attaching the address to a contact that was queried from the database, SaveChanges is called.
NOTE There are many different ways to link entities to one another based on particular scenarios. You will learn about this in Chapter 15.
Example 5-3. Creating a new address in memory
Dim contact = context.Contacts.Where(Function(c) c.FirstName = "Robert").First() Dim address = New Address() With address .Street1 = "One Main Street" .City = "Burlington" .StateProvince = "VT" .AddressType = "Business" .ModifiedDate = DateTime.Now 'join the new address to the contact .Contact = contact End With context.SaveChanges()
var contact = context.Contacts.Where(c => c.FirstName == "Robert").First(); var address = new Address(); address.Street1 = "One Main Street"; address.City = "Burlington"; address.StateProvince = "VT"; address.AddressType = "Business"; address.ModifiedDate = DateTime.Now;
//join the new address to the contact address.Contact = contact; context.SaveChanges();
When the newly created address is joined with the contact, becauseObjectContext is managing the contact the context will recognize that it needs to create a new ObjectStateEntry for the address. Because the address has no identity key, its EntityState will be set toAdded. When SaveChanges is called, because theEntityState is Added an Insert command is constructed and sent to the database. Here is that command:
exec sp_executesql N'insert [dbo].[Address]([Street1], [Street2], [City], [StateProvince], [CountryRegion], [PostalCode], [AddressType], [ContactID], [ModifiedDate]) values (@0, null, @1, @2, null, null, @3, @4, @5) select [addressID] from [dbo].[Address] where @@ROWCOUNT > 0 and [addressID] = scope_identity()', N'@0 nchar(15),@1 nchar(10),@2 nchar(2),@3 nchar(8),@4 int,@5 datetime2(7)', @0=N'One Main Street',@1=N'Burlington',@2=N'VT',@3=N'Business', @4=209,@5='2008-11-22 20:19:58.7090000'
5.3.1. Breaking Down the Native Insert Command The preceding parameterized command does a number of notable things. First, it has an Insert command that inserts a new address using the values of each property of the entity. Notice that even though the code did not set all of the properties, the command uses all of the properties and inserts defaults, in this case null, where the properties weren't explicitly set in the code. The fifth line down is the beginning of aSelect command. In addition to inserting the new address, the command will return to the application the primary key value that the database generated for the new address. As part of the call to SaveChanges , the new address in the application code will receive itsAddressID from the database so that you can continue working with it in code if you wish. The last thing to point out in the command is that one of the fields in the Insert command is the ContactID. Remember that the Address entity does not have a ContactID property. The relationship betweenContact and Address is through the navigation properties, and it is the mappings that interact with the ContactID foreign key value. When
SaveChanges is called, part of the process that creates the native command identifies the relationship and uses the mappings in the model to determine that the ContactID of the related contact needs to be used for theContactID field of the address being inserted into the database. When the insert completes, not only will the address in memory have its newAddressID value, but like the update in the preceding section, the entity will be refreshed and its EntityState will be set toUnchanged .
5.4. Inserting New Parents and Children The preceding example inserted a new address to an existing contact. What if you wanted to create a new contact with a new address? In typical data access scenarios, you would have to first insert the new contact, retrieve its ContactID , and then use that to insert the new address. SaveChanges does all of this for you when it sees that both are new and that they are related. It also uses the model's mappings to figure out which is the dependent entity (in this case, Address ) and needs the foreign key C ( ontactID ). With this information, it executes the database inserts in the correct order. The code in Example 5-4 creates a new contact on the fly using theContact class's CreateContact factory method.
NOTE Recall that the model's code generator creates a factory class for everyEntityObject , which uses all of the non-nullable properties as its arguments. The example then creates a new address in the same manner as withExample 5-3 . Next, it joins the new contact to the new address. At this point, the context has no knowledge of these new entities; therefore, they need to be added to the context. Because the entities are joined, you can add either entity, and it will bring along the rest of the graph. So in this case, the contact is added explicitly and the address is pulled into the context along with the contact.
NOTE
AddToContacts is a method that the model's code generator created. The generated context class has an AddTo method for each entity in the model, as well as a generic AddObject method. You'll learn more about adding and attaching entities to the context and to each other in Chapter 15 . You will also see a variety of examples of these methods in many of the samples throughout the book. Finally, SaveChanges is called.
Example 5-4. Inserting a new contact with a new address
Dim contact = Contact.CreateContact _ (0, "Camey", "Combs", DateTime.Now, DateTime.Now) Dim address = New Address() address.Street1 = "One Main Street" address.City = "Olympia" address.StateProvince = "WA" address.AddressType = "Business" address.ModifiedDate = DateTime.Now 'join the new address to the contact address.Contact = contact 'add the new graph to the context
context.AddToContacts(contact) context.SaveChanges
var contact = Contact.CreateContact (0, "Camey", "Combs", DateTime.Now, DateTime.Now); var address = new Address(); address.Street1 = "One Main Street"; address.City = "Olympia"; address.StateProvince = "WA"; address.AddressType = "Business"; address.ModifiedDate = DateTime.Now; //join the new address to the contact address.Contact = contact; //add the new graph to the context context.AddToContacts(contact); context.SaveChanges();
As the entities are added to the context, the context creates a new ObjectStateEntry for each one. Because they are being added and have no identity key, the EntityState for each is Added. SaveChanges handles these as it did with the previous insert, except that it also takes care of using the contact's new ContactID when inserting the address. The following SQL is the result of the call toSaveChanges . There are two commands. The first command inserts the new contact and does a Select to return the new contact'sContactID. The second command inserts the new address, and as you can see in the second-to-last line, the @4 parameter has a value of 709 . This is the newContactID . This command also selects the new address'sAddressID value to return to the application:
exec sp_executesql N'insert [dbo].[Contact]([FirstName], [LastName], [Title], [AddDate], [ModifiedDate]) values (@0, @1, null, @2, @3) select [ContactID] from [dbo].[Contact] where @@ROWCOUNT > 0 and [ContactID] = scope_identity()',N'@0 nchar(5), @1 nchar(5),@2 datetime2(7),@3 datetime2(7)', @0=N'Camey',@1=N'Combs',@2='2008-11-23 08:14:41.6420000', @3='2008-11-23 08:14:41.6420000' exec sp_executesql N'insert [dbo].[Address]([Street1], [Street2], [City], [StateProvince], [CountryRegion], [PostalCode], [AddressType], [ContactID], [ModifiedDate]) values (@0, null, @1, @2, null, null, @3, @4, @5)
select [addressID] from [dbo].[Address] where @@ROWCOUNT > 0 and [addressID] = scope_identity()', N'@0 nchar(15),@1 nchar(7),@2 nchar(2),@3 nchar(8),@4 int,@5 datetime2(7)', @0=N'One Main Street',@1=N'Olympia',@2=N'WA',@3=N'Business',@4=709, @5='2008-11-23 08:14:41.6470000'
As you build more complex models later in the book, you will see how the insert can handle various types of entities with data that is related through navigation properties. In addition, with other types of mappings, such as inheritance, you will see entities that map back to multiple database tables and even entities in a many-to-many relationship.
5.5. Deleting Entities The last type of modification to look at is deleting entities. The Entity Framework has a very specific requirement for deleting data: it must have an entity in hand in order to delete it from the database. ObjectContext has a DeleteObject method that takes an EntityObject as a parameter—for example, an instance of aContact. When DeleteObject is called, the context sets the EntityState of that object'sObjectState Entry to Deleted. When SaveChanges is called, the context notes the Deleted EntityState and constructs aDelete command to send to the database. If the entity has already been retrieved from the database, this will not pose a problem. But sometimes you might want to delete data from the database that has not been queried. Unfortunately, there is no way with the Entity Framework to delete data in the database directly unless you use your own stored procedures. Therefore, even in this case, you will need to first query for the object and then delete it.
NOTE Although you will work with stored procedures in the next chapter, this particular usage is more advanced and will be covered in Chapter 13. Example 5-5 demonstrates the scenario where the contact to be deleted has not yet been retrieved. It uses the GetObjectByKey method described in Chapter 4 to retrieve the contact. Here you can also see how an EntityKey is constructed on the fly using the strongly typedEntitySet name (which includes the name of the EntityContainer, PEF), the name of the property that is theEntityKey, and the value of the key. Therefore, the EntityKey is for a Contact whose ContactID is 438. The EntityKey is passed into theGetObjectByKey method, which will first inspect the existingEntityObject s being managed by the context to see whether that contact has already been retrieved. If it is not found there, the context will create and execute a query to retrieve that contact from the data store. Once the contact is in hand, it is passed into theDeleteObject method, which marks it for deletion by setting the
EntityState to Deleted. Example 5-5. Retrieving and deleting a contact entity
Dim contactKey = New EntityKey("PEF.Contacts", "ContactID", 438) Dim contact = context.GetObjectByKey(contactKey) context.DeleteObject(contact) context.SaveChanges
var contactKey = new EntityKey("PEF.Contacts", "ContactID", 438); var contact = context.GetObjectByKey(contactKey); context.DeleteObject(contact); context.SaveChanges();
Here is the Store command that GetObjectByKey executed, as well as theDelete command that was executed as a result of the call to SaveChanges :
exec sp_executesql N'SELECT [Extent1].[ContactID] AS [ContactID], [Extent1].[FirstName] AS [FirstName], [Extent1].[LastName] AS [LastName], [Extent1].[Title] AS [Title], [Extent1].[AddDate] AS [AddDate], [Extent1].[ModifiedDate] AS [ModifiedDate] FROM [dbo].[Contact] AS [Extent1] WHERE [Extent1].[ContactID] = @p0',N'@p0 int',@p0=438 exec sp_executesql N'delete [dbo].[Contact] where ([ContactID] = @0)',N'@0 int',@0=438
The Delete command simply passes in theContactID to delete the appropriate data.
5.6. Summary This chapter provided you with a high-level overview of how the Entity Framework creates the necessary Insert, Update, and Delete commands to store your changes to the database with a single call toSaveChanges . This is the default behavior of the Entity Framework and one of its core features. However, you are not bound to the default behavior. It is possible to override this mechanism to leverage your own stored procedures. The Entity Framework has a number of ways to use stored procedures. The next chapter will introduce you to overriding the dynamic generation of Insert, Update, and Delete commands with your own stored procedures, as well as show you how to use stored procedures to query data. You also can work with stored procedures that the Designer does not support as easily. We will cover these more advanced techniques in Chapter 13.
Chapter 6. Using Stored Procedures with the EDM Many databases use stored procedures to perform predefined logic on database tables. Although one of the key features of the Entity Framework is its ability to automatically build native commands based on your LINQ to Entities or Entity SQL queries, as well as the commands for inserting, updating, or deleting data, you may want to override these steps and use your own predefined stored procedures. Although the dynamically built commands are secure, efficient, and generally as good as or better than those you may write yourself, there are many cases where stored procedures already exist and your company practices may restrict direct use of the tables. Alternatively, you may just want to have explicit control over what is executed on the store and prefer to create stored procedures. The sample database includes four stored procedures that we skipped in our discussion of model creation in Chapter 2 . In this chapter, you will update the model, pulling in those four stored procedures, implementing them in the model, and interacting with them in some code. In this chapter, you will override the Entity Framework's command generation feature for a particular entity and direct it to use your stored procedures instead when SaveChanges is called. The end of the chapter will also address the concept of combining entities that map to database views with stored procedures to provide fully functional entities to completely avoid direct database access. This chapter will focus on the stored procedures functionality that the Entity Data Model (EDM) Designer readily supports. In Chapter 13 , we will work with stored procedures that are not so easily implemented.
6.1. Adding the Stored Procedures into the Model The EDM tools provide a feature called Update Model from Database, which is available from the Designer context menu. You can use it to add previously skipped database objects or those that have been added to the database since the time you originally created the model. Update Model from Database can also recognize new fields added to tables that have already been mapped in the database. You'll spend a little more time with this tool at the end of the chapter, but for now, let's dive right into it to bring in those stored procedures that we overlooked in Chapter 2. To update the model, start by right-clicking anywhere in the Model Browser and selecting Update Model from Database. This will open the Update Model Wizard, which instructs you to Choose Your Database Objects. You can expand only the Stored Procedures node because there are no tables or views in the database that aren't already in your model. The list of database objects available in this view is not based on which entities you have created, but the fact that those tables and views are represented in the Store Schema Definition Layer (SSDL) portion of the model. Because you did not include the stored procedures when you first built the model, they are not represented in the SSDL, and therefore the Update Model from Database tool sees them as being new.
NOTE If you had added new tables and views to the database, you would see them listed here as well. The Stored Procedures node will list procedures and user-defined functions in the database.
NOTE Version 1 of the Entity Framework supports scalar user-defined functions (UDFs), but not table-valued UDFs. Checking the Stored Procedures checkbox will automatically select all of the available procedures. You can expand the node to see what's there or to individually select the objects you want to use. For this example, you'll want all four procedures: CustomersbyState, DeleteContact , InsertContact , and UpdateContact, as shown in Figure 6-1.
Figure 6-1. Selecting database objects that aren't already contained in your model
The wizard has two additional tabs: Refresh and Delete. These tabs will display which existing items in the model will be refreshed and, if any of the database items have been deleted, which items will be deleted from the model. These two tabs are read-only.
Click Finish to add the stored procedures to the model. When the update is complete, the model will not look any different when viewed in the Designer. Stored procedures are not automatically added to the conceptual layer of the model. Instead, they have been represented in the SSDL as function elements. It will be your job to define how these functions should be implemented in the model using mapping.
6.2. Working with Functions In the model, a Function represents either a stored procedure or a UDF in the database.Example 6-1 lists the four functions that were created in the SSDL to represent the four stored procedures in the sample database.
Example 6-1. Functions created in the SSDL
Each of these four functions represents a different stored procedure in the database. The first function,
CustomersbyState, represents the following T-SQL query: PROCEDURE [dbo].[CustomersbyState] @statecode nchar(50) AS SELECT DISTINCT contact.* from [Contact],[Address] WHERE [Contact].[ContactID]=[Address].[contactID] AND [Address].[StateProvince]=@statecode
The Insert, Update, and Delete procedures perform the changes you would expect to the database.
6.2.1. Function Attributes Most of the function attributes align with attributes that are common to database procedures. Because the SSDL is describing the data store, these attributes are applied in the model so that the Entity Framework API will have a thorough description of the procedures.
Aggregate, BuiltIn, and NiladicFunction are attributes that apply to UDFs, not stored procedures. For stored procedures, they will always be false . Because these are optional and false by default, they are not even required here. If you were adding functions manually for stored procedures, you wouldn't even need to use these, but the wizard inserts them.
NOTE What the heck does niladic mean anyway? Niladic is a mathematical term meaning that the function takes no input parameters. SQL Server's GetDate() is an example of a niladic function.
IsComposable refers to whether you can use the results of the function in another query. This must always be false for stored procedures. The ParameterTypeSemantics attribute refers to the input parameter,statecode. The AllowImplicitConversion enum (which is the default) merely means that the data type input can be converted implicitly to a store provider data type if necessary. For example, if an integer is passed into this parameter, the Entity Framework will just go ahead and convert it to a char when creating the command to execute the stored procedure. The Parameter element describes any input or output parameters. In this case, there is only an input parameter, specified by Mode="In". Additional mode options areInOut, Out, and ReturnValue . All four align with the stored procedures flags to define parameters that are being sent to the procedure. Here is a description of each mode option:
In
In parameters are read by the stored procedure.
Out
Out parameters are populated by the procedure and returned.
InOut
InOut parameters are read by the stored procedure and returned. The procedure may or may not update this parameter before returning it.
ReturnValue
ReturnValue parameters indicate that the stored procedure may create a value and return it as the last operation in the procedure. The returned value will be captured by the Entity Framework. For example, after an Insert, the stored procedure might return a newly generated identity value.
You'll notice that the parameter in the SSDL is nchar, whereas the parameter in the database's procedure is more explicit: nchar(50) . This won't pose a problem when data is sent to the stored procedure, thanks to the
AllowImplicitConversion enum. The other enumerations are explained in the documentation.
NOTE Notice that for this query function, the SSDL defines only what is necessary to call the function. There is no indication of returned data. You'll learn more about how stored procedures are implemented from the SSDL back to the Conceptual Schema Definition Layer (CSDL) later in this chapter. Now let's take a look at a more complex function,UpdateContact . Here is the actual stored procedure:
PROCEDURE UpdateContact @contactid INT, @firstname NCHAR(50), @lastname NCHAR(50), @title NCHAR(50) AS UPDATE Contact SET [FirstName]=@firstname,[LastName]=@lastname,[Title]=@title, [ModifiedDate]=GETDATE() WHERE [ContactID]=@contactid
The UpdateContact function in the SSDL has the same attributes as theCustomersbyState function, as well as parameter elements to represent the input parameters. You will see later in this chapter how you can use mappings to easily leverage the Update, Insert, and Delete stored procedures when coding against the EDM. You'll also see how the query stored procedure (CustomersbyState ) is handled differently than the Data Manipulation Language (DML) functions.
DML, CRUD, and CUD DML is a frequently used acronym that stands for Data Manipulation Language, and it refers to the three types of functions a data access technology must provide to manipulate data: Insert, Update , and
Delete . You will also frequently see the termCRUD used, which stands for Create, Read, Update, and Delete (Create is used rather than Insert since CRUD sounds much better than IRUD). Lastly, some people use the term CUD to refer to the same three DML operations Create ( , Update, and Delete ). Unfortunately, CUD has a different meaning for people who live in cow country, so developers prefer to use DML instead.
6.3. Implementing Functions As you saw in Chapter 5 , the default behavior of the Entity Framework is to construct the necessary Insert, Update, and Delete commands on the fly when you call SaveChanges.
You can override this behavior for specific entities by using functions instead. You can map functions to specific entities. Then, when SaveChanges is called, the Entity Framework will use the stored procedures for updates to entities that have functions mapped to them. For entities that have no function mappings, the Entity Framework will perform the default behavior of generating the commands dynamically. The CustomersbyState stored procedure is for reading data, not updating it. You can link functions for "read" stored procedures to entities that match what the procedure returns. You can use these functions in the EDM in other ways, but the Designer supports only these two scenarios where you can map a function directly to an entity. These are the scenarios we will cover in this chapter. A later chapter will dig into working with stored procedures that are not as simple to implement.
NOTE You will find the terms stored procedures and functions used interchangeably throughout the model and the Designer. The model consistently refers to functions , whereas the Designer, in an effort to use familiar terminology, uses stored procedures in a number of places.
6.3.1. Rules for Mapping Functions to Entities There are some strict rules for mapping functions to entities:
All or nothing You must map either all three functions or none of them. This is a requirement based on how the Entity Framework works with these mappings. Visual Studio will display an error (Error 2025) that reads "The schema validation failed for mapping schema". The error details explain that the mapping is incomplete and will even tell you which of the three function mappings is missing.
No random input or output parameters Every input parameter of a function must line up with a property in the entity. You can't substitute your own data to use as an input parameter. You only can use one of the entity's properties.
Associations to EntityReferences require input parameters on all three functions We won't focus on this rule in this chapter. However, if you were mapping functions to the Address , you would have to make some special considerations with respect to the Contact navigation property. You'll see this in action in the next chapter.
6.3.2. Wiring Up Insert, Update, and Delete Functions to an Entity If you look more closely at the Mapping Details window, you will notice two icons in the upper-left corner. Select the Contact entity in the Designer to display its mappings. The icons will become active. Clicking the top icon causes the Mapping Details window to display the table mappings. The lower icon is for displaying function mappings. You can also display function mappings by right-clicking an entity and choosing Stored Procedure Mappings. In the Mapping Details window, you will see three placeholders for selecting an Insert function, an Update function, and a Delete function, as shown in Figure 6-2.
Figure 6-2. The function or stored procedures view of the Mapping Details window
Click the first item, Select Insert Function, which will display an arrow to the right that represents a drop-down list. Click the arrow to see your options. The Designer will identify all of the unmapped functions in the store layer and present them in the drop-down list. Select the InsertContact function. The Designer will discover the parameters that are defined in the SSDL and will automatically map them to properties in the Contact entity that have matching names. In this example, everything lines up perfectly, as you can see in Figure 6-3.
Figure 6-3. The InsertContact function mapped to the Contact entity
The InsertContact stored procedure happens to return the new ContactID that was generated when the contact is inserted: PROCEDURE [dbo].[InsertContact] @firstname NCHAR(50), @lastname NCHAR(50), @title NCHAR(50) AS
INSERT INTO [Contact] ([FirstName] ,[LastName] ,[Title] ,[AddDate] ,[ModifiedDate]) VALUES (@firstname,@lastname,@title,GETDATE(),GETDATE())
SELECT SCOPE_IDENTITY() AS NewContactID WHERE @@ROWCOUNT > 0
You may recall from Chapter 5 that when the Entity Framework constructs its own Insert command, it selects the new identity value and automatically pushes it into the entity object that was inserted. You can achieve the same effect by mapping the returned NewContactID value directly to the entity's ContactID property. That will mean it will not be necessary to requery the database to acquire the ContactID for an inserted contact. To map the returned value, type NewContactID over the text "". The ContactID will be automatically chosen as the property to map to because it is the EntityKey for Contact , and therefore is a very good first guess for the Designer to make for you. Select the DeleteContact and UpdateContact functions to map to the other two functions. There are no other return values, so you will not need to apply a ResultColumnBinding for
the update (see Figure 6-4).
Figure 6-4. The function mappings for Contact after you've finished mapping the stored procedures functions
6.3.2.1. The Use Original Value option One last point regarding the Mapping Details window concerns the Use Original Value checkbox for the Update function. As you learned in Chapter 5 , an entity will have an original value and a current value stored in its ObjectStateEntry . If the entity has been modified, the current value will be used for the update by default. Here you have the ability to modify that behavior.
6.3.3. Inspecting the Mappings in the XML Now it's time to see how these mappings have impacted the Mapping Schema Layer (MSL) section of the model in the raw XML. A second EntityTypeMapping section has been added within the EntitySetMapping section for the Contacts EntitySet . The first is the one that defines the scalar property mappings for Contact. The new EntityTypeMapping contains an inner element called ModificationFunctionMappings . Within this element the three functions are mapped out, as shown in Example 6-2.
Example 6-2. EntitySetMapping with function mappings added
In Example 6-2 , you can see that a second EntityTypeMapping element has been added to the Contacts EntitySetMapping . Each function is listed within this new section, and based on everything you have already learned about reading this file, the elements should be familiar and the mappings should be logical. Notice in the UpdateContact that each ScalarProperty has a Version attribute. That is the notation that ties back to the Use Original Version checkboxes, which are unchecked, therefore indicating that the version is Current .
6.3.4. Using These Mapped Functions Now that the functions have been mapped to the entities, the Entity Framework will automatically use them to handle any Contact entities that need to be persisted to the database anytime you call SaveChanges . That's all there is to it. You won't call these functions directly in your code. To see this in action, you'll need to watch the SQL Server Profiler to see when the stored procedures are called, rather than a generated command. Example 6-3 shows a test that retrieves an address and a contact, and then edits both of them.
Example 6-3. Testing the function mapping
Private Sub TestFunctionOverride() Using context As New PEF Dim contact = context.Contacts.Include("Addresses") _ .Where(Function(c) c.Addresses.Any).First 'make a change to contact contact.LastName = contact.LastName.Trim & "-Jones" 'make a change to the address Dim address = contact.Addresses.First address.Street2 = "Apartment 42" 'call SaveChanges context.SaveChanges() End Using End Sub
private static void TestFunctionOverride() { using (PEF context = new PEF()) { var contact = context.Contacts.Include("Addresses") .Where(c => c.Addresses.Any()).First(); //make a change to contact contact.LastName = contact.LastName.Trim() + "-Jones"; //make a change to the address var address = contact.Addresses.First(); address.Street2 = "Apartment 42"; //call SaveChanges context.SaveChanges(); } }
When the SaveChanges method is called, the required updates are sent to the database. Because you mapped the functions to the Contact entity, the change to this contact object is manifested in the following command, which executes the UpdateContact stored procedure: exec [dbo].[UpdateContact] @contactid=325,@firstname=N'Virginia', @lastname=N'Miller-Jones',@title=N'Ms.
The Address entity has no mapped functions; therefore, Object Services constructed this Update command, which was sent to the database: exec sp_executesql N'update [dbo].[Address] set [Street2] = @0 where ([addressID] = @1)', N'@0 nchar(12),@1 int', @0=N'Apartment 42',@1=2260
The second line of the command is the Update command. The third line defines the parameters for the command, and the last line passes in the parameter values. is the new value of Street2 and 2260 is the AddressID of the address to update.
'Apartment 42'
You will learn a lot more about how the Entity Framework performs saves and how you can impact them as you read through the book. For now, let's continue to focus on stored procedures.
6.3.4.1. What about the read stored procedure? So far, we have dealt with only the Insert, Update, and Delete functions. Another stored procedure came into the model: the CustomersbyState procedure, which does a query of the database and returns a set of rows from the Customer table. In our current model, a row from the Customer table maps back exactly to the Customer entity, which means this procedure passes an important rule for wiring up read procedures in the EDM. The rule is that the results from the procedure must match an existing entity. Because CustomersbyState returns what equates to a Customer
entity, it is easy to map this function in the Designer.
In Chapter 13 , you will learn how to wire up procedures that return results that do not match up to an entity. You map the CustomersbyState procedure using the Model Browser, not the Mapping Details window. The next section discusses the Model Browser in detail.
6.4. The EDM Designer's Model Browser To access the Model Browser, you need to right-click in the background of the model in the Designer, and then select Model Browser from its context menu. In Figure 6-5 , a number of the model's objects have been expanded. This view of the model gives you a great way to see the overall picture of the conceptual layer and the store layer without all of the nitty-gritty XML.
Figure 6-5. Viewing the CSDL and SSDL in the Model Browser
The Model Browser helps you navigate the objects in the conceptual layer (entities, properties, and associations). The lower portion allows you to navigate the items in the SSDL. Notice that in the Model Browser, these are referred to as Tables, Views, and Stored Procedures and not by their SSDL schema names of Entity and Function. Many of the features of the Designer are available in the context menu of the Model Browser as well, such as validating
the model or view mappings, and updating the model from the database. The Model Browser also provides a means for mapping the functions from the SSDL. Although you can also map some of these from an entity's Mapping Details window, you can map functions that are for reading data from the store only from the Model Browser.
6.5. Mapping the Last of the Four Functions: CustomersbyState Right-click the CustomersbyState stored procedure in the Model Browser and choose Create Function Import from its context menu. The Add Function Import dialog box will let you name the function import and map it to an existing entity or a scalar type (e.g., an integer, string, etc.); see Figure 6-6.
Figure 6-6. Mapping a stored procedure to an entity that will return a Contact entity
The new function import will not be displayed in the model in the Designer, but you can see it in the Model Browser if you open the first node and drill first into EntityContainer and then into Function Imports. In the XML, you will find the following additions to the CSDL section inside theEntityContainer element:
Notice that the return type is not a single contact, but a collection of contacts. If only one contact is returned, you will end up with a collection containing a single item. The mapping information is in a new FunctionImportMapping element in the MSL's EntityContainerMapping section. Unlike the Update, Insert, and Delete mappings, this is not included as part of the contact'sEntitySet mappings, but rather stands alone:
6.5.1. Using the CustomersbyState Function After you map the function, a new method is automatically generated for theProgrammingEFDB1Entities class:
CustomersbyState. You can call the method directly in your code using an instantiated context, as shown in the following code:
Dim results = context.CustomersbyState("WA")
var results= context.CustomersbyState("WA");
This is not the same as creating and executing a query. The function will be executed immediately when the function is called in code. The execution will not be deferred. The return type will be a System.Data.Objects.ObjectResult(Of
Contact) (in C#, an ObjectResult ), which you can enumerate through or bind to data controls. You could also use one of the LINQ conversion methods to return a more common type of IEnumerable . For example, you could return a list of Contact objects rather than the ObjectResult, using the following code:
context.CustomersbyState("WA").ToList()
context.CustomersbyState("WA").ToList()
NOTE Not every function mapping will become a method of the ObjectContext . Methods that return scalar values or those that send data to the database will be part of the model but not be available as a method. These can be called from Entity Client. You will learn more about these variations in Chapter 13.
6.5.2. Using Functions in a Query Because the function returns an IEnumerable (the ObjectResult ), it is possible to use the function in a query, as shown in the following code:
Dim results = From c In context.CustomersbyState("WA") Where c.LastName.StartsWith("S")
var results = from c in context.CustomersbyState("WA") where c.LastName.StartsWith("S") select c;
However, this is not a LINQ to Entities query, but a LINQ to Objects query—the query will be performed on the results of the function. That means the function will be executed on the server side and then the results will be processed further in memory. For example, if there are 800 customers in WA but only three of them have last names that begin with S , all 800 customers will be returned from the database and then LINQ will pull out the three that you were really looking for. Databases do not support using stored procedures as subqueries, which is why it is not possible to compose a LINQ to Entities query using these functions. Therefore, .NET and the Entity Framework coordinate to break up the query into a function call and a separate LINQ to Objects query.
6.6. More About the Update Model Wizard At the beginning of the chapter, you used the Update Model Wizard to pull in the four stored procedures from the database. Although you used the wizard to add database objects that you skipped over when first creating the model, you can also use the wizard to add objects that were created in the database after you originally built the model. For example, if a new table has been added to the database, the Update Model Wizard will discover that the table is not already listed in the SSDL of the model and will display it in the Add page of the wizard. If you select this new table, the wizard will add the table to the model and will create a new entity for it. This is the same way that the Entity Data Model Wizard works when you are creating new models. The Update Model Wizard does not allow you to specify changes to existing objects—for example, tables that were included in the model but have since been modified in the database. The wizard will automatically apply those changes. If you have added new columns to an existing table for which an entity exists in the model, those fields will come into the model and will be added to the entity automatically. Not all changes will affect the conceptual model, however. For example, if you change the spelling of a column name in the database, the wizard will not know to line it up with the existing entity property and instead will create a new property. In this case, you would need to remove the new property and modify the entity mappings so that the existing property points to the correct column.
6.6.1. A Frequently Asked Question About Deleting Entities from the Model One scenario in particular has confused many developers who work with the EDM. If you delete an entity from the model and later wish to re-create it, you might expect the Update Model Wizard to re-create the entity for you; but it won't. The wizard bases its proposed changes by comparing the model's SSDL to the database schema. When you delete an entity from the conceptual model, the table representation in the SSDL remains intact. The wizard will not detect a change, and therefore will not list that table in the Add window. You can solve this problem in two ways. The first way is to create the entity from scratch in the Designer, and then, using the Mapping Details window, map it back to the table. The second way is to open the model in the XML view, as you did in Chapter 2, and manually delete the SSDL'sEntityType section for that table, as well as itsEntitySet . You can refresh your memory on the SSDL's XML by reviewing Chapter 2.
6.7. Summary Many developers and database administrators rely on stored procedures for a variety of reasons, including consistency, security, and reliability. Even though the Entity Framework composes queries and commands automatically, you can override this default behavior by implementing your own stored procedures in the model. This chapter highlighted functionality that the Designer readily supports: mapping procedures to entities when the procedure's input parameters and results line up with existing entities and their properties. Chapter 13 will dig further into these implementations as well as those that are a little trickier to pull off.
Chapter 7. Tuning Up a Model In the previous chapters, we discussed some of the core concepts of the Entity Framework, the Entity Data Model (EDM), querying, and other straightforward operations. The simple database and console application we used illustrated key points and kept you focused on the lessons. Now it's time to look at some more realistic scenarios. In this chapter, we'll create and work with a more realistic model. The model will be contained in its own assembly so that you can reuse it in other applications in your enterprise. The chapter will also address the important task of clarifying the names used in a model that has been created from a database. You will also learn about many-to-many relationships as well as a few more tips about mapping stored procedures.
7.1. The BreakAway Geek Adventures Business Model The example company for which we will be writing software is called BreakAway Geek Adventures. This small company arranges adventure vacations for hard-working programmers who need a break. Examples of vacations that can be booked through BreakAway Geek Adventures include whitewater rafting in Belize and bicycling in Ireland. The company has been in business for a number of years and has an old application that uses a SQL Server database for its data store. Now it's time to write shiny new applications for this venerable firm in .NET, leveraging the Entity Framework.
NOTE You can download a script for creating this database from the book's website. Look for the database named BreakAway. There is a version for SQL Server 2005 and a version for SQL Server 2008. Figure 7-1 shows the BreakAway database schema.
Figure 7-1. The BreakAway database schema
7.2. Creating a Class Library Project to Host an EDM The first step is to create the new model. Rather than create the EDM directly in the Windows application, in this chapter you will create a separate project for the EDM. This is a good start on your way to planning for larger applications. 1.
In Visual Studio, create a new Class Library project named BreakAwayModel.
2. Delete the Class1 file that was automatically created. 3.
Add a new ADO.NET EDM to the project. Change the default name (Model1.edmx) to BAModel.edmx.
4.
On the Choose Model Contents page, choose Generate from Database and then select the BreakAway Data Connection if it has already been added to Visual Studio. If it hasn't been added, create it on the fly using the New Connection button. Leave the default connection settings name, BreakAway, alone for now and go to the next page of the wizard.
5.
On the Choose Your Database Objects page, check all three objects: Tables, Views, and Stored Procedures. Open the Tables node. You will see that because the database contains a diagram, the table that controls the diagram is listed (sysdiagrams ). Uncheck that since you don't need the diagram in your model. Creating the diagram in SQL Server Management Studio also resulted in seven stored procedures and one function being added for the sake of diagramming. Their names begin with either fn_ or sp_ and contain the word diagram . They won't interfere with your model, but you can uncheck these procedures and functions if you prefer.
6.
Leave the default model namespace intact. You'll get a chance to change that shortly.
7.
Wrap up model creation by clicking the Finish button.
The newly created model will open in the Designer window and should look something likeFigure 7-2.
Figure 7-2. The initial model created from the BreakAway database
NOTE Did you notice that the PrimaryDestination column was misspelled in the database? In the previous application, the developer had to constantly tangle with this field name. But with the EDM it will no longer be a problem. Though a small detail, this is a really nice benefit of using the data model. Changing the field name in the database could have a big impact in the database schema, especially if that field name is used in views, functions, or stored procedures. In the model, you can change the property to whatever name you like without impacting the database.
7.4.2. Determining Which Navigation Property Is Mapped to Which Foreign Key Field Before you can rename these navigation properties, you'll need to figure out which foreign key fields the navigation properties belong to. For example, does refer to the PrimaryActivity or the SecondaryActivity?
Customer.Activities
You can do this by looking at the properties of each navigation property and seeing which association it is bound to, and then looking at that association and seeing which field is involved. Let's start with Activities. Click the Activities navigation property in the Customer entity. In its Properties window, BreakAway.FK_Customers_Activities is the Association property. Use the Properties window drop-down (near the top of the Properties window) to select that association.
NOTE There are a number of ways to select an association in the model. The Properties window drop-down is one way to select the association. You can also select it in the Model Browser. An additional method is to right-click a navigation property and to choose Select Association from its context menu. Any of these methods will cause the association to be highlighted in the Designer and its properties to display in the Properties window. Right-click the association's line in the model and select Table Mapping from the context menu. The termTable Mapping seems to be used generically in the Designer, even when the mapping is an Association Mapping. In Figure 7-4 , you can see that this association is for the PrimaryActivity.
Figure 7-4. Checking the mapping details of an association to discover which foreign key is involved in the association
Rename the Activities navigation property to PrimaryActivity and the Activities1 navigation property to SecondaryActivity. You can do the same detective work for the Locations and Locations1 navigation properties to see which one should be named PrimaryDestination and which one should be named SecondaryDestination. You need to fix the other ends of these associations as well. The Activity entity has two navigations back to the Customer entity. Going in this direction, the navigations represent "Customers who have listed this activity as their primary activity" and "Customers who have listed this activity as their secondary activity." Rename Customers to PrimaryPrefCustomers and Customers1 to SecondaryPrefCustomers . Make the same changes to the Customers and Customers1 navigation properties in the Destination
entity.
7.5. Mapping a Few Stored Procedures The database has a number of stored procedures. For now, we'll do function mapping for the procedures for the Payments table— InsertPayment, UpdatePayment, and DeletePayment —using
the same technique you learned in Chapter 6.
Open the Stored Procedure Mappings window for the Payment entity and select the appropriate functions for insert, update, and delete.
7.5.1. Mapping the Insert Function The InsertPayment function returns a newly generated PaymentID called NewPaymentID . The parameter names don't match the property names of the entity; therefore, you will need to manually map some of the properties. Notice that the InsertPayment function needs to know the ReservationID . In the model, Reservation is a navigation property of the Payment entity. You will have access to the navigation property in the mapping window, so you can select Reservation.ReservationID to map to the required parameter. Be sure to map that to the Result Column Bindings item, as you did for the InsertContact function in the preceding chapter. The insert mapping should look the same as in Figure 7-5.
Figure 7-5. Mapping the input parameters and the results of the InsertPayment stored procedure to properties in the Payment entity
7.5.2. Mapping the Update Function When you are mapping the UpdatePayment function, you will also have to manually map the properties whose names don't match the input parameters.
7.5.2.1. Using the Use Original Value checkbox Because of the way this stored procedure works, you can take advantage of the special Use Original Value column that exists only for the update functions. The stored procedure performs a concurrency check against the timestamp field. If anyone edited the record in between the time the user retrieved the record and when he attempted to save changes, the order won't be updated and an OptimisticConcurrencyException will be thrown. You'll learn more about working with concurrency in Chapter 18. When a payment is updated, the database will automatically update the timestamp field. The UpdatePayment procedure returns the new timestamp value. Map that return value as shown in Figure 7-6.
Figure 7-6. The UpdatePayment function mapping
7.5.2.2. The DeleteFunction mapping and an awkward stored procedure parameter A typical stored procedure for deleting a record takes the primary key field as a parameter. When you map the Delete function you will see that the DeletePayment function has not only the PaymentID as a parameter, but also ReservationID . This is in the database in advance to simplify your task of mapping the functions, but you will not typically have the additional parameter. Why is it there and why should it make the mapping simpler? A mapping requirement with the Entity Framework makes it necessary. In the Insert and Update stored procedures, it makes sense to have the ReservationID as a parameter because that supplies the ForeignKey value that the Payment record in the database needs. As a general rule, the Entity Framework requires properties that are involved in association mappings to be mapped in all of the function mappings for the entity. Because Payment has an association to Reservation—even in the function where it seems illogical, Delete —you must map the property that the association revolves around (Reservation.ReservationID ). This is not meant to aggravate you or your database administrator, but it is how the Entity Framework is able to generically deal with all of the things you might try to achieve in your models and your queries. So, unfortunately, developers are stuck with the rule regardless of whether it makes sense to them. Putting the extra parameter(s) into the stored procedure in the database is the easiest way to deal with this. If that's not an option in your application, the alternative is to modify your model's Store Schema Definition Layer (SSDL). You will learn in Chapter 13 that it's possible to modify the SSDL to create virtual tables, stored procedures, and functions that don't physically exist in the database. The downside to modifying the SSDL manually is that if you update the model, many of the SSDL modifications will be overwritten. At this stage in the book, it is simpler to just have the parameter written directly into the stored procedure. Map the DeletePayment function so that it matches Figure 7-7.
Figure 7-7. The DeletePayment function mapping
You now have an Entity Data Model from a highly normalized database. Since you have already done so much work on this model, we will leave the task of performing more advanced customizations to Chapters Chapter 12 and Chapter 13. Figure 7-8 shows the current view of the model, with some of the entities moved around to make it more visually appealing.
Figure 7-8. The new BreakAway model
As you can see in Figure 7-10, Activity and Equipment are joined in a many-to-many relationship. Thanks to the original table names, the navigation property names happen to be just right and don't need editing. Each piece of equipment has activities and each activity has a collection of equipment.
Figure 7-10. Activity and Equipment joined in a many-to-many relationship
Each activity also has trips and every trip has a collection of activities. It will be very convenient not to have to construct joins when traversing these relationships in queries. Because the join tables contain only the keys involved, the EDM can easily represent the relationship without the aid of a join entity. This mapping not only enables a convenient association directly between the two entities, but also manages querying, inserts, and updates across this join. You'll see this in action as you move throughout the book.
7.7. Building the BreakAwayModel Project Now it's time to build the model into an assembly that you will be able to use in the many projects that you will be building in upcoming chapters.
7.7.1. Don't Overlook the Assembly and Model Names Before you compile the model, you will want to change a few names so that when you access the model and its classes from another project, you won't have to work with cumbersome names.
7.7.1.1. BAGA assembly namespace You will have to make references to the assembly namespace throughout the code of your other applications that are using that namespace. Therefore, it will be handy to have a nice, short name for the namespace. The acronym for BreakAway Geek Adventures is BAGA, which is a good option. Open the project's Properties window, and on the first page, Application, change the root namespace to BAGA.
7.7.1.2. Entity container name When you created the model with the Entity Data Model Wizard, you left the default name for the EntityContainer as
BreakAway. Change that name toBAEntities . Remember that the place to do this is in the model's Properties window, which you can access when the model is open in the Designer.
NOTE When you change this name and save the model, theConnectionString name in the app.config file should change to BAEntities as well. It's not a bad idea to double-check that this happened by looking in the app.config file. Changing this name will make typing Entity SQL expressions easier, as you will have to include this container name in every Entity SQL expression.
7.7.1.3. Model namespace You can change the model's namespace so that it's consistent with the container name, in this case BAModel to .
7.7.2. The Impact of Compiling a Project on an EDMX File When a project containing an EDMX is compiled, the compiler extracts theStorageModels, ConceptualModels, and
Mappings sections of the EDMX file and creates individual schema files from them. In this case, the files are BAModel.ssdl, BAModel.csdl, and BAModel.msl . By default, these files are embedded into the assembly that is built from the project. Figure 7-11 shows the compiled assembly in Red Gate's Reflector tool, with the embedded files listed under Resources.
Figure 7-11. The schema files embedded in the assembly by default
If you look at the metadata portion of the EntityConnection string that the Entity Data Model Wizard inserted into the app.config file, you'll see the following notation:
res://*/BAModel.csdl|res://*/BAModel.ssdl|res://*/BAModel.msl
Much of the functionality in the Entity Framework depends on its ability to read the schema files. The * in the metadata of the connection string indicates that you can find the files in the assembly.
7.7.2.1. Splitting out the schema files Having the model in the assembly is convenient when you don't expect the model to change often after it has been deployed. However, you may want to take advantage of the model's loose coupling at some point. For example, you or your database administrator might modify the database in a way that changes the schema, but introduces nothing new that would impact the objects in the application. In this case, you would need to update the model so that the database changes are reflected in the SSDL schema. Then, because of this change, you would need to adjust some of the mappings to be sure that the entities are mapped correctly to the SSDL. So, in this scenario, the SSDL and MSL layers change, but no change is made to the conceptual layer. You may not want to have to rebuild and redeploy the assembly. Doing so may also affect the versioning of your application. Although the files are embedded by default, there is an option to have the files exist outside the assembly. The model has a property called Metadata Artifact Processing. The property is available in the model's Properties window, as shown in Figure 7-12.
Figure 7-12. Changing how the model's schema files are created during the build process
Notice that the connection string has changed. The metadata no longer has a* to indicate that the files are embedded. Instead, it shows the relative path of the files. You will find them in the project's output directory, which by default is in either the bin\debug or the bin\release folder in the project folder.
7.7.2.2. Moving the schema files You can put the schema files anywhere you want. However, you will need to be sure that the connection string points to the correct path. If, for example, you place the files in C:\EDMS, you'll need to modify themetadata attribute to the following:
metadata=C:\EDMS\BAModel.csdl| C:\EDMS\BAModel.ssdl| C:\EDMS\BAModel.msl
NOTE Although this chapter covered creating a model in a separate assembly, it's useful to be aware of a special case for the metadata attribute. If you create an EDM inside an ASP.NET Web Site Project, because of the way in which Web Site Projects are compiled, the path will be affected. The entire metadata attribute will be metadata=res://*. This does not happen with Web Application Projects. You can learn more about the EntityConnection's metadata attribute in the MSDN Library documentation.
7.8. Summary In this chapter, you went through the steps of creating an EDM from a more realistic database, which you will be using throughout the rest of this book. Then you spent some time cleaning up many of the automatically created entity and property names so that they will be more logical when it comes time to use the model in your applications. You have now prepared an assembly that can easily be referenced from a variety of projects and used in other applications. Because the runtime schema files are embedded into the assembly, it will be even simpler to reuse and share the model. In the next chapter, you will write your first Windows applications using this model.
Chapter 8. Data Binding with Windows Forms and WPF Applications So far, you've seen how to interact directly with an EDM using snippets of code in a console application. Although there is much more to learn about the Entity Framework, at this point it's time to see how you can use the Entity Framework as part of your applications. In this chapter, you will explore basic data-binding scenarios in Windows Forms and Windows Presentation Foundation (WPF). You'll see how the Entity Framework objects work with Visual Studio's data-binding features in much the same way that DataTables and DataSet s do, without having to explicitly set and retrieve the values of each control. The data binding's change notification mechanism works automatically with the Entity Framework's change tracking, so editing data that was queried through the Entity Data Model does not require a lot of extra coding. In the examples here, you'll bind directly to the results of Entity Framework queries as you learn the concepts. In Chapter 20 , after you have learned much more about the Entity Framework, I will address n-tier Windows Forms applications. The chapter will begin with data binding in Windows Forms and will then move on to the WPF techniques.
Until you have more tools in your Entity Framework tool belt, the best way to determine a newly added Customer at this point is to use a flag to identify that a new Customer
is being added to the BindingSource . We'll employ a Boolean variable named adding for the flag.
Once that is in place, you will need to do the following for new customers: 1. Create a new contact object. 2. Add the contact to the new customer. 3. Set necessary defaults on the contact. 4. Set necessary defaults on the customer. 5. Set the adding flag to false. Let's see how to implement these steps.
NOTE In Chapter 10, you will learn how to add business logic to entities and these types of steps won't be necessary, especially not in the user interface.
8.1.11.2. Adding the code to ensure that new customers are created properly Add the Boolean variable in Example 8-6 to the form's declarations.
Example 8-6. Placing the adding variable into the form's declarations
Public Class Form1 Private context As BAGA.BAEntities Private activities As List(Of BAGA.Activity) Private destinations As List(Of BAGA.Destination) Private adding As Boolean
public partial class Form1 : Form { BAGA.BAEntities context; List activities; List destinations; bool adding;
In the CustomerBindingSource.AddingNew event, set the adding flag to True, as shown in Example 8-7.
Example 8-7. Setting the adding flag to True
Private Sub CustomerBindingSource_AddingNew _ (ByVal sender As Object, _ ByVal e As System.ComponentModel.AddingNewEventArgs) _ Handles CustomerBindingSource.AddingNew adding = True End Sub
private void customerBindingSource_AddingNew (object sender, AddingNewEventArgs e) { adding = true; }
In the CurrentChanged event, check the adding flag. If it is true , perform the steps outlined earlier on the new Customer. If adding is false, this logic will be skipped, as shown in Example 8-8. In this example, CustomerBindingSource.EndEdit is called prior to adding the related entities to the Customer. This method will trigger the BAEntities context to add the new Customer and therefore the context will also manage the new Contact entity properly. Without this method call here, you will experience problems when it comes time to call SaveChanges . Finally, note that the CustomerType is assigned with an EntityKey. You'll learn more about assigning EntityReference.EntityKey
in the next chapter.
Example 8-8. Filling out the defaults for a new Customer
Private Sub CustomerBindingSource_CurrentChanged _ (ByVal sender As Object, ByVal e As System.EventArgs) _ Handles CustomerBindingSource.CurrentChanged If _adding Then CustomerBindingSource.EndEdit() Dim newcust = CType(CustomerBindingSource.Current, BAGA.Customer) If newcust.Contact Is Nothing Then newcust.Contact = New BAGA.Contact newcust.Contact.ModifiedDate = Now newcust.Contact.AddDate = Now End If newcust.InitialDate = Now newcust.CustomerTypeReference.EntityKey = _ New EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1) _adding = False End If End Sub
private void customerBindingSource_CurrentChanged (object sender, EventArgs e) { if (adding) { customerBindingSource.EndEdit();
var newcust = (BAGA.Customer)customerBindingSource.Current; if (newcust.Contact == null) { newcust.Contact = new BAGA.Contact(); newcust.Contact.ModifiedDate = Now; newcust.Contact.AddDate = Now; } newcust.InitialDate = Now; newcust.CustomerTypeReference.EntityKey = new EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1); _adding = false; } }
You'll need one last line of code for saving newly added customers. It's not uncommon for the BindingSource to leave its current item in the "edit state." With entities, this means that the changes in the UI won't be pushed into the entities, and therefore SaveChanges will not see the need to do any updates to the database. BindingSource.EndEdit
will ensure that the UI changes are registered with the entities. Add this method to the Save Item button's Click event, just before SaveChanges is
called, as shown in Example 8-9.
Example 8-9. Using EndEdit to ensure that BindingSource completes the current edit process
Private Sub CustomerBindingNavigatorSaveItem_Click _ (ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles CustomerBindingNavigatorSaveItem.Click CustomerBindingSource.EndEdit() context.SaveChanges() End Sub
private void customerBindingNavigatorSaveItem_Click (object sender, EventArgs e) { customerBindingSource.EndEdit(); context.SaveChanges(); }
8.1.11.3. Testing the form's add functionality Run the form again and add a new customer. You'll be able to enter name and preference information and save the record. Because the context is keeping track of additions and edits, it is possible to make changes to multiple records before clicking the Save button. When you do, all of the changes you have made to the list of customers, whether they were additions or edits, will be sent to the database. The best way to ensure that the code is working is to stop the application after you have saved your changes, and then start it again. This will force it to requery the database, and you can verify that the changes were definitely persisted to the store. Not all of the form features will work. For instance, you will run into problems if you attempt to delete a customer, because of constraints in the database that we have not yet addressed. In upcoming chapters, you will learn how to perform this and other types of functions with your entities, how to add business logic, how to write layered applications, and more. You could add plenty of features to this form to make it even more functional, but it's time to move on to a different type of client-side data binding: data binding in WPF.
8.2. Data Binding with WPF Applications For the WPF data-binding example in this section, you'll focus on interacting with trips and their details: destination, lodging, and activities. You will also get a chance to see how many-to-many relationships work both for data retrieval and for updates. If you've never created a WPF application before, this will be a useful, albeit simple, introduction. It will be a bit of a dive into the not-so-shallow end of the WPF pool, but the code samples should provide sufficient buoyancy. If you are looking for tips on how to make WPF perform its many shiny tricks, a data access book is not the place to look.
NOTE Quite a number of wonderful WPF books, articles, and other resources are available—too many to list here. For a good first look at WPF, I recommend MSDN's "How Do I?" videos at http://msdn.microsoft.com/en-us/bb629407.aspx#wpf/. Currently, no data-binding controls for WPF are available "in the box" with Visual Studio 2008. Third-party control developers are a great source for WPF data-binding controls. But for this walkthrough, we'll stick to what's available directly with Visual Studio.
NOTE WPF is a much younger technology than Windows Forms, and therefore it does not have the suite of data-binding controls available for Windows Forms. The WPF sample in this chapter will use controls that are available in Visual Studio 2008. Microsoft is committed to providing WPF controls, and currently provides a DataGrid in the WPF Toolkit, available on its code-sharing site, CodePlex, at http://codeplex.com/wpf/. A number of third-party providers have also written controls for WPF, including DataGrids and others that do data binding.
8.2.1. Creating the WPF Form The purpose of this form will be to edit trips that exist in the BreakAway catalog. Trips are defined by a destination, a start and end date, a price, lodging, and a list of activities. Figure 8-14 shows the form you will build.
Figure 8-14. The WPF form for managing BreakAway's Trips catalog
A slew of controls are involved in this form. You'll learn how to bindListBoxes and TextBox es and how to have them interact with one another, as well as some tricks that you'll need to know for doing all of this with the Entity Framework.
8.2.2. Creating the New Project We'll begin by creating a new WPF project, adding the references to use the model, and getting all of the controls onto the form: 1.
Create a new WPF project in the same solution where you created the model and the Windows Forms application.
2.
Add a reference to the BreakAwayModel project and toSystem.Data.Entity as you did for the previous application.
3. Copy the app.config file from the Model project into this project. Remember, this is just a cheat to quickly get theConnectionString into the current application. 4. Drag a WPF ListBox control from the Controls Toolbox onto the default form. This will be theListBox for displaying the trips. 5. Change the ListBox's name to lstBoxTrips.
NOTE If you haven't used WPF before, you might appreciate that thename property is at the top of the control's Properties window.
8.2.3. Adding Code to Query the Entities That Drive the Form Adding events to WPF is the same as for Windows Forms. In C#, you can use the Events page of the Properties windows. In VB, you can do the same or use the Class Name and Method Name drop-downs in the Code window. 1. Declare variables for the form. As in the previous application, you'll need a context and some variables to contain the selection lists. You can add an Imports or using statement for the BAGA namespace so that you don't have to type it repeatedly. While you're at it, add the System.Data.Objects namespace as well. This will reduce some typing later on. (SeeExample 8-10.)
Example 8-10. Adding the necessary namespaces and variables for the form
Imports BAGA Imports System.Data.Objects Class Window1 Private context As BAEntities Private activities As List(Of Activity) Private destinations As List(Of Destination) Private lodgings As List(Of Lodging) Private trips As List(Of Trip)
using BAGA; using System.Data.Objects; using System.Collections.ObjectModel; namespace BreakAwayWPFCSHarp { public partial class Window1 : Window { private BAEntities context; private List activities; private List destinations; private List lodgings; private List trips;
2. In the Window.Loaded event handler, add the code for retrieving the trips as well as the related selection lists (see Example 8-11). You can return the selection lists as generic lists, but you should use theExecute method to return trips as ObjectResults because the trips will be edited as well as data-bound.
NOTE Later on in the book, you'll learn how to create a generic method that you can use to query for any reference lists so that it won't be necessary to have separate queries for selection lists such as Destinations, Lodgings, and Activities.
Example 8-11. Querying for lists that will be needed by the form
Private Sub Window1_Loaded _
(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) _ Handles Me.Loaded context = New BAEntities activities = context.Activities _ .OrderBy(Function(a) a.ActivityName).ToList destinations = context.Destinations _ .OrderBy(Function(d) d.DestinationName).ToList lodgings = context.Lodgings _ .OrderBy(Function(l) l.LodgingName).ToList Dim tripResults = context.Trips _ .OrderBy("it.Destination.DestinationName") _ .ToList lstBoxTrips.ItemsSource = tripResults End Sub
private void Window_Loaded (object sender, System.Windows.RoutedEventArgs e) { context = new BAEntities(); activities = context.Activities .OrderBy(a => a.ActivityName).ToList(); destinations = context.Destinations .OrderBy(d => d.DestinationName).ToList(); lodgings = context.Lodgings .OrderBy(l => l.LodgingName).ToList(); trips = context.Trips . .OrderBy("it.Destination.DestinationName").ToList() lstBoxTrips.ItemsSource = trips; }
There are a few things to note about the code inExample 8-11 . First, it doesn't use the Execute method. Since we're no longer using the Object data source, we can go back to using ToList. The other notable code is the ItemsSource property. This is the method that WPF uses to bind lists to controls, rather than using DataSource.
8.2.4. XAML's Role in Data Binding Although the code identifies theItemsSource , the Designer will need to know some more information about how to present the data. Data binding in WPF is very different from data binding in Windows Forms. On the design side, WPF is made of controls within controls within controls. To get the first ListBox to display the data that you set to itsItemsSource property, you'll need to do a few things to the XAML. For first-time WPFers, the following section will build up the XAML for the ListBox step by step to explain all of the working parts.
8.2.5. Binding with the ListBox The XAML for the ListBox that you put on the form starts out with only the name and its margins:
NOTE Your XAML may be a little different from what's displayed in these examples because the positioning will not be the same. Not only will your margin values be different, but also, depending on the placement of your controls, you may have attributes such as VerticalAlignment or HorizontalAlignment . Don't worry about those positioning differences from the examples you'll see here. Focus on the binding. Next, you will need to define what the rows will look like. You do this with a control called anItemTemplate:
Within the ItemTemplate , you will use a DataTemplate because you are binding to a set of data:
Inside the DataTemplate, you will need two TextBlocks: one to display the DestinationName and another to display the StartDate. A DataTemplate can contain only a single child. Therefore, we'll use aStackPanel (container) control as the single child and then place the TextBlock controls inside the StackPanel. This seems a little tricky, but it is the commonly used method.
StackPanel s are vertical by default. Since we want the destination and date to be side by side,Orientation is explicitly set to Horizontal. Finally, you need to display data in the TextBlock s. This is where the data binding finally comes in. TheText value uses specific syntax. Binding indicates that it will be getting its data by way of binding, rather than having it hardcoded into the list. Path refers to the name of the property within whatever it is bound to, and StringFormat defines how to format the string if you don't want to see the default format:
Notice that the Path of the first TextBlock is Destination.DestinationName . WPF will read the Trip.Destination navigation property and get to the DestinationName. Add the ItemTemplate and all of its children shown in the preceding code to the XAML in Design view. You can type it directly into the XAML or drag the controls from the Toolbox. You can type the TextBlock text values directly into the XAML.
8.2.6. Testing the Example You've got enough to see some action already. Run the form to see the trip destinations and start dates listed in the ListBox.
All of the typing you've done in the XAML may result in some typos. Although the consequences of some typos will be exceptions thrown at runtime, often you won't see the results you expect even if there are no typos highlighted by IntelliSense in the code. If you're testing the code, and controls are empty when they shouldn't be, ensure that you typed in the correct control names and property names.
8.2.7. Selecting an Entity and Seeing Its Details The next step is to view the trip details. On the form shown inFigure 8-14 , you can see that the start and end dates appear in text boxes on the form, and the destination and lodging information is displayed in combo boxes. Eventually, you will use combo boxes for editing trips as well. WPF's binding goes far beyond binding data to controls. You can also bind controls to each other, creating dependencies between them. We'll use this feature to link the TextBox controls to the ListBox. The TextBox controls will obtain their content from theListBox's selected trip. Drag two TextBox controls (not TextBlocks) and two ComboBox controls onto the form. Arrange them similar to the example and change their names to txtStart, txtEnd, cboDestination, and cboLodging.
8.2.7.1. Binding the TextBox controls to the ListBox The original XAML of the txtStart controls will look like this:
You'll be adding a Binding element as a child of eachTextBox , which means you'll need to remove the slash at the end of the control so that you can add the TextBox closing element after the binding. Example 8-12 shows what the txtStart and txtEnd controls will look like after you add theBinding elements.
Example 8-12. The txtStart and txtEnd controls after adding the Binding element
NOTE In the ListBox you added a binding into the Text property of a TextBlock. Here you are using aBinding element, and the syntax of the path and its StringFormat is different when used in this way.
8.2.7.2. Binding the ComboBox controls to the ListBox and to the data Binding a WPF ComboBox is similar to binding a Windows Forms ComboBox in that you need to set the source (ItemsSource ) to a set of data, identify the value and display properties, and identify what source provides its value. We'll bind to the ItemsSource in code, just as you did for the ListBox . Then we'll use XAML to provide the rest of the information as you just did for theTextBox. First, the code. In the Window.Loaded event, bind the results of the Destinations and Lodgings queries to the ComboBox es. You can add this right beneath the code for binding the lstBoxTrips control:
cboDestination.ItemsSource = destinations cboLodging.ItemsSource = lodgings
cboDestination.ItemsSource = destinations; cboLodging.ItemsSource = lodgings;
In the XAML, add more attributes to the ComboBox controls, rather than adding a child element as you did for theTextBoxes, as shown in Example 8-13.
Example 8-13. Adding more attributes to the ComboBox controls
Before: After:
The DisplayMemberPath and SelectedValuePath attributes refer to the properties of the list of lodgings to which you bound the ComboBox in code. SelectedValue gets the LodgingID from the currently selected trip in the ListBox. Example 8-14 shows the XAML for the ComboBox that displays the selected trip's destination.
Example 8-14. XAML for displaying the destination of the selected trip
8.2.7.3. Testing the sample Now your form is starting to get interesting. When you run the application, the Start Date and End Date text boxes and the Destination and Lodging combo boxes should sync up to whatever trip is selected in the ListBox , as shown in Figure 8-15.
Figure 8-15. The form with the selection functionality enabled
the button's default event, and add a call to SaveChanges in the event handler, as shown inExample 8-16.
Example 8-16. Enabling saves
Private Sub btnSave_Click _ (ByVal sender As System.Object, _ ByVal e As System.Windows.RoutedEventArgs) Handles btnSave.Click context.SaveChanges() End Sub
private void btnSave_Click (object sender, System.Windows.RoutedEventArgs e) { context.SaveChanges(); }
Run the form and edit one of the trips, changing a date and the lodging. Close the form and then run it again. When you select that trip again, you'll see that the date was updated but not the lodging. The problem isn't that it's related data, but that the ComboBox does not support updating. It is not sending the change back to the object. You'll have to do that manually using the control's SelectionChanged event. SelectionChanged is the default event, so you can get to it by double-clicking the ComboBox in the Designer. Do this for the Lodging and Destination combo boxes and then add the code in Example 8-17 to change the currently selected trip's Lodging and Destination properties to the ComboBox selections.
Example 8-17. Changing the currently selected trip's Lodging and Destination properties to the ComboBox selections
Private Sub cboLodging_SelectionChanged _ (ByVal sender As System.Object, _ ByVal e As System.Windows.Controls.SelectionChangedEventArgs) _ Handles cboLodging.SelectionChanged Dim selectedTrip = CType(lstBoxTrips.SelectedItem, Trip) selectedTrip.Lodging = CType(cboLodging.SelectedItem, Lodging) End Sub
Private Sub cboDestination_SelectionChanged _ (ByVal sender As System.Object, _ ByVal e As System.Windows.Controls.SelectionChangedEventArgs) _ Handles cboDestination.SelectionChanged Dim selectedTrip = CType(lstBoxTrips.SelectedItem, Trip) selectedTrip.Destination = _ CType(cboDestination.SelectedItem, Destination) End Sub
private void cboLodging_SelectionChanged (object sender, System.Windows.Controls.SelectionChangedEventArgs e) { var selectedTrip = (Trip)lstBoxTrips.SelectedItem; selectedTrip.Lodging = (Lodging)cboLodging.SelectedItem; } private void cboDestination_SelectionChanged (object sender, System.Windows.Controls.SelectionChangedEventArgs e) { var selectedTrip = (Trip)lstBoxTrips.SelectedItem; selectedTrip.Destination = (Destination)cboDestination.SelectedItem; }
8.2.9.1. What if the user changes the destination? You might prevent destinations from being edited on existing trips, but you'll need to use thatComboBox for new trips. If the user changes the trip's destination, you won't see the change on the ListBox. WPF provides a way to sort the items of list controls with aSortedDescriptions collection. If you re-sort the list after the user selects a destination from the combo box, the list will be refreshed, the new destination name will appear, and the item will be properly sorted using the new name.
NOTE WPF's sorting features are very different from what you may be used to. You can read more aboutSortedDescriptions in the MSDN documentation. Add the code in Example 8-18 to the end of the cboDestination's SelectionChanged event.
NOTE Add System.ComponentModel to the Imports/using statements to use this feature.
Example 8-18. Allowing the List to be sorted
lstBoxTrips.Items.SortDescriptions.Add _ (NewSortDescription("Destination.DestinationName", _ ListSortDirection.Ascending)) lstBoxTrips.Items.SortDescriptions.Add _ (New SortDescription("StartDate", ListSortDirection.Descending))
lstBoxTrips.Items.SortDescriptions.Add (new SortDescription("Destination.DestinationName", ListSortDirection.Ascending)); lstBoxTrips.Items.SortDescriptions.Add (new SortDescription("StartDate", ListSortDirection.Ascending));
8.2.10. Adding Items to the Child EntityCollection Next, we'll add the ability to add activities to a trip. To do this, you'll need a way to select a new activity to add. Add a new ComboBox to the form with the name cboActivities. In the Window.Loaded event, you have already queried for the list of activities. Now you need to bind those results to this new ComboBox . Add the following binding beneath the binding forcboLodgings:
cboActivities.ItemsSource = activities
cboActivities.ItemsSource = activities;
Next, the ComboBox needs to know which property to display and which to use as the value. You can set both of these in the Properties window for the ComboBox. Change SelectedValuePath to ActivityID and DisplayMemberPath to ActivityName . Unlike the other combo boxes, no additional bindings need to be typed directly into the XAML. The ComboBox has a SelectionChanged event, but it's not useful for reacting to a user selection. Instead, add another button to the form. We'll use that to read the selected item in the cboActivities ComboBox and add it to the current trip's Activities EntityCollection. The ListBox that shows the activities will update automatically because of its bindings.
Add the code in Example 8-19 to the new button's Click event.
Example 8-19. Adding Activities to the selected trip entity
Private Sub btnAddActivity_Click _ (ByVal sender As System.Object, _ ByVal e As System.Windows.RoutedEventArgs) _ Handles btnAddActivity.Click Dim selectedActivity = CType(cboActivities.SelectedItem, Activity) If Not selectedActivity Is Nothing Then Dim selectedTrip = CType(lstBoxTrips.SelectedItem, Trip) If Not selectedTrip Is Nothing Then selectedTrip.Activities.Add(selectedActivity) End If End If End Sub
private void btnAddActivity_Click (object sender, System.Windows.RoutedEventArgs e) { Activity selectedActivity = (Activity)cboActivities.SelectedItem; if (selectedActivity != null) { var selectedTrip = (Trip)lstBoxTrips.SelectedItem; if (selectedTrip != null) { selectedTrip.Activities.Add(selectedActivity); } } }
This code ensures that an activity and a trip are selected before it tries to perform the main task. Notice how the new activity is added to the trip's Activities collection with the Add method. You will likely use the EntityCollection.Add method quite a lot in your Entity Framework-based applications. Chapter 15 drills into this functionality in detail.
8.2.10.1. Testing the new feature for adding activities Run the application, select a trip, and add some activities. You'll see theActivities ListBox react. You can save the changes with your existing Save method. Note that since the data is not refreshed, again you'll want to stop and start the application for proof that the change was saved.
8.2.11. The Last Task: Adding New Trips to the Catalog
Adding new trips will take a bit more code to implement. Not only will you need to set some defaults on the new trip entity, but also you'll have to use a few tricks to make the user interface flow properly. Start by adding a new button to the form that will be the user's New Trip button. That's all you need to do in the UI. In the button's Click event, you'll create a new trip and set some defaults.
8.2.11.1. A few WPF tricks for a more interactive ListBox Before modifying the new button's Click event, you'll need to make two changes that are related to WPF's data binding and are not specifically related to the Entity Framework. In the Windows form, you had a BindingSource to coordinate between the controls and the data. WPF has no such control; however, it does have another mechanism, called the ObservableCollection. Without getting too sidetracked, if you use anObservableCollection of trips as the source for the Trip ListBox control, as you add and remove items from this collection theListBox will respond by adding or removing the items from the display. It's worth the effort to use this rather than a ListBox so that you won't have to write the extra code to stuff your new trip into theListBox. To pull this off, you'll need a new variable that is scoped to the wholeWindow class. Add this to theWindow 's declarations. We'll instantiate it in the declaration so that it's ready to use in the Window.Loaded event.
NOTE Add the Collections.ObjectModel namespace to the Imports/using statements to use this feature.
Private ObservableTrips As New ObservableCollection(Of Trip)
private ObservableTrips = new ObservableCollection();
In the Window.Loaded event, after the Trips query has been executed, transfer the trip results into the new collection. Then change the ItemsSource of the Trips ListBox to this new collection, as shown in the following code:
For Each t In trips ObservableTrips.Add(t) Next lstBoxTrips.ItemsSource = ObservableTrips
foreach (var t in trips) { ObservableTrips.Add(t); } lstBoxTrips.ItemsSource = ObservableTrips;
Now when you add new trips to the collection, they will automatically pop into theListBox . But they'll be at the bottom and will remain there until you run the application again. That's no good. You can get the ListBox to sort on the DestinationNames and StartDate s using this WPF code, which is very different from what you might be used to with Windows Forms or ASP.NET applications. You can copy the sorting code from theDestination ComboBox's SelectionChanged event into the Window.Loaded event to benefit from the sorting early on. In this way, if you add a new trip before you hit the other location where the sort is applied, the new trip will drop into the correct position in the ListBox. With the ListBox controlling the sort, you can remove theOrderBy method in the Trips query.
NOTE You'll still need the sorting code in the ComboBox to trigger the refresh. There may be a better way to trigger a refresh in theListBox than adding the SortDescription again. But this little trick will do for now.
8.2.11.2. Coding the Add New Trip feature With that functionality in place, you can now add a new trip and have the form respond in an expected manner. The Click event of the New Trip button will add a new trip, set some default values, and add the trip into the ListBox's items (see Example 8-20).
Example 8-20. The Click event of the New Trip button
Private Sub btnNewTrip_Click _ (ByVal sender As System.Object, _ ByVal e As System.Windows.RoutedEventArgs) Handles btnNewTrip.Click 'create a new Trip object with default date values Dim newtrip = New Trip newtrip.StartDate = Today newtrip.EndDate = Today 'add a default destination. Sorting will fail if Destination is null newtrip.Destination = destinations[0] 'add the trip to the context so that its changes will get tracked context.AddToTrips(newtrip) 'add the new trip to the bound collection ObservableTrips.Add(newtrip) 'select the new trip so that the bound controls will be tied to it lstBoxTrips.SelectedItem = newtrip End Sub
private void btnNewTrip_Click (object sender, System.Windows.RoutedEventArgs e) { \\create a new Trip object with default System.DateTime values var newtrip = new Trip(); newtrip.StartDate = System.DateTime.Today; newtrip.EndDate = System.DateTime.Today; \\add a default destination. Sorting will fail if Destination == @null newtrip.Destination = destinations[0]; \\add the trip to the context so that its changes will get tracked; context.AddToTrips(newtrip); \\add the new trip to the bound collection ObservableTrips.Add(newtrip); \\select the new trip so that the bound controls will be tied to it lstBoxTrips.SelectedItem = newtrip; }
8.2.11.3. Validating the new trip before saving You may want to consider using one last bit of code: validation code for the new trip.
You can call the short method shown in Example 8-21 prior to calling SaveChanges to prevent your demo from blowing up if the user gets click-happy. It just checks to make sure that any new trips have a destination and valid start and end dates. If it returns False , it displays some type of message and doesn't call SaveChanges.
Example 8-21. Validating the new trip's data
Private Function validateNewTrips() As Boolean Dim newtrips = From t In ObservableTrips _ .Where(Function(t) t.TripID = 0) For Each Trip In newtrips If Trip.Lodging Is Nothing Then Return False ElseIf Trip.StartDate < Today Then Return False ElseIf Trip.EndDate < Trip.StartDate Then Return False Else Return True End If Next Return True 'when newTrips.Count=0 End Function
private bool validateNewTrips() { var newtrips = from t in ObservableTrips.Where(t => t.TripID == 0) select t; foreach (var Trip in newtrips) { if (Trip.Lodging == null) return false; else if (Trip.StartDate < System.DateTime.Today) return false; else if (Trip.EndDate < Trip.StartDate) return false; else return true; } return true; //when newTrips.Count==0 }
8.2.11.4. Testing the final version of the WPF demo
Run the demo again and check out the new features. When you add a new trip, watch how smoothly the boundTrip ListBox displays the new trip at the top of the ListBox . When you change the default destination, the trip will reappear alphabetically sorted in theListBox , but still selected. Add some activities to the new trip. Save your changes and restart the application to prove that it all really worked (see Figure 8-18).
Figure 8-18. The final WPF form with all of its features in place
8.3. Summary The Entity Framework has a number of levels of entry. In this chapter, you got a chance to apply much of what you learned in previous chapters in creating two starter client-side applications. The Windows Forms application leaned heavily on drag-and-drop data binding, whereas the WPF application let you get your hands a little dirtier as you interacted with the entities in code. The applications in this chapter went beyond typical Hello World introductory demos and gave you an opportunity to learn some of the nuances of data binding with entity objects. At the same time, you learned how to perform some good data-binding tricks in Windows Forms and WPF that will make life with entities a little easier. In the next chapter, you will dive into a little more theory as we go into much more detail regarding how Object Services manages entity objects.
Chapter 9. Working with Object Services Most of the work that you will do in the Entity Framework will involve the objects that are based on the entities in your Entity Data Model (EDM). The Object Services API is the part of the framework that creates and manages these objects. Although you have worked with Object Services in much of the code you wrote in earlier chapters, and you have touched on a variety of its topics along the way, you haven't yet seen the big picture. The API has a lot of tools that you can access directly to take charge of your entity objects. This chapter is devoted to giving you a better understanding of the Object Services API: what it is responsible for, what it does under the covers, and some of the ways that you can take advantage of it. You will learn about how queries are processed and turned into objects, how these objects are managed during their life cycle, and how Object Services is responsible for the way entities are related to each other. You will see how the ObjectQuery works and how it relates to LINQ to Entities queries under the covers. This chapter will also give you a better understanding of how Object Services manages an entity's state, beyond what you learned in Chapter 5. As you become more familiar with the purpose, features, and implementation of Object Services, you will be better prepared to solve some of the challenges you will face as you move from using the "drag-and-drop" application-building features that Visual Studio provides to building enterprise applications where you need to have much more control over how all of the pieces of the application interact with one another.
Database Manipulation Language (DML) command processing Additional features
9.2. Query Processing In the past few chapters, you saw that there are quite a few ways to create queries in the Entity Framework. Object Services is used for all of the query methods except EntityClient , which uses a lower-level API. Object Services sits on top of EntityClient and leverages its functionality on your behalf. At a high level, query processing in the Entity Framework involves translating the LINQ or Entity SQL queries into queries that the data store can process. At a lower level, it first parses your query into a command tree of LINQ or Entity SQL query operators and functions, combined with the necessary entities and properties of your model. The command tree is a format that the various providers that have been designed to work with the Entity Framework will be expecting. Next, the provider API (Oracle, SQL Server, MySQL, etc.) transforms this tree into a new expression tree composed of the provider's operators and functions and the database's tables and columns. This tree is finally passed to the database.
Expression Trees and Command Trees Expression tree and command tree are terms you will see when discussing LINQ and the Entity Framework. An expression tree is a way to represent code in a data structure. This is not limited to LINQ, but by creating an expression tree from a LINQ query, your application can identify particular elements of the query and process them accordingly. A command tree is a form of an expression tree that is used in the Entity Framework. It has a particular structure that can be depended on by the ADO.NET providers, which will need to read that command tree in order to translate the command into their native command syntax. If you'd like to learn more, see the MSDN documentation on expression trees at http://msdn.microsoft.com/en-us/library/bb397951.aspx/.
9.2.1. From Query to Command Tree to SQL LINQ to Entities leverages the LINQ parser to begin processing the query, whereasObjectQuery uses a different parser. After each has gone through its first transition, they both follow the same path. Let's take a look at how each query is turned into the eventual store command.
NOTE Store command or native command refers to the command that the data store uses—for example, a T-SQL command for SQL Server.
9.2.1.1. From a LINQ to Entities query to a command tree LINQ starts its journey in the LINQ APIs and is then passed to the Object Services API. When you create a LINQ to Entities query, you are using syntax that is built into Visual Basic and C# that has enhancements that the Entity Framework has added. LINQ converts this query into a LINQ expression tree, which deconstructs the query into its common operators and functions. The LINQ expression tree is then passed to Object Services, which converts the expression tree to a command tree.
9.2.1.2. From Entity SQL and query builder methods to a command tree The ObjectQuery class and the query builder methods that you've been using are part of Object Services. When building a query with ObjectQuery , you write an Entity SQL string to express the query. If you use query builder methods, those methods will build an Entity SQL expression and an ObjectQuery for you. The ObjectQuery then passes the Entity SQL string to the entity client's parser, and this parser creates a command tree. Whether a query began as a LINQ to Entities query or as an ObjectQuery with Entity SQL, the command trees are the same. From this point on, both types of queries follow the same processing path.
NOTE For the sake of comparison, when you query using EntityClient , its Entity SQL expression is also parsed into a command tree, enters the query path at this stage of the process, and is treated the same as the command trees that were created from LINQ to Entities and ObjectQuery queries.
9.2.1.3. How EntityClient turns command trees into store commands The newly created command tree is still expressed in terms of the entities in the model's conceptual layer. So at this point, the processor uses EDM mappings to transform the terms of the command tree into the tables, columns, and other objects of the database. This process might run through the command tree a number of times to simplify the demands made in the query before it comes up with an equivalent of the database's tables and columns. Once this new version of the tree has been created, it is sent to the store provider (e.g., SqlClient ), which will know how to convert the command tree into its native command text. Entity Framework provider writers use the common schema of a command tree to create their functionality for generating SQL from the command tree. For example, a SqlClient provider will transform the tree into T-SQL that SQL Server can execute; an Oracle provider will transform the tree into a proper PL/SQL command. Figure 9-2 shows the steps these queries take to get to the data store.
Figure 9-2. How the various query styles get to the data store
9.2.2. A Better Understanding of Query Builder Methods Writing Entity SQL is not always simple. Although the process is familiar to those who already write store commands, it is different enough that it will probably take some time before the syntax rolls naturally from your fingertips. Query builder methods can be quite useful, as the methods are discoverable through IntelliSense and take away some of the pain of remembering the exact syntax. In Chapter 4 , you built a variety of queries using the CreateQuery method with an Entity SQL expression as its parameter. You also used query builder methods. Examples Example 9-1 and Example 9-2 are intended to refresh your memory.
Example 9-1. CreateQuery with Entity SQL
Dim qStr = "SELECT VALUE c " & _ "FROM PEF.Contacts AS c " & _ "WHERE c.FirstName='Robert'" Dim contacts = context.CreateQuery(Of Contact)(qStr)
var queryStr = "SELECT VALUE c " + "FROM PEF.Contacts AS c " + "WHERE c.FirstName='Robert'";
var contacts = context.CreateQuery(queryStr);
Example 9-2. Query builder method with Entity SQL parameters
Dim contacts = context.Contacts _ .Where("it.FirstName = 'Robert'")
var contacts = context.Contacts .Where("it.FirstName = 'Robert'")
Both sets of code define the same ObjectQuery , which searches for contacts whose first name is Robert. Neither will actually return results until something forces the query to be executed. The query builder methods may still require that you write part of the expression, such as the Where predicate it.FirstName='Robert' in Example 9-2 , but they are still a great deal easier than using the CreateQuery method.
9.2.2.1. Query builder methods and EntitySets Query builder methods are methods of ObjectQuery . How is it, then, that these methods are available from context.Contacts ? The classes generated from the model reveal the answer to this question. The preceding queries are based on the first model you built and used in Chapters Chapter 3 and Chapter 4. context is a variable that represents the PEF ObjectContext , which is the wrapper class that serves up the EntitySets of the various classes in the model. (In Chapter 3 this was called ProgrammingEFDB1Entities , but in Chapter 4 we simplified it to PEF.) Example 9-3 shows the declaration of this class in the classes generated from the model.
Example 9-3. Declaration of the ObjectContext class
Partial Public Class PEF Inherits Global.System.Data.Objects.ObjectContext
public partial class PEF : global::System.Data.Objects.ObjectContext
This class has a property for each EntitySet—for example, Contacts. Each of these properties returns an ObjectQuery(Of T)/ObjectQuery of the entity type it wraps. The Contacts
property returns an ObjectQuery of Contact entities, as shown in Example 9-4.
Example 9-4. The ObjectContext.Contacts property
_ Public ReadOnly Property Contacts() As _ Global.System.Data.Objects.ObjectQuery(Of Contact) Get If (Me._Contacts Is Nothing) Then Me._Contacts = MyBase.CreateQuery(Of Contact)("[Contacts]") End If Return Me._Contacts End Get End Property
public global::System.Data.Objects.ObjectQuery Contacts { get { if ((this._Contacts == null)) this._Contacts = base.CreateQuery("[Contacts]"); return this._Contacts; } }
Because the property returns an ObjectQuery , it has all of the methods and properties of an ObjectQuery, including the query builder methods: Select, Where , GroupBy, and so forth.
9.2.2.2. From query builder methods to Entity SQL expressions Object Services uses the query builder methods and any expressions, such as what is contained in a Where clause, to build an Entity SQL expression. The result is the same as though you had explicitly created a ObjectQuery and typed in the Entity SQL yourself. You can then use the expression to create a ObjectQuery in the same way you would use a CreateQuery method.
How Can You Tell the Difference Between LINQ Methods and Query Builder Methods? LINQ's method syntax looks very similar to the query builder methods, except for one big difference: the parameters. The parameters of a LINQ method are lambda expressions, whereas the parameters of the query builder methods are Entity SQL string expressions . A number of methods have the same name: Where , OrderBy, Select , and others. The compiler uses the parameters to determine which path to go down, in much the same way that the .NET compiler handles overloaded methods anywhere else.
9.2.2.3. Combining LINQ methods and query builder methods Query builder methods return an ObjectQuery , and you can use a LINQ to Entities method on an ObjectQuery . Therefore, it's possible to compose a query such as the following: context.Contacts.Where("it.FirstName='Robert'").Take(10)
The first part, context.Contacts.Where("it.FirstName='Robert'"), returns an ObjectQuery. Then, LINQ's Take method is appended to that. Take returns an IQueryable; thus, the type of the query that results will be a System.LINQ.IQueryable—in other words, a LINQ to Entities query. You can't go the other way, though, adding query builder methods after a LINQ method. For instance, context.Contacts.Take(10) returns a System.LINQ.IQueryable . You can use query builder methods only on an ObjectQuery and you cannot append a query builder method to this IQueryable without first casting the LINQ query to an ObjectQuery
and then appending the method. Casting a LINQ to Entities query to ObjectQuery is beneficial in a number of scenarios, and you'll see these as you move
forward in this chapter.
9.2.3. Breaking Apart the ObjectQuery
You have already seen some of the members of ObjectQuery , such as the query builder methods and the Include method. Additional methods and properties are available that will help you better understand the role of ObjectQuery . Here are some that you can see when inspecting an ObjectQuery in the debugger. Figure 9-3 shows an ObjectQuery in debug mode with its properties, as well as a way to access the results. Figure 9-4 shows a LINQ to Entities query in the debugger; as you can see, LINQ to Entities exposes the results directly, but also contains an ObjectQuery.
Figure 9-3. An ObjectQuery called contacts in the debugger, showing the various properties of ObjectQuery
Figure 9-4. A LINQ to Entities query in the debugger, showing that the LINQ to Entities query contains an ObjectQuery
If you want to get to ObjectQuery properties and methods from a LINQ to Entities query, you can cast the LINQ to Entities query to ObjectQuery.
9.2.3.1. ToTraceString One very helpful ObjectQuery method is ToTraceString , which displays the native store command that will be created from your query. Figure 9-5 shows some code that calls ToTraceString and the value the method returns at runtime.
Figure 9-5. Viewing the native command that will be generated from an ObjectQuery using the ToTraceString method while debugging
Example 9-5 demonstrates casting a LINQ to Entities query to an ObjectQuery in order to call the ToTraceString method.
Example 9-5. Casting a LINQ to Entities query to use ObjectQuery methods such as ToTraceString
Dim contacts = From c In context.Contacts Where c.FirstName = "Robert" Dim str = CType(contacts, Objects.ObjectQuery).ToTraceString
var contacts = from c in context.Contacts where c.FirstName == "Robert" select c; var str = ((Objects.ObjectQuery)contacts).ToTraceString();
9.2.3.2. ObjectQuery.CommandText As with ADO.NET, CommandText refers to the query string being passed in for execution. Because of the different ways in which you can build queries with the Entity Framework, CommandText is represented in a variety of ways, as shown in Table 9-1.
Table 9-1. CommandText values of various types of queries Query method
Query
ObjectQuery.CommandText
ObjectQuery
Context.Contacts
[Contacts]
context.CreateQuery(Of Contact)
SELECT VALUE c
("SELECT VALUE c
FROM PEF.Contacts AS c
ObjectQuery with Entity SQL
FROM PEF.Contacts AS c
WHERE c.FirstName='Robert'
WHERE c.FirstName='Robert'")
Query builder context.Contacts
SELECT VALUE it
.Where("it.FirstName = 'Robert'")
FROM (
.OrderBy("it.LastName")
SELECT VALUE it FROM ( [Contacts] ) AS it WHERE it.FirstName = 'Robert' ) AS it ORDER BY it.LastName
LINQ to Entities
(empty) From c In context.Contacts Where c.FirstName = "Robert"
9.2.3.3. ObjectQuery.Parameters In Chapter 4 , you saw how to build a parameterized query. Any parameters that you created then will be listed in the ObjectQuery's Parameters property.
9.2.3.4. ObjectQuery.Context The Context property refers to the instantiated ObjectContext from which the ObjectQuery is being run. The ObjectContext not only coordinates the execution of queries and provides the mechanism for SavingChanges back to the data store, but also plays a much bigger role as the manager of objects in memory.
9.2.4. Query Execution with the ToList or ToArray Method So far, the query has been defined but no data retrieval has actually occurred. Query execution occurs when the Entity Framework retrieves the data from the store. Queries can be executed implicitly or explicitly. In previous chapters, you enumerated over the results of a query (using VB's For Each or C#'s foreach ). Enumerating over a query will force a query to execute implicitly. You don't need to specifically say "go get the data." The fact that you are attempting to work with the query results will cause the Entity Framework to do that for you. Another way to force execution is to append the ToList or ToArray LINQ method to a query. Example 9-6 appends ToList to the CreateQuery method to execute the query immediately and return a list of Contact entities.
Example 9-6. Executing a query with ToList
Dim contacts = context.CreateQuery(Of Contact)(queryStr).ToList()
var c2 = context.CreateQuery(queryStr).ToList();
NOTE A big difference between using ToList or ToArray rather than enumerating is that these methods will force the complete results to be returned all at once. When enumerating, depending on what you are doing with each result as you get to it, it may take awhile before you get to the end of the results. Until that time, the database connection will remain open.
9.2.5. Query Execution with the Execute Method ObjectQuery has
an Execute method, which also forces execution, but it requires a parameter to define MergeOption s for the objects that result, as shown in the
following code:
Dim contacts = context.Contacts.Execute(MergeOption.AppendOnly)
var contacts = context.Contacts.Execute(MergeOption.AppendOnly);
Four merge options influence how newly returned objects impact objects that may already exist in memory. MergeOption is also a property of the ObjectQuery , so you can set the value directly even when you're not using the Execute method. is the default, and it will be used when you don't set the option directly while executing queries without the Execute method. However, with Execute , you must set this parameter, even if you just want the AppendOnly default. AppendOnly
You used Execute in the preceding chapter for the Windows Forms data-binding example. Execute does not return an ObjectQuery, but rather a type called ObjectResult. An ObjectQuery contains all of the metadata about the query itself, the query expression, the connection information, and more, as well as any results (after the query has been executed). When you use the Execute method, however, you have only the results, not the metadata. Using Execute is beneficial in some scenarios, but in others, its limitations, such as the fact that you can enumerate over ObjectResult s only once, might be a problem. Because MergeOption impacts what happens with the returned data, its purpose will make more sense after we have discussed some additional topics. We'll return to MergeOption in more detail later in this chapter.
9.2.6. ObjectContext.Connection By default, ObjectContext will use the connection string defined in the application's app.config file that has the same name as the name of the context's EntityContainer . For example, when the EntityContainer name is BAEntities , Object Services will search for a connection string named BAEntities in the app.config file. If no matching connection string is found and no override is provided, an exception will be thrown at runtime. The exception reads "The specified named connection is either not found in the configuration, not intended to be used with the EntityClient provider, or not valid." You can override the default behavior in a number of ways. One way to override the default is to supply a different connection string name in the constructor of the . This string needs to be available in the app.config file as well. Example 9-7 uses the connection string named connString to create an ObjectContext .
ObjectContext
Example 9-7. Specifying which EntityConnection string to use for a context
Dim context=new PEF("Name=connString")
var context = new PEF("Name=connString");
NOTE You can't use only the connection string name. You also need to add "Name=" as part of the parameter. Another way to override the default is to supply an EntityConnection object instead. This would be the same EntityConnection that is used with the EntityClient provider. By creating an explicit EntityConnection, you can manipulate that EntityConnection prior to instantiating a context with it. Example 9-8 creates the EntityConnection but does not do anything special with it. You will learn a lot more about manipulating an EntityConnection in Chapter 16.
Example 9-8. Explicitly creating a new EntityConnection to use with a context
Dim econn = New EntityConnection("name=connString") Dim context = New PEF(econn)
var econn = new EntityConnection("name=connString"); var context = new PEF(econn);
9.2.6.1. Why does the context need the EntityConnection? The EntityConnection gives ObjectContext three important pieces of information: metadata , database connection information, and the name of the ADO.NET data provider. The metadata, which points to the Conceptual Schema Definition Layer (CSDL), Store Schema Definition Layer (SSDL), and Mapping Schema Layer (MSL) files, provides the context with the location of these files. You can embed them into an assembly or place them somewhere in the file system. The context needs access to the metadata files to begin the process of transforming the query into the store command. ObjectContext
will pass the database connection string onto the EntityClient layer so that it will be able to connect to the database and execute the command.
The last element of an EntityConnection string is the name of the data provider (e.g., System.Data.SqlClient ). This tells the Entity Framework to which data provider to send the command tree for part of the query processing.
9.2.7. Handling Command Execution with EntityClient So, what's next? You've got your ObjectQuery all set. You know the ObjectQuery will do all of the work to create a command tree. Somehow, the command tree gets handed off to the EntityClient provider along with the database connection string provided by the ObjectContext . If you dig into the Entity Framework assemblies using Reflector, you will find that the ObjectContext calls on EntityClient to do the job of creating the connection and executing the command on the data store.
As you saw with the EntityClient queries in Chapter 3, EntityClient returns an EntityDataReader, not objects.
9.3. Object Materialization After EntityClient retrieves the database results into an EntityDataReader, it passes the EntityDataReader back up the stack to Object Services, which transforms, or materializes, the results into entity objects. The data in EntityDataReader is already structured to match the conceptual layer, so it's just a matter of those contents being cast to objects. If the query used a projection and there is no matching entity, the results are materialized into DbDataRecord s instead of entity objects, as you saw in many of the queries you wrote earlier. Figure 9-6 demonstrates the path a query takes from the command tree to the database and then back to Object Services to be materialized into objects.
Figure 9-6. The EntityClient providing the command execution functions for an ObjectQuery
9.4. The ObjectContext ObjectContext is the core class in Object Services. It performs a variety of functions: It provides the connection and metadata information needed to compose and execute queries, as well as to persist data to the data store. It acts as a caching container for objects in memory, whether they have been retrieved from the database, created programmatically, or brought from another ObjectContext. It maintains the state information of all of the objects it is managing, as well as retaining the original and current values of all of the objects. It provides relationship information so that graphs can be created from related objects that it is managing. You have actually seen the ObjectContext perform all of these functions in the code you wrote in previous chapters. Now it's time to dig a little deeper.
9.4.1. ObjectContext Is a Cache for In-Memory Objects When objects are returned from queries, ObjectContext creates pointers to each entity, in effect, caching references to these entities. ObjectContext not only keeps track of all of these entities, but also keeps track of other information regarding those entities, including their state, their original and current values, and their relationships to one another.
An ObjectContext by Any Other Name You can refer to the ObjectContext, or talk about objects that theObjectContext is managing, in several ways: The ObjectContext = Context = Cache = Object cache The entity is managed by the context = The entity is being change-tracked = The entity is cached = The entity is attached to the ObjectContext
9.4.1.1. Objects are not required to be in the ObjectContext cache Objects can be in memory without being managed by theObjectContext . That means that although the object instance exists, the ObjectContext is not aware of the object. You can have anEntityObject in application memory that is not being tracked by the context, by doing any one of the following: Explicitly instruct the ObjectQuery to return objects without attaching them to the cache. You can do this by setting ObjectQuery.MergeOption to the NoTracking option. Use the ObjectContext.Detach method to explicitly detach an object from the ObjectContext. Create a new object in memory. Unless or until you explicitly attach or add the object to theObjectContext or to an object that is already in the cache (e.g., adding a Reservation to a Customer's Reservation EntityCollection property or adding a Customer as a Reservation's CustomerReference ), it is not part of the cache. Deserialize entities that were serialized. Although the act of serializing an entity or entities does not detach entities from their ObjectContext , the entities that are in the serialized package will not be attached to anObjectContext when they are deserialized. The EntityState of an object that is not in the cache is alwaysDetached.
Chapters Chapter 15 and Chapter 17 will provide much more insight into controlling theObjectContext and the effect that caching has on entities' relationships and change tracking.
9.4.2. Entity Objects When objects are returned from queries, they are managed by theObjectContext by default. The only responsibility of an entity object is to know what its current state is, that is, to know the current values of its properties.
NOTE An entity object that is being managed by theObjectContext is considered to be "attached" to that context. This is when the ObjectContext maintains an ObjectStateEntry for that entity. You first learned about theObjectStateEntry type in Chapter 5. The properties of an entity class are the scalar and navigation properties that define the schema of that entity. For example, the BreakAway Contact class has only the following properties: ContactID, FirstName, LastName, AddDate, ModifiedDate, Title, Customer, Addresses, and Lodging . These are the same properties that are in the entity in the model. You can look into the classes to see what the code looks like.Example 9-9 shows the Visual Basic code related to theFirstName scalar property.
Example 9-9. The VB code related to the FirstName scalar property
_ Public Property FirstName() As String Get Return Me._FirstName End Get Set Me.OnFirstNameChanging(value) Me.ReportPropertyChanging("FirstName") Me._FirstName = Global.System.Data.Objects.DataClasses. _ StructuralObject.SetValidValue(value, false, 50) Me.ReportPropertyChanged("FirstName") Me.OnFirstNameChanged End Set End Property Private _FirstName As String Partial Private Sub OnFirstNameChanging(ByVal value As String) End Sub Partial Private Sub OnFirstNameChanged() End Sub
The code generator created two events for theFirstName property: OnFirstNameChanged and OnFirstNameChanging . These partial
methods are defined in the class to enable you to add code to handle these events. You will learn how to extend the classes' event handling and other code in Chapter 10. Each entity class also has a single method whose name begins with the wordCreate . These are factory methods that allow you to create a new instance of that class. The arguments of the Create methods take values for each non-nullable scalar property in the class. For the Contact class, you may notice that theTitle parameter is not being used by theCreateContact method shown in Example 9-10. That's because Title is a nullable property.
Example 9-10. The CreateContact method
Public Shared Function CreateContact(ByVal contactID As Integer, _ ByVal firstName As String, ByVal lastName As String, _ ByVal addDate As Date, ByVal modifiedDate As Date) As Contact Dim contact As Contact = New Contact contact.ContactID = contactID contact.FirstName = firstName contact.LastName = lastName contact.AddDate = addDate contact.ModifiedDate = modifiedDate Return contact End Function
public static Contact CreateContact(int contactID, string firstName, string lastName, global::System.DateTime addDate, global::System.DateTime modifiedDate) { Contact contact = new Contact(); contact.ContactID = contactID; contact.FirstName = firstName; contact.LastName = lastName; contact.AddDate = addDate; contact.ModifiedDate = modifiedDate; return contact; }
Once you have used this function to create a new contact, you can add additional properties in the code. You won't find much more when looking in the classes generated from the model. The entity class itself has no properties for keeping track of its state changes. It only maintains the current values of its properties.
9.4.3. EntityKey and EntityState The entity does, however, inherit fromEntityObject, and therefore it exposes two additional properties that come fromEntityObject:
EntityKey and EntityState. EntityKey is a critical class for keeping track of individual entities. It contains the entity's identity value, which could be from a single property, such as ContactID , or could be a composite key that depends on a number of the entity's properties.Figure 9-7 shows an EntityKey for a BreakAwayContact . It says that this entity belongs to theBAEntities container and to the Contacts EntitySet, and that its key property is composed of only one property, ContactID, whose value is 1. The ObjectContext reads the EntityKey information to perform many of its functions. For example, when the context merges objects, locates entities in the cache, or creates EntityReference values. The type information is not included in theEntityKey. Instead, the EntitySetName indicates to which EntitySet the object with this key belongs. This little class is one of the most important classes in the Entity Framework. It acts as an object's passport throughout the application's runtime.
Figure 9-7. An object's EntityKey, which includes critical identity information for each object
9.4.3.1. The EntityState enums The EntityState property of an object's EntityKey identifies whether the entity is:
Added An entity was instantiated at runtime and added to the context. WhenSaveChanges is called, Object Services will create an Insert command for this entity.
Deleted An entity is being managed by the cache and has been marked for deletion. WhenSaveChanges is called, Object Services will create a Delete command for this entity.
Detached
The ObjectContext is not tracking the entity.
Modified The entity has been changed since it was attached to the context.
Unchanged No changes have been made to the entity since it was attached to the context.
9.4.4. Merging Results into the Cache By default, anytime the ObjectContext performs a query, if any of the returned objects already exist in the cache the newly returned copies of those objects are ignored. The EntityKey s are instrumental in enabling this to happen. TheEntityKey s of the objects returned from a query are checked, and if an object with the same EntityKey (within the same EntitySet; e.g., Contacts ) already exists in the cache, the existing object is left untouched. You can control this using an ObjectQuery property called MergeOption , which was introduced briefly earlier in this chapter. The four possibilities for MergeOption are as follows:
AppendOnly (default) Add only new entities to the cache. Existing entities are not modified.
OverwriteChanges Replace the current values of existing entities with values coming from the store.
PreserveChanges Replace original values of existing entities with values coming from the store. The current values are untouched, and therefore any changes the user makes will remain intact. This will make more sense after we discuss state management later in this chapter.
NoTracking Objects returned by the query will not have their changes tracked and will not be involved in SaveChanges . Again, this will make more sense after we discuss state management. There are two ways to define MergeOption s. The first is to use the MergeOption method of ObjectQuery , as shown in the following code:
Dim contacts = context.CreateQuery(Of Contact)(queryString) contacts.MergeOption = MergeOption.PreserveChanges
var contacts = context.CreateQuery(queryString); contacts.MergeOption = MergeOption.PreserveChanges;
The second way to define a MergeOption is as a parameter ofObjectQuery.Execute, as you saw earlier in this chapter. Remember that you can cast a LINQ to Entities query to anObjectQuery and use ObjectQuery methods, including MergeOption, as you did with ToTraceString earlier in this chapter.
9.5. State Management and ObjectStateEntry In Chapter 5, you learned that ObjectContext manages the state information for each of its objects. You were also introduced to the ObjectStateEntry classes that ObjectContext
maintains—one for each entity and relationship in its cache. Let's look more closely at the ObjectStateEntry classes that track the entity objects.
You can retrieve an ObjectStateEntry by passing an EntityKey to the ObjectContext.ObjectStateManager.GetObjectStateEntry method.
NOTE GetObjectStateEntry
has a sibling method TryGetObjectStateEntry . In this chapter, you will get a high-level look at the ObjectStateManager and ObjectStateEntry classes.
Chapter 15 will dig much more deeply into these classes. Debugging the ObjectStateEntry won't give you much insight into the object, but you can see that the ObjectStateEntry in Figure 9-8 is for the Contact whose ContactID is 6.
Figure 9-8. The ObjectStateEntry for a Contact whose ContactID is 6
The more interesting information is returned from some of the methods of the entry: CurrentValues and OriginalValues . These methods return an array of the values for each scalar property. You do need to know the index position of the property you are seeking; for example, you can return the original value of FirstName by calling contactEntry.OriginalValues(3) in VB or contactEntry.OriginalValues[3] in C#. Also, metadata is available from the entry, so it is possible to find values by using the property names. This will take a bit more effort, and you'll learn about navigating around these entries in Chapter 17. Figures Figure 9-9 and Figure 9-10 use a custom utility to show the ObjectStateEntry information for an entity before and after some changes have been made. I call the utility the Object State Entry Visualizer and you will actually be writing it yourself in Chapter 17.
Figure 9-9. Inspecting and displaying information from an unchanged entity's ObjectStateEntry
Figure 9-10. The same viewer shown in Figure 9-9, but presenting the changes made to the entity as defined in its ObjectStateEntry
What is most important to understand right now is that CurrentValues and OriginalValues are tracked, but the ObjectContext maintains this information.
9.5.1. Change Tracking The ObjectContext knows about the state of an object through change tracking. Every entity implements the IEntityWithChangeTracker interface. Recall that the PropertyChanging
and PropertyChanged events in the model classes represent part of the change-tracking functionality. When an object's property is changed, the
IEntityWithChangeTracker
interface reports this change to the designated ChangeTracker —that is, the current ObjectContext , which updates the appropriate value of that
object's ObjectStateEntry . For this to work, the Object inherits internal functions from IEntityWithChangeTracker. This interface has a public member—the SetChangeTracker method—that allows you to identify which ObjectContext is responsible for the change tracking. These methods are used internally, and although they may seem tempting in some scenarios, you will almost always be better off not using them directly.
9.6. Relationship Management Although objects know how to traverse from one to another, it is the ObjectContext that binds related objects together. This may not be evident, even if you perform a query that explicitly retrieves a graph, such as in the following:
context.Customers.Include("Reservations.Trip") .Include("Reservations.Payments")
Figure 9-11 depicts the graph that results from this query.
Figure 9-11. A Customer graph including Reservations and other related objects
In fact, although it may look like your query is shaping the returned data, the object graph is shaped by the ObjectContext after the objects have been materialized and attached to the context. TheObjectContext 's ability to identify and implicitly join related entities is referred to as its relationship span.
NOTE This chapter aims to give you a high-level understanding of relationships. However, relationships and associations are a very interesting topic, and we will cover them much more thoroughly in Chapter 15. You can explicitly combine related entities in code. Here's an example of code that creates a new Reservation object and then adds it to a Customer's Reservations property. The Reservations property is an EntityCollection , so this code adds the new Reservation not to the Customer, but to the collection:
Dim res = Reservation.CreateReservation(0, New Date(2008, 10, 1)) c.Reservations.Add(res)
var res = Reservation.CreateReservation(0, new DateTime(2008, 10, 1)); c.Reservations.Add(res);
However, if you were to perform queries that returnedCustomers and Reservations separately, the ObjectContext would identify those that are related and make it possible for you to traverse through Customer.Reservations or
Reservation.Customer with no effort. TheObjectContext takes care of that for you through its relationship span capability. When poking around the entity classes, you may have noticed that theEntityCollection properties, such as Addresses and Reservations, were read-only. Because of the wayObjectContext works, you can't attach an EntityCollection directly to an entity. In other words, if you had a collection of Addresses that belong to a contact, you can't just call
Contact.Addresses=myAddressCollection. Instead, you must add theAddress entities to theContact.Addresses entity collection one at a time using context.Addresses.Add(myAddress). There is also an Attach method for connecting related entities. Chapter 15 is devoted to the ins and outs of relationships in the Entity Framework.
Object Graphs An object graph refers to a set of individual but related objects that are seen as a whole unit. An Entity object graph may look like one object when you are coding against it, but in fact, it is a collection of objects and relationships. The most tangible way to view a graph is to serialize it to XML, whereby the related objects are represented hierarchically. In this representation, each object contains the objects to which it is related.
9.6.1. Attaching and Detaching Objects from the ObjectContext I have mentioned the topic of attaching and detaching objects a number of times in this chapter. Objects whose changes and relationships are being managed by the context are considered to be attached. EntityObject instances that are in memory but are not being managed by the context are considered to be detached, and even have their EntityState value set toDetached. Attaching and detaching can happen implicitly thanks to the internal functionality of the Entity Framework, or explicitly by calling the methods in your code. You have seen that an object that is attached to anObjectContext has its state and its relationships managed by that context. You also know that an object that is detached has no state. And you have dealt with many objects in the coding samples that were automatically attached to the ObjectContext as the result of executing a query; you even added an object or two using the Add and Attach methods. Now you will look a little more closely at explicitly attaching and detaching objects.
9.6.1.1. ObjectContext.Add Use ObjectContext.Add to add newly created objects that do not exist in the store. The entity will get an automatically generated temporary key and its EntityState will be set toAdded. Therefore, when SaveChanges is called, it will be clear to the Entity Framework that this entity needs to be inserted into the database.
Beware of added entities that are joined to other objects. Object Services will attempt to add the related objects to the database as well. You'll learn more about this, and how to deal with this behavior when working with a WCF service, in Chapter 14.
9.6.1.2. ObjectContext.Attach To attach an object to a context, use the ObjectContext.Attach method as shown in the following code:
context.Attach(myObject)
context.Attach(myObject);
The Attach method requires an object that has an existingEntityKey. An object will have anEntityKey if it has come from the data store or if you explicitly create the key. But these objects that you are attaching are assumed to exist in the data store. When you call SaveChanges , the value of theEntityKey is used to update (or delete) the appropriate row by finding its matching ID (most often a primary key) in the appropriate table. When you attach an entity using the Attach method, the object's EntityState becomes Unchanged . This means nothing has happened to the entity since the time it was attached.
What Happens to EntityState When You Attach or Detach Entities? When attaching to a context, a brand-newObjectStateEntry is created, its EntityState is Unchanged , and both OriginalValues and CurrentValues are populated with the single set of property values from the incoming entity. When detaching an object, its ObjectStateEntry is removed from the context.
What if you have made changes to an entity and then detached it and attached it again? As stated earlier, the newly attached entity will be Unchanged and all of the changes that you may have made earlier will be lost. This is expected behavior for the Entity Framework, but to many developers who are new to working with the Entity Framework, this is surprising behavior. Remember that the object doesn't own its state information; an ObjectContext does. If you have an object that is being tracked and has changes, but then you detach the object, the ObjectStateEntry for that object is removed from the context. All of the state is gone, including the original values. Poof! When you Attach to a context, a brand-newObjectStateEntry is created. The property values for the incoming object are used to populate the OriginalValues and CurrentValues arrays of the ObjectStateEntry.
9.6.1.3. ObjectContext.AttachTo An object needs an EntityKey to be change-tracked and to be involved in relationships. If you need to attach an object that does not have an EntityKey , you can use theAttachTo method, which also requires that you indicate to which
EntitySet the object belongs. With the name of theEntitySet, the Context can dynamically create an EntityKey for the object. The following example shows how to use theAttachTo method, where myContact is an already instantiated Contact entity object:
context.AttachTo("Contacts",myContact)
context.AttachTo("Contacts",myContact);
In some cases, an object may not have an EntityKey. For example, an EntityKey is generally an indication that the object has come from the data store and has some type of a primary key field. Newly added objects are given temporary EntityKey s. But what if you want to work with an object whose data exists in the data store, but you are creating that object on the fly in memory without actually retrieving it first? In this case, this object will not have an EntityKey by default, and you'll need to create one yourself. You can create an EntityKey object using an EntitySet name, the name of the property that is the identifier, and the value of that key. As mentioned earlier, it is possible to have composite keys, so you would construct those by first creating KeyValuePair s and then adding them to theEntityKey.
9.6.1.4. Creating an EntityKey on the fly You may need to create anEntityKey on the fly if, for example, you are creating a newCustomer and you need to identify the CustomerType for the Customer . Say, for instance, that customers in our BreakAway example can be classified as "Standard," "Silver," or "Gold." A CustomerType table is in the database and aCustomerType entity is in the model. In some scenarios, you may not have CustomerType entities already in memory, and you won't want to create a new CustomerType because the Entity Framework will attempt to add the type to the database. But the
business rule is that all customers start out as Standard customers and the CustomerTypeID for Standard customers is
1.
NOTE The EDM allows you to define default values for scalar properties but not for navigation properties, so you may find yourself frequently needing to create a default value for an EntityReference just as in this example. InChapter 10 , you'll learn how to define this type of business logic for your entities so that you don't need to repeat it frequently in your code. This is a great scenario for using anEntityKey , which you can then use for setting theCustomerTypeReference of the new Customer . The simplest constructor for anEntityKey takes a qualified EntitySet name (the EntityContainer name plus the EntitySet name), the name of the property that holds the key, and the value.Example 9-11 shows a new
EntityKey being created for a CustomerType that is wrapped by theCustomerType EntitySet. The new EntityKey is then used for the CustomerTypeReference of a new Customer object.
Example 9-11. Creating a new EntityKey
Dim typeEKey = _ New EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1) newcust.CustomerTypeReference.EntityKey = typeEKey
var typeEKey = new EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1); newcust.CustomerTypeReference.EntityKey = typeEKey;
9.6.2. ObjectContext.ApplyPropertyChanges: A Handy Method for Updating Entities ApplyPropertyChanges is an extremely useful method ofObjectContext because it so handily solves the problem of using detached objects to perform updates. If you have an object in the context and a copy of that object that is detached, you can update the attached object with properties from the detached object. The method needs the name of the EntitySet to find the attached entity and the object that will be used to update the attached entity. The signature for
ApplyPropertyChanges is as follows:
ApplyPropertyChanges(entitySetName as String, changed as Object)
ApplyPropertyChanges(string entitySetName, Object changed)
NOTE You'll see ApplyPropertyChanges used in many of the samples throughout this book where you need to work with detached entities. For example, you'll find this used in both services applications inChapter 14, and then again in later chapters on ASP.NET and WCF services. Here's how it works. The context looks at the changed object passed into the second parameter. If that object does not have an EntityKey , the context can build one based on the metadata of theEntitySet defined in the first parameter. For example, the Contacts EntitySet is defined to returnContact entities, and the Contact property that is used for the
EntityKey is ContactID . That's enough information to construct theEntityKey if necessary. With theEntityKey in hand, the context looks for an attached entity in the Contacts EntitySet with a matching EntityKey . Once that is found, it compares the scalar values of the attached entity to the detached entity and updates any of the properties in the attached entity with new values from the incoming object. As those changes are made, the ObjectStateEntry and
EntityState are impacted. When it comes time to callSaveChanges , the new values will be sent to the server. ApplyPropertyChanges will update only scalar values. It does not touch navigation properties. You will see in later chapters how to use ApplyPropertyChanges in a graph.
9.7. Sending Changes Back to the Database Not only is Object Services focused on getting data from the database and managing those objects, it also manages the full life cycle of the objects, including persisting changes back to the database.
9.7.1. ObjectContext.SaveChanges You spent a good deal of time learning about theObjectContext.SaveChanges method in action in Chapter 5 . This is an important function of Object Services. Here we'll take a look at a few more features of SaveChanges .
Can Updates, Inserts, and Deletes Be Handled in Bulk? As you saw in Chapter 5, each command generated bySaveChanges is sent to the database one at a time to be executed. Unfortunately, bulk processing of commands is not something that the Entity Framework version 1 is able to perform intrinsically. However, Alex James, a program manager on the Entity Framework team, has written a series of blog posts about how to pull this off with the Entity Framework. See http://blogs.msdn.com/alexj/ for more information.
NOTE In Chapter 18 , you'll learn about optimistic concurrency and you'll see when additional values are sent to the database for concurrency checking.
9.7.2. SaveChanges Returns an Integer A little-known fact about the SaveChanges method is that it returns an integer representing the number of
ObjectContext objects that were affected. As you will learn in more detail later in this book, this number includes not only entity objects, but also relationship objects that the ObjectContext maintains. Therefore, if you create a new entity and then add that entity to the
EntityCollection of another entity, a relationship object is created to represent the relationship between the two entities. Initially, that object has an EntityState of Added because it is a new object. If you move a child entity from one parent to the other, the relationship object that represented the first relationship is deleted and a new one is created to reflect the new end (the new parent). After the SaveChanges call, all of the changes will be accepted in theObjectContext and every object'sEntityState will become Unchanged . So, whether that object is an entity or a relationship it will be counted in the number returned by SaveChanges .
9.7.3. Data Validation with the SavingChanges Event ObjectContext has one public event, SavingChanges, which occurs whenSaveChanges is called. It is your only opportunity to validate or impact the entities before the commands are created. The code you insert into SavingChanges will run before the API performs the actualSaveChanges method. In this single location, you can perform validation on all of the entities that theObjectContext is managing. This could be
a little daunting, as you are more likely used to organizing validation information within individual classes. Unfortunately, with the Entity Framework, you'll need to pile this business logic all into one place, or at least make all of the calls to perform validation in this one event. You'll learn how to implementSavingChanges in the next chapter.
9.7.4. Concurrency Management Data concurrency is the bane of any data access developer trying to answer the question "What happens if more than one person is editing the same record at the same time?" The more fortunate among us deal with business rules that say "no problem, last one in wins." In this case, concurrency is not a problem. More likely, it's not as simple as that and there is no silver bullet to solve every scenario at once. Be default, the Entity Framework will take the path of "last one in wins," meaning that the latest update is applied even if someone else updated the data between the time the user retrieved the data and the time he saved it. You can customize the behavior using a combination of attributes in the EDM and methods from Object Services. Chapter 18 will deal with this topic in depth, but here is a short overview of the functionality provided.
9.7.4.1. Optimistic concurrency The Entity Framework uses an optimistic concurrency model, which means it does not lock records in the database, making it possible for others to read and write data in between a user's retrieval and update.
9.7.4.2. ConcurrencyMode In the EDM, the scalar properties of an entity have an attribute calledConcurrencyMode. By default, this is set toNone . In a typical data application, usually particular pieces of data raise concurrency concerns, not an entire row in a table. When you set the ConcurrencyMode of a particular property toFixed , Object Services will use the value of that property to determine whether a change in the database row is something about which you need to be informed.
9.7.4.3. OptimisticConcurrencyException When SaveChanges is called, if any of the flagged values have changed in the database, an OptimisticConcurrency exception will be thrown. Chapter 18 will go into great detail about handling these exceptions.
9.7.5. Transaction Support Object Services operations performed against the data store, such as queries or theSaveChanges method, are transactional by default. You can override the default behavior using System.Transaction.TransactionScope,
EntityTransaction , or one of the otherSystem.Data.Common.DbTransaction classes, such as SqlClient.SqlTransaction. EntityTransaction inherits from DbTransaction as well.
NOTE Transaction support works only with operations against the store, not with operations against objects. By default, the last step ofSaveChanges is to accept all changes to the objects. This means that in the ObjectStateEntry , the original values are replaced with the current values and the object's EntityState is set to
Unchanged . This is especially important with respect to values that are generated on the server. This action will use those returned values as well. However, when SaveChanges is inside your transaction, the changes don't come back from the server until you call
DbTransaction.Commit or TransactionScope.Complete. Because of this, you need to set AcceptChangesDuringSave , the SaveChanges argument, to False. Additionally, after theCommit or Complete is called, you will need to manually call ObjectContext.AcceptAllChanges. You'll find more information on transactions in Chapter 16.
9.8. Additional Features Object Services' core features revolve around query processing and managing objects, as you have now seen. However, Object Services works with entity objects in other ways as well. We'll look at some of the more important of these features.
9.8.1. Object Services Supports XML and Binary Serialization Data is serialized in order for it to be transmitted across boundaries and processes, most commonly with remote or message-based services. Entity classes generated from the EDM are extended with theSerializable and DataContractAttribute attributes, as shown in the following code:
_ Partial Public Class Contact Inherits Global.System.Data.Objects.DataClasses.EntityObject ... End Class
System.Serializable enables the object to be binary-serialized or XML-serialized. Binary serialization is used implicitly in ASP.NET, though in some scenarios you may need to explicitly code the serialization to persist or stream data. XML serialization is most commonly used to send messages to and from web services. The DataContractAttribute enables serialization for exchanging data with Windows Communication Foundation (WCF) services. In addition, EntityKey s are serialized along with the object. This means the object can be transmitted between applications and services, in some cases with very little effort on the part of the developer.
9.8.1.1. ObjectContext, ObjectStateManager, and ObjectStateEntry are not serializable It is very important to be aware that in version 1 of the Entity Framework,ObjectContext, ObjectStateEntry, and ObjectStateManager are not serializable. This is one of the reasons I have emphasized the fact that objects do not retain their own state information. Without writing your own custom code, you cannot serialize or transport the change tracking or state information of your objects. You will learn more about this, and how to handle state when crossing process boundaries, first in Chapter 14 and later in ChaptersChapter 21 and Chapter 22. These chapters deal with web services and ASP.NET applications.
9.8.1.2. Automatic serialization Anytime you pass an object or a set of objects as a parameter to a web or WCF service operation, the object will automatically be serialized as it is sent to the service. When it receives the serialized data, the service will automatically deserialize the object(s) and be able to work with it right away.
9.8.1.2.1. XML and DataContract serialization XML serialization is used for ASMX Web Services and can also be used with WCF. WCF more commonly uses data contract serialization, which does serialize into XML, but differently than XML serialization.
NOTE Aaron Skonnard compares the two in theMSDN Magazine article "Serialization in Windows Communication Foundation" (http://msdn.microsoft.com/en-us/magazine/cc163569.aspx/). Whether you are using an ASMX Web Service or WCF, your entities are automatically serialized into XML when they are transmitted between a service operation and a client application.
NOTE You are getting only a quick overview of building and consuming web services and WCF services here. Chapter 14 provides detailed walkthroughs of these processes. In the following example of a WCF service contract, the GetContact operation signature indicates that aContactID must be sent from the client and that a Contact entity is returned to the client, as shown in the following code:
_ Function GetContact(ByVal contactID As Integer) As Contact
[OperationContract()] Contact GetContact(int contactID );
In the next code snippet, the function queries the EDM to retrieve the data, and then returns the Contact:
Using context As New BreakAwayEntities Dim contact = From c In Context.Contacts _ Where c.ContactID = contactID Return contact.FirstOrDefault End Using
using (BreakAwayEntities context = new BreakAwayEntities()) { var cust = from c in context.Contacts.Include("Customer") where c.ContactID == contactID select c; return cust.FirstOrDefault(); }
There is no code here for serialization. The act of serialization is an inherent function of the service. On the client side, again, no explicit deserialization is occurring. .NET knows the payload is serialized and will automatically deserialize it to a Customer object:
Private Sub GetCustFromService() Dim proxy = New BreakAwayCommonService.BreakAwayCommonServiceClient Dim cust = proxy.GetCustomer(21) Console.WriteLine("{0} {1}", cust.FirstName.Trim, cust.LastName) End Sub
In Chapter 14 , you will build a WCF client and service and see more regarding how this works.
9.8.1.3. Binary serialization In an ASP.NET website, you would use binary serialization to store information in the session cache or in the page's
ViewState . You can place objects directly into these caches, and extract them without having to explicitly serialize them since Object Services handles the serialization automatically.
9.8.1.4. Serialization and object state Since you are serializing only the objects and not the context, the state data stored in the ObjectStateEntry is not included. The EntityState of the objects in the serialized form isDetached; when you deserialize the objects they remain in a Detached state. If you attach them to anObjectContext, whether it's a newObjectContext or the same one to which they were previously attached, their state will become Unchanged . Your starting point with those objects becomes the values used when the data was originally retrieved from the database.
9.8.1.5. Explicit serialization You can also use methods in theSystem.Runtime.Serialization namespace to serialize your objects explicitly. The Entity Framework documentation has a great sample of serializing objects to a binary stream and then deserializing them again. This works no differently than serializing any other types of objects, and therefore is not specific to the Entity Framework. Look for the topic titled "How To: Serialize and Deserialize Objects" in the Entity Framework documentation for more information.
9.8.2. Object Services Supports Data Binding EntityCollection and ObjectQuery both implement IListSource , which enables them to bind to data-bound controls.
Because the objects implement INotifyPropertyChanges , you can use them in two-way binding, which means that updates made in the control can be sent back to the objects automatically. In Chapter 8 , you wrote a Windows Forms application that bound data to a BindingSource that in turn tied the data to various binding controls. You also performed data binding with WPF objects. In both applications, when updating the form's controls those changes were automatically made in the objects. This occurred thanks to the IListSource . ASP.NET data-bound and list controls also support data binding. Because of the nature of web pages, however, you'll need to pay attention to postbacks and their impact on change tracking. You can bind directly to the DataSource properties of the controls, or use a client-side EntityDataSource control. Although LINQDataSource does support read-only use of LINQ to Entities queries, it is more closely focused on LINQ to SQL and doesn't support everything in LINQ to Entities. Therefore, it's best to use EntityDataSource instead. In the next chapter, you will focus on using the ASP.NETEntityDataSource to build data-bound web pages. Some of the chapters appearing later in the book will demonstrate how to use business layers with Windows Forms and ASP.NET applications.
9.8.3. Object Services Supports Custom Classes You can use your own classes in Object Services in two ways. The simplest way is to inherit from EntityObject , which tightly binds your class to the Entity Framework. This is how the default code-generated classes are defined. In scenarios where the EntityObject constrains your classes and architecture too much, you can directly implement
IEntityChangeTracker, IEntityWithKey , and IEntityWithRelationships interfaces to take full advantage of Object Services.
NOTE Chapter 19 focuses on building custom classes in the Entity Framework. The use of custom classes with these interfaces is referred to as IPOCO, which adds the termInterface to the Plain Old CLR Objects (POCO) acronym. POCO describes classes that are not bound to .NET classes. The Entity Framework does not fully support POCO, but with interfaces, it is more loosely coupled than when the classes inherit from EntityObject.
9.8.3.1. IEntityWithChangeTracker When you implement IEntityWithChangeTracker , changes to your objects will be tracked, andSaveChanges will persist these changes to the store.
9.8.3.2. IEntityWithKey Implementing IEntityWithKey will ensure that theObjectContext manages objects efficiently. Without this implementation, there will be more overhead and more resources will be used to perform operations that rely on the EntityKey.
9.8.3.3. IEntityWithRelationships Entities that have navigation properties and therefore depend on associations must implement
IEntityWithRelationships so that Object Services is able to manage the relationships.
Note to Developers Using Domain-Driven Design, Unit Testing, and POCO Version 1 of the Entity Framework does not fully support POCO. Because of the need in this version to implement the aforementioned interfaces, it is not possible to be completely disassociated from the Entity Framework to perform unit testing or to have the level of separation that is required in domain-driven architecture. .NET 4.0 will target pure POCO implementations. The Entity Framework team is working closely with domain-driven experts such as Jimmy Nillson, Eric Evans, and Martin Fowler to achieve this.
Chapter 19 will further demonstrate techniques for implementing custom classes in the Entity Framework.
9.9. Summary In this chapter, you got an overview of the Object Services features, and now you should have the big picture of what Object Services is all about. As you can see, it is the core of the Entity Framework APIs. The only time you will not be using Object Services is when you work with the EntityClient directly to retrieve streamed, read-only data. Except for working withEntityClient , nearly everything you will learn in the rest of this book will be dependent on Object Services. As I noted throughout this chapter, many of the later chapters in this book will more thoroughly cover the individual topics highlighted here.
Chapter 10. Customizing Entities In previous chapters, we worked with entity classes that the Entity Framework's code generator created. The methods and events available to you for these classes were limited to the methods and events derived from their base classes: ObjectContext and EntityObject. Because the purpose of entities is to provide data schema, they contain little in the way of business logic. This is great for getting started, but many applications will need more. The extensibility of the Entity Framework provides a number of ways to not only add your own logic, but also use your own classes and plug them into an ObjectContext. In this chapter, you'll learn how to add new logic to entities or override their existing logic using partial classes, a feature introduced in .NET 2.0. In Chapter 19 , you will learn how you can tie your own custom classes into the Entity Framework.
10.1. Partial Classes All of the classes that are generated from an Entity Data Model (EDM)—the class that inherits from ObjectContext as well as the entities themselves—are partial classes. Partial classes allow you to break a class into multiple code files, and they are especially valuable when you want to make changes to generated code. Without partial classes, modifications to generated code will be lost whenever the generation is performed again. Rather than making your changes directly in the generated code, you can make them in a separate file that will not be touched when the code generator performs its magic. As long as your class is declared a partial class, another class with the same name will not cause a conflict. During compilation, .NET merges the separate files into one class.
NOTE For a great introduction to partial classes, the article "Implications and Repercussions of Partial Classes in the .NET Framework 2.0" (http://www.code-magazine.com/article.aspx?quickid=0503021/) by Dino Esposito is very informative. For example, a quick look at the code that is generated for the BreakAway application described in previous chapters reveals that the ObjectContext class and the application entities are marked as partial classes, as shown in Example 10-1.
Example 10-1. The ObjectContext and entities marked as partial classes
Partial Public Class BreakAwayEntities Inherits Global.System.Data.Objects.ObjectContext Partial Public Class Trip Inherits Global.System.Data.Objects.DataClasses.EntityObject
public partial class BreakAwayEntities : global::System.Data.Objects.ObjectContext public partial class Trip : global::System.Data.Objects.DataClasses.EntityObject
To add to any of these classes all you need to do is to create another file and declare another partial class, which you will see in the upcoming examples. There are a few rules for implementing partial classes: you don't need to repeat
inheritance or interface implementations; all of the partial classes for a particular class need to be in the same assembly; and you must not repeat any attributes. With regard to the last point, if you try to state the attributes more than once, you will get a compiler error letting you know that this is a problem.
Creating and Naming Files to Contain Partial Classes How you organize partial classes is a matter of coding style, and you or your development team may already have a practice that you use for partial classes. My pattern is to create a separate code file for each partial class that I implement. Therefore, I have an Entities.vb/.cs file for all of the additions to the class that implements theObjectContext (e.g.,
BAEntities), as well as individual files for each entity— Customer.vb/.cs, Trip.vb/.cs, and so on. You must always create these new files in the same assembly as the model that contains the generated classes.
Visual Basic infers the assembly namespace when creating additional parts of a partial class, whereas C# requires the namespace to be specified, as shown in the following code:
Partial Public Class BAEntities End Class
namespace BAGA //assembly namespace is required for C# partial classes { public partial class BAEntities { } }
10.2. Customizable Methods In addition to being able to split classes into multiple files, partial classes allow you to split methods across the files as well, using a technique called partial methods . The Entity Framework creates a few partial methods for its code-generated classes. These methods are declared but not implemented in the generated class. You can then add the method's implementation in your partial class. These generated partial methods include one that is called when an ObjectContext is instantiated, named OnContextCreated , and a pair of methods, Changed and Changing , for every property of every entity. In the following sections we'll look at each in more detail.
10.2.1. The OnContextCreated Method The first partial method, ObjectContext.OnContextCreated , lets you add custom code that will be executed at the time the context is instantiated. Here is how that is implemented in the generated code.
NOTE The partial methods have names that follow the naming convention of an event. But even though the names may make you think these are events, they are, in fact, methods. This is because of the nature of partial classes and the ability to split up a class's code across files. In the end, they do behave like events at runtime, but no inheritance is involved. At compile time, if the partial method is not implemented, it is not included in the compiled assembly, which is a nice form of optimization. The method is defined in the class that derives fromObjectContext (e.g., BAEntities ). As you can see in the following code, VB and C# differ in their syntax:
Partial Private Sub OnContextCreated() End Sub
partial void OnContextCreated();
OnContextCreated is called by the context object's constructor and the constructor overloads, as shown in the following code:
Public Sub New() MyBase.New("name=BAEntities", "BAEntities") Me.OnContextCreated End Sub Public Sub New(ByVal connectionString As String) MyBase.New(connectionString, "BAEntities") Me.OnContextCreated
End Sub Public Sub New(ByVal connection _ As Global.System.Data.EntityClient.EntityConnection) MyBase.New(connection, "BAEntities") Me.OnContextCreated End Sub
public BAEntities() : base("name=BAEntities", "BAEntities") { this.OnContextCreated(); } public BAEntities(string connectionString) : base(connectionString, "BAEntities") { this.OnContextCreated(); } public BAEntities(global::System.Data.EntityClient.EntityConnection connection) : base(connection, "BAEntities") { this.OnContextCreated(); }
By default, the OnContextCreated partial method contains no code because in the generated classes, the partial methods are only being declared. In the partial class that you write, you can add your own code to the method.
10.2.1.1. Implementing OnContextCreated To add code that you want to run when a context is instantiated, place the method calls inside the partial class for the ObjectContext. Visual Basic has properties, events, and methods available in drop-down boxes at the top of the code window. Select BAEntities in the Class Name drop-down on the left, and then select OnContextCreated from the Method Name drop-down on the right. This will automatically create the following VB code, which you could also just type in manually; in C#, you must type the method in manually:
Private Sub OnContextCreated() 'add logic here End Sub
partial void OnContextCreated() { //add logic here }
10.2.2. The On[Property]Changed and On[Property]Changing Methods Every scalar property of every entity has its own version ofPropertyChanging and PropertyChanged—for example, FirstNameChanged and FirstNameChanging. Like OnContextCreated, there is no default implementation forPropertyChanging and PropertyChanged ; only a declaration. The methods exist in case you want to take advantage of them. PropertyChanging lets you insert logic when a property is about to change, and PropertyChanged is hit after the property has changed.
NOTE ObjectContext also has a pair of Changed and Changing events. So, although you can insert logic based on a specific property changing with the partial methods, you can also have logic that runs, no matter which property is changed. We will discuss these events in Section 10.3. In the generated code, the methods are defined and then called in each property's setter. The following examples show what this looks like for the ActivityName property of the Activity entity in the generated code. First the two partial methods are declared (seeExample 10-2).
Example 10-2. The property Changing and Changed method declarations
Partial Private Sub OnActivityNameChanging(ByVal value As String) End Sub Partial Private Sub OnActivityNameChanged() End Sub
partial void OnActivityNameChanging(string value); partial void OnActivityNameChanged();
Then the ActivityName property calls those methods just before and after the value is changed (seeExample 10-3).
Example 10-3. The generated class calling the Changing and Changed methods
Public Property ActivityName() As String Get Return Me._ActivityName End Get Set Me.OnActivityNameChanging(value) Me.ReportPropertyChanging("ActivityName") Me._ActivityName = Global.System.Data.Objects.DataClasses. _ StructuralObject.SetValidValue(value, true) Me.ReportPropertyChanged("ActivityName") Me.OnActivityNameChanged End Set End Property
public string ActivityName { get { return this._ActivityName; } set { this.OnActivityNameChanging(value); this.ReportPropertyChanging("ActivityName"); this._ActivityName = global::System.Data.Objects.DataClasses. StructuralObject.SetValidValue(value, true); this.ReportPropertyChanged("ActivityName"); this.OnActivityNameChanged(); } }
10.2.2.1. Implementing the property-level PropertyChanged and PropertyChanging methods To implement the PropertyChanged and PropertyChanging methods, create a new code file to contain custom code for theActivity entity, and name the file Activity.vb or Activity.cs. In the file, add the code shown inExample 10-4.
Example 10-4. Defining a partial class for an entity
Partial Public Class Activity End Class
public partial class Activity { }
Visual Basic's event drop-downs make the next steps a little simpler than in C#. In VB, select Address from the Class Name drop-down; this will cause the Method Name drop-down to populate with all of the property-changing events. Choose OnAddressIDChanging and OnAddressIDChanged, which will stub out the event handler methods for you automatically. In C#, IntelliSense will help you as you type the methods into your code. The value parameter of the Changing method is the value that is about to be applied to the property, as shown inExample 10-5. In this method, we'll supplement theActivity to restrict the length of the ActivityName field in the OnActivityNameChanging method.
Example 10-5. The partial methods as implemented by VB and C#
Private Sub OnActivityNameChanging (ByVal value As String) If value.Length > 50 Then Throw New InvalidOperationException _ ("Activity Name must be no longer than 50 characters") End If End Sub Private Sub OnActivityNameChanged () End Sub
partial void OnActivityNameChanging (string value) { if ((value.Length) > 50) throw new InvalidOperationException
("Activity Name must be no longer than 50 characters"); } partial void OnActivityNameChanged() {}
If you look at the signatures of the Changed and Changing methods for the individual properties, you'll see that theChanged method has no parameters at all and the Changing method receives the new value. Because you are coding within the entity's class, you have access to the entity, its properties and methods, and its related data. This means you can impact whatever you wish about the Address entity in this business logic.
10.2.3. Using PropertyChanged to Calculate Database-Computed Columns Locally Here's an example of taking advantage of these methods. Many databases use computed columns to perform calculations on the fly. An example of this is in Microsoft's sample database, AdventureWorksLT. The LineTotal column of the SalesOrderDetail table is a computed column. Figure 10-1 shows the column properties in the database. You can see that the Computed Column Specification property formula calculates the LineTotal based on the UnitPrice, UnitPriceDiscount, and OrderQty columns.
Figure 10-1. The LineTotal column, a computed column in the AdventureWorksLT SalesOrderDetail table
You would likely want to know that value in your application as the order is being created or modified, without depending on a trip to the database to get the LineTotal . Instead, you can create a method or read-only property in the partial class tocompute the LineTotal locally, and then call that method anytime the UnitPrice, UnitPriceDiscount, or OrderQty column is changed. Because LineTotal is a computed column in the database, the value created on the client side will not be sent to the server upon calling SaveChanges . Thanks to the default dynamic command generation capability, thatLineTotal value will be replaced by the value computed by the database when you call SaveChanges.
NOTE Computed columns are marked as StoreGeneratedValue in the model, just as an identity column is. Therefore,SaveChanges will construct the command to send the updates and return any properties that are StoreGeneratedValues. The custom method or property gives you the ability to calculate that property locally as needed. Although this computed property works very well for formulas in which the required values are contained within the same entity, you have to be careful if you are calculating data from related entities. The SalesOrderHeader entity in AdventureWorksLT has aSubTotal
property that could be populated by summing up the LineTotal properties of all related SalesOrderDetails . But this assumes that all of the related details have been retrieved, and it may require a quick trip to the database to ensure that this is so. Depending on your application's architecture this could be a bad assumption to make, so this is something to consider before depending on this type of calculation on the client side.
Extensibility Points Although EntityObject and ObjectContext expose some of their events as partial classes, which let you jump in and add your own logic, it would be nice to insert custom logic in a lot of other places as well. Many developers have been letting Microsoft know the points along the various "pipelines"—such as when queries are being generated, or objects are being materialized or even inserted into a context—they would like to be able to tap into to further customize their solutions. Look for many more methods and events to be available in the next version of the Entity Framework.
10.3. Customizable Event Handlers You can override only a few event handlers in your applications:
ObjectContext.SavingChanges EntityObject.PropertyChanging EntityObject.PropertyChanged RelatedEnd.AssociationChanged
10.3.1. The ObjectContext.SavingChanges Event As I mentioned in the preceding chapter,SavingChanges is your only opportunity to validate or affect data before it is persisted to the database. SavingChanges executes just prior to when theSaveChanges method builds the database Insert, Update, and Delete commands. Because this represents a single event for all of the entities in your model, it is possible that you will have quite a lot of code in here, if you need to make some last-minute changes to any or all of the types in your system. Therefore, you'll want to consider how you organize these validations. You could perform them per entity type, or you could perform them perEntityState. You could also build the validators into partial classes for the various entities, and call them from
ObjectContext.SavingChanges .
GetObjectStateEntries: A Critical Method When Coding the SavingChanges Method There's a method that you haven't seen yet that is frequently used when customizing the
SavingChanges event. GetObjectStateEntries is a method ofObjectContext.ObjectStateManager that allows you to work with groups of entities. You'll be spending more time with the ObjectStateManager in Chapter 17. GetObjectStateEntries is the only way to access the entities in the SavingChanges event. This method returns ObjectStateEntries by filtering on a particular
EntityState. Once you have the ObjectStateEntries , you can navigate to the actual entity objects, as you will see in the code at the end of this sidebar. You can pass in one or more EntityState enumerators (separated by VB'sOr or C#'s | ) to determine which group or groups of entities you want to work with. For instance, GetObjectStateEntries(EntityState.Added) returns all of the new entities in the context;
osm.GetObjectStateEntries(EntityState.Added Or EntityState.Modified) returns all of the new entities as well as any that have been modified. The only downside to this is that if you want to explore the entries in any way, not just by EntityState,
GetObjectStateEntries still requires that you use the enums. For example, if you wanted to find all of the Trip entries in the ObjectStateManager , regardless of their state, you would need to pass in all of the possible EntityState options— Added, Deleted, Modified, and Unchanged . Then you can filter on the type of the entity referenced by these ObjectStateEntries , as shown in the following code:
Dim TripEntries = From entry In osm.GetObjectStateEntries _ (EntityState.Added Or EntityState.Deleted _ Or EntityState.Modified Or EntityState.Unchanged) _ Where TypeOf entry.Entity Is Trip
var TripEntities = from entry in osm.GetObjectStateEntries (EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged) where entry.Entity is Trip select entry;
NOTE There is, of course, one more EntityState enum that the preceding examples have ignored:Detached. Detached entities don't exist in the ObjectContext, so there's no reason to look for them here.
10.3.1.1. Implementing SavingChanges Before you can override SavingChanges, you'll need to extend the partial class for theObjectContext. You can do this in the entities code file. Example 10-6 demonstrates overriding theSavingChanges event in the BreakAway model. The handler updates the
ModifiedDate property for every contact that is either modified or added. It also sets a default CustomerTypeReference when the contact is a customer. The example first grabs all of the Modified and Added entries without worrying whether they are for entities or relationships. Then, it identifies contacts and updates the AddDate and ModifiedDate fields. Next, it checks for customers and updates their CustomerTypeReference (a necessity pointed out in the preceding chapter) if it hasn't been assigned yet.
Example 10-6. Setting default values in SavingChanges in VB
Private Sub BAEntities_SavingChanges _ (ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.SavingChanges Dim osm = Me.ObjectStateManager 'get Added or Modified entries For Each entry In osm.GetObjectStateEntries _ (EntityState.Added Or EntityState.Modified) If TypeOf entry.Entity Is Contact Then Dim con = CType(entry.Entity, Contact) With con 'only update AddDate if it has not been set yet If .AddDate = DateTime.MinValue Then .AddDate = Now .ModifiedDate = Now End With End If If TypeOf entry.Entity Is Customer Then Dim cust = CType(entry.Entity, Customer) With cust If cust.CustomerTypeReference.EntityKey Is Nothing Then cust.CustomerTypeReference.EntityKey = _ New EntityKey _ ("BAEntities.CustomerTypes", "CustomerTypeID", 1) End If End With End If Next End Sub
Enums Not Supported in Entity Framework You'll notice in Example 10-6 that an integer is assigned to theEntityKey value. This is a case where it might make more sense to predefine a set of enums that would be identified as CustType.Standard,
CustType.Silver , and CustType.Gold . In this way, there would be no need for the developer to remember the actual value, 1, for the Standard customer. Unfortunately, if you were to use an enum when building the EntityKey, for example:
New EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", CustType.Standard)
an exception would be thrown indicating that the constructor requires an integer. That is because the Entity Framework does not support enums and unfortunately at this point there are no plans to include this support in .NET 4.0.
In C#, you have to perform a few extra steps to wire up theSavingChanges event handler. You can do this by overriding the OnContextCreated partial method, as shown inExample 10-7.
Example 10-7. Setting default values in SavingChanges in C#
partial void OnContextCreated() { this.SavingChanges += new System.EventHandler(mySavingChanges); } public void mySavingChanges(object sender, System.EventArgs e) { var osm = this.ObjectStateManager; //get Added | Modified entries; foreach (var entry in osm.GetObjectStateEntries (EntityState.Added | EntityState.Modified)) { if (entry.Entity is Contact) { var con = (Contact)entry.Entity; //only update AddDate if it has not been set yet if (con.AddDate == DateTime.MinValue) con.AddDate = DateTime.Now; con.ModifiedDate = DateTime.Now; } if (entry.Entity is Customer) { var cust = (Customer)entry.Entity; if (cust.CustomerTypeReference.EntityKey == null) cust.CustomerTypeReference.EntityKey = new EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1); } } }
One of the validations performed in Examples Example 10-6 and Example 10-7 involved setting a default value for a Customer's CustomerType. In the sample database, theCustomer's CustomerTypeID foreign key is a non-nullable field. In the Entity Framework, although you can set defaults for scalar values, it's a little more difficult to enforce constraints on EntityReference s or to have them automatically implemented. Taking care of this constraint during
SavingChanges by providing a default so that the value is not null is a convenient way to solve the problem. Otherwise, if the CustomerTypeID had been left empty, an exception would be thrown.
10.3.2. The EntityObject.PropertyChanging and EntityObject.PropertyChanged Events In addition to the Changing and Changed methods for the individual properties of a class,EntityObject has class-level
PropertyChanged and PropertyChanging events as well. These two events fire anytime any property in the Address class changes.
10.3.2.1. The order of the Changing/Changed events If you implement the class-level events as well as any of the specific property methods, both the method and the event will be hit when the particular property is modified. Here is the order in which the events are hit: 1. Property-level On[Property]Changing method 2. Class-level PropertyChanging event 3. Class-level PropertyChanged event 4. Property-level On[Property]Changed method
PropertyChanged and PropertyChanging Events and Methods Fire During Object Materialization You may build the event handlers and methods we've discussed in this chapter with the idea of using them whenever your custom code impacts the data in your entities. But be aware that all of these methods and events will also be hit when an entity is being populated from a query. So, if you are querying for addresses, these events will be raised over and over again as each address is materialized. This may or may not be desirable. Unfortunately, there is no simple way to discern whether you are in the process of materializing objects. You could set up global Boolean variables to indicate that a query is being processed and then check their values before executing the code in the methods. Or you can find another workaround by searching the MSDN forums for a thread titled "PropertyChanged during ObjectMaterialization" (which I started, as a matter of fact). As a response to my question, Matthieu Mezil proposed a solution that reads the stack trace. So, although there is always a way to solve these types of problems, the key is to be aware of what your code is doing and what impact it may have on your application or resources.
10.3.2.2. Event parameters The Sender parameter of thePropertyChanged and PropertyChanging events contains the entity in its current state. You'll have to cast Sender back to the actual type to access these values. TheEventArgs for both events have a
PropertyChanged property that is a string that defines the name of the changing/changed property. Unlike the property-level Changing method (e.g., AddressPropertyChanging), the PropertyChanging event does not provide the new value.
10.3.2.3. Implementing the class-level PropertyChanging and PropertyChanged events Once again, the place to implement thePropertyChanging and PropertyChanged events is in an entity's partial class. In VB, these events are available in the IDE drop-downs. Using theAddress class as an example again, in the Address partial class, select Address Events from the Class Name drop-down and then select OnPropertyChanged and OnPropertyChanging from the Method Name drop-down. The event handlers shown in Example 10-8 will automatically be created.
Example 10-8. Implementing PropertyChanged and PropertyChanging in VB
Private Sub Address_PropertyChanged(ByVal sender As Object, _ ByVal e As System.ComponentModel.PropertyChangedEventArgs) _ Handles Me.PropertyChanged Dim propBeingChanged As String = e.PropertyName 'add your logic here End Sub Private Sub Address_PropertyChanging(ByVal sender As Object, _ ByVal e As System.ComponentModel.PropertyChangingEventArgs) _ Handles Me.PropertyChanging Dim propBeingChanged As String = e.PropertyName 'add your logic here End Sub
In C#, you'll need to manually wire up the event handlers as you did for SavingChanges . You can do this by adding a constructor to the Partial class, as shown in Example 10-9. The PropertyChanged and Changing events derive from a different .NET namespace: System.ComponentModel, rather than the System.EventHandler that you used for
SavingChanges.
Example 10-9. Implementing PropertyChanged and PropertyChanging in C#
public partial class Address { //wire up the events inside the Address class constructor public Address() { this.PropertyChanged += new PropertyChangedEventHandler(Address_PropertyChanged); this.PropertyChanging += new PropertyChangingEventHandler(Address_PropertyChanging); } //create the methods that will be used to handle the events private void Address_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { string propBeingChanged = e.PropertyName; //add your logic here } private void Address_PropertyChanging(object sender, System.ComponentModel.PropertyChangingEventArgs e) { string propBeingChanged = e.PropertyName; //add your logic here } }
In C#, if IntelliSense does not help you to build these partial classes, methods, and event handlers you may have forgotten to wrap the class in the namespace of the assembly. Be sure to use the same name as the namespace of the generated code class.
10.3.3. The AssociationChanged Event Another critical data event exposed through Object Services isAssociationChanged .
NOTE There is no AssociationChanging event handler. You can create an AssociationChanged event for any navigation property of an entity. There is no way to have an overall event to capture all association changes in an ObjectContext. You'll need to wire this up manually in both VB and C#.Example 10-10 demonstrates creating an AssociationChanged event handler for the ContactReference property of the Address. In the partial class for theAddress, create a method (here it's called ContactRef_AssociationChanged ) to execute the desired logic; then in the class constructor, add code to wire up the event handler to this method. The implementation is the same for EntityReferences as it is forEntityCollection.
10.3.3.1. Event arguments Both the EntityReference and EntityCollection implementations have CollectionChangeEventArgs in their parameters. This argument contains two properties: Action and Element. The Action property can be one of theCollectionChangeAction enums: Add , Refresh , or Remove. The Element property returns an object that is the entity on the other end of the relationship being changed. You can cast it back to the appropriate entity type if you want to work with that entity. Example 10-10 shows an AssociationChanged event handler for theCustomerReference of the Address, followed by the opposite—an AssociationChanged handler for the Addresses property of the Customer.
Example 10-10. Implementing the AssociationChanged event
Imports System.ComponentModel; Partial Public Class Address 'wire up the event handler in the class constructor Public Sub New() AddHandler Me.ContactReference.AssociationChanged, _ AddressOf ContactRef_AssociationChanged End Sub the method that will be executed when the event is fired Private Sub ContactRef_AssociationChanged _ (ByVal sender As Object, _ ByVal e As ComponentModel.CollectionChangeEventArgs) Dim act As System.ComponentModel.CollectionChangeAction = e.Action Dim custOnOtherEnd = CType(e.Element, Customer) 'add your logic here
End Sub End Class
Partial Public Class Customer Public Sub New() AddHandler Me.Addresses.AssociationChanged, _ AddressOf Addresses_AssociationChanged End Sub Private Sub Addresses_AssociationChanged(ByVal sender As Object, _ ByVal e As System.ComponentModel.CollectionChangeEventArgs) Dim act As CollectionChangeAction = e.Action Dim addressOnOtherEnd = CType(e.Element, Address) 'add your logic here End Sub End Class
using System.ComponentModel; namespace BAEntities { public partial class Address { public Address() { this.CustomerReference.AssociationChanged += new CollectionChangeEventArgs(Add_CustRefChanged); } private void Add_CustRefChanged (object sender,CollectionChangeEventArgs e) { CollectionChangeAction act = e.Action; Customer custOnOtherEnd = (Customer)e.Element; //add your logic here } public partial class Customer {
public Customer() { this.Addresses.AssociationChanged += new CollectionChangeEventHandler(Addresses_AssociationChanged); } Private void Addresses_AssociationChanged (object sender, CollectionChangeEventArgs e) { CollectionChangeAction act = e.Action; Address addOnOtherEnd = (Address)e.Element; //add your logic here } } }
10.4. Other Opportunities for Customization With partial classes, you can do more than override existing methods and events. You can also create your own class methods or properties.
10.4.1. Custom Properties Partial classes are useful as a way to work around the limitations of the EDM. An example of such a limitation is the model's inability to create properties as a result of an expression.
NOTE This capability is on the list for the next version of the Entity Framework, which will be part of .NET 4.0/Visual Studio 2010. The feature is currently called DefiningExpressions. For example, it would be very nice to be able to create a FullName property that is the result of concatenating FirstName and LastName, but the schema of the Entity Framework's EDM doesn't support this.
NOTE Samples appearing later in the book will also use the FullName and TripDetails custom properties implemented in this section. If you are following along with the book's walkthroughs, be sure to add these properties to the BreakAway model's project. Instead, you can create a FullName property directly in the partial class. Keep in mind that these properties, even if they are not marked as read-only, will not be included in updates or inserts when you call SaveChanges. A ReadOnly FullName property would be appropriate for display purposes. You could then write your code to modify the FirstName and LastName properties, and those would be sent to the database when SaveChanges is called. Also handy would be a second property that you could use when you need to list full names alphabetically. The properties shown in Example 10-11 are part of the class.
Contact partial
Example 10-11. Adding a new property to the Contact partial class
Public ReadOnly Property FullName() As String Get Return Me.FirstName.Trim & " " & Me.LastName End Get End Property Public ReadOnly Property FullNameAlpha() As String Get Return Me.LastName.Trim & ", " & Me.FirstName End Get End Property
public string FullName { get { return this.FirstName.Trim() + " " + this.LastName; } } public string FullNameAlpha { get { return this.LastName.Trim() + ", " + this.FirstName; } }
NOTE You cannot use these custom properties in LINQ to Entities or Entity SQL queries, but you can use them in client-side queries, which are just LINQ to Objects queries. Some properties that you will be able to use in a number of samples as the book progresses are those that display the details of a Reservation. The Reservation entity does not contain much interesting information in its scalar properties. The useful details are in the Trip navigation property (start and end dates, trip cost) and in the Destination
property of the Trip (the destination name).
Rather than reconstruct this information over and over again, you can create a property of Reservation that will give this to you already concatenated. The same information is convenient to concatenate from the Trip entity. After adding a TripDetails property to the Trip entity, you can then add a TripDetails property to Reservation that reads the Trip.TripDetails. The Trip.TripDetails property in Example 10-12 protects itself from an exception if the destination information hasn't been loaded.
Example 10-12. A custom property to provide a commonly needed concatenation in the BreakAway application
Partial Public Class Trip Public ReadOnly Property TripDetails() As String Get Try Return Destination.DestinationName.Trim _ & " (" & StartDate.ToShortDateString & "-" _ & EndDate.ToShortDateString _ & "; " & String.Format("{0:C}", TripCostUSD.Value) & ")" Catch ex As Exception Return Destination.DestinationName End Try End Get End Property End Class
Partial Public Class Reservation Public ReadOnly Property TripDetails() As String Get Return Trip.TripDetails End Get End Property End Class
using System; namespace BAGA { public partial class Trip { public string TripDetails { get { try { return Destination.DestinationName.Trim() + " (" + StartDate.ToShortDateString() + "-" + EndDate.ToShortDateString() + "; " + string.Format("{0:C}", TripCostUSD.Value) + ")"; } catch (Exception ex)
{ return Destination.DestinationName; } } } } } public partial class Reservation { public string TripDetails { get { return Trip.TripDetails; } } } }
10.4.2. Custom Properties That Perform Calculations on Child Collections In the BreakAway model, you could create custom read-only properties in the Reservation entity for TotalPaid and PaidinFull that would be calculated based on the sum of the payments for that reservation. As mentioned earlier in the discussion of computed columns, the data would be valid only if you could ensure that all of the payments are accounted for. If there is a chance that some of the payments have not been retrieved from the database, you shouldn't use this.
10.4.3. Overloading Context and Entity Methods .NET's method extensions provide another way to add logic to your entity classes. As an example, let's overload an entity's factory method. These are the Shared/Static methods for each entity that let you quickly create a new entity. The parameter list for these factory methods consists of all of the non-nullable properties in the class. The entire set of non-nullable properties isn't always the most desirable list of fields to populate when creating a new class. For example, in the BreakAway model classes, the Contact.CreateContact factory method has the signature shown in Example 10-13.
Example 10-13. Signature of the Contact.CreateContact factory method
Public Shared Function CreateContact _ (ByVal contactID As Integer, ByVal firstName As String, _ ByVal lastName As String, ByVal addDate As Date, _ ByVal modifiedDate As Date) As Contact
public static Contact CreateContact (int contactID, string firstName, string lastName, global::System.DateTime addDate, global::System.DateTime modifiedDate)
In most cases, the ContactID will be 0, and if your SavingChanges event handler takes care of the AddDate and ModifiedDate fields, why be forced to enter them when you create a new Contact ? You may also have some of the other information available, which means that after calling CreateContact , you still have to set more properties. Creating an overload of the method would be very convenient. You can place the new version of the method in theContact 's partial class. Example 10-14 shows a more useful CreateContact method.
Example 10-14. Overriding the Create factory method
Public Shared Function CreateContact(ByVal firstName As String, _ ByVal lastName As String) As Contact Dim contact As Contact = New Contact contact.FirstName = firstName contact.LastName = lastName Return contact End Function
public static Contact CreateContact(string firstName, string lastName) { Contact contact = new Contact(); contact.FirstName = firstName; contact.LastName = lastName; return contact; }
When you call the CreateContact method, two signatures will be available, as shown in Figure 10-2.
Figure 10-2. The new CreateContact overload as shown by IntelliSense
As you are using the different methods that the entities inherit from EntityObject or ObjectContext , keep your mind open to the idea of being able to enhance them to suit your purposes.
10.4.4. Partial Classes Are for More Than Just Overriding Existing Methods and Events You can, of course, create all kinds of new logic in partial classes, whether the logic pertains to properties or to methods. For example, perhaps you want to perform some validation logic without saving changes, such as supplying default values for entities. You could place methods for this within an entity's partial class and then call that class as needed. If you like the idea of having validation performed on a number of entities at once, or even on a variety of entity types (as SaveChanges can do), you could place the method in the ObjectContext 's partial class. Keep in mind that only attached entities will be available. Other than creating your own classes, the partial classes are the primary mechanism in the Entity Framework for adding business logic to entities.
10.4.5. Custom Code Generation It is possible to override EDM code generation completely so that you can define what the generated classes look like. As you'll learn in Chapter 19 , you will need to follow some rules. The code generator is based on the System.Data.Entity.Design API, and you can use it directly. A sample code generator is available on the Entity Framework team's Code Gallery page at http://code.msdn.com/adonetefx/ to get you started.
10.4.6. Creating Common Methods or Properties for All Entities If you have a method or property that you would like to be able to perform on any entity, rather than adding that method to each entity's partial class, you can create a common method. Here are a few ways in which you can do that: Place the method into the ObjectContext 's partial class and pass the entity in as a parameter. This will work only for entities that are attached to the ObjectContext .
Create an extension method for EntityObject . This will then be an available method for any class that inherits from EntityObject . Build a custom code generator for the model, inserting the logic into each generated class. Better yet, insert a call to this logic so that you can modify the logic without having to rebuild the model.
10.5. Summary In this chapter, you learned how to use partial classes generated by the Entity Framework to insert your own business logic into entity classes and to the class that serves as your context. You can override events, add code to partial methods, and even add completely new methods and properties to the generated classes. Although there are a lot of opportunities for customizing entities and theObjectContext , sometimes you will find that these are not enough. The EntityObject s are designed to encapsulate schema and relationships, not behavior. The lack of an opportunity to tap into AssociationChanging when you do have access toAssociationChanged is an obvious example. In many places it would make a big difference in our applications if we could insert more logic, but we'll have to wait for version 2 of the Entity Framework for this. If you still want more out of these classes, you should consider using custom classes instead, which we will cover in Chapter 19. Additionally, in a more traditional architecture, you might want to use the Entity Framework only in a data access layer of your application and then have a way to move data from the entities to your objects and back for updates.
Chapter 11. Using the ASP.NET EntityDataSource Control With the Entity Framework you can build both Rapid Application Development (RAD) applications and highly architected applications. On the RAD end, the ASP.NET EntityDataSource control enables quick declarative data binding that you can use in a number of scenarios. In this chapter, you will build two ASP.NET web applications using entities that make use of the EntityDataSource control. The first application will introduce you to the EntityDataSource , and the second will add some more complexity, featuring hierarchical data as well as greater interaction between the controls. After you build the examples, the chapter will bring you on a tour of some of the more interesting features of the EntityDataSource control. The EntityDataSource control allows you to define a data source for binding your data to controls in the UI, using markup syntax. It is related to the other ASP.NET DataSource controls, such as SqlDataSource and
LinqDataSource. Using entities in web applications can be challenging because theObjectContext does not survive postbacks and therefore cannot do its job of keeping track of changes to your entities. The EntityDataSource control helps you to resolve many of the challenges in scenarios where you do not need to use a business or data access layer. Later in the book, after you have learned about the Entity Framework in more detail, you will learn about building layered ASP.NET applications. This chapter will give you an opportunity to work with theEntityDataSource control as well as use direct data binding to query results. You'll also perform some editing and updating along the way. You'll begin by jumping right in and using anEntityDataSource in a simple scenario so that you can get a feel for the control's general purpose and functionality. Then you will learn how the control works and what its important properties and events are. You'll add some more features to the first example, and finally, you will use this knowledge to build a master/detail form that employs a number of coordinated EntityDataSource controls so that you can see how to use the controls in more typical applications as well as learn a few more tricks along the way.
11.1. Getting to First Base with the EntityDataSource Control and Flat Data Although you can bind query results directly to any data-binding or list control in a web application, updating entities is challenging due to the life cycle of an ASP.NET Page class. As you expand your knowledge of the Entity Framework, you will be better prepared to address these challenges, and you'll leverage this understanding to build another web application, in Chapter 21 . In that chapter, we will also look closely at the life cycle of the ASP.NETPage class so that you understand why it presents such difficulties for change tracking. For now, it helps to know that there's an easy way to use entities in web applications when you are looking for a quick solution. The Entity Framework adds a new control to the set of existing (and possibly familiar) ASP.NET DataSource controls (SqlDataSource, LinqDataSource , etc.), which simplifies data binding for read/write functionality. You can configure the EntityDataSource control in the UI and it will handle all of the grunt work for inserting, updating, and deleting entities on your behalf. Once you've defined an EntityDataSource control, you can bind it to any web control that supports data binding. Let's start with a small and simple Hello Entities application so that you can get used to how the control works. The following pages will walk you through the steps for displaying and editing contacts from the BreakAway model. This will be flat data—no related data will be involved.
11.1.1. Creating the Hello Entities Project You'll begin by creating a new ASP.NET Web Application project for this example: 1. In the same solution you have been working with in previous chapters, create a new ASP.NET Web Application project. 2. Add a reference to the BreakAwayModel project. 3. Copy the ConnectionStrings section from the BreakAwayModel project's app.config file into the web application's web.config file, replacing the existing ConnectionStrings section.
4. Save and build the application. This is an important step that allows the Entity Data Source Wizard that you'll be using to find the connection string information in the web.config file. 5. Open the Default.aspx page to begin adding controls.
11.1.2. Creating a GridView and an EntityDataSource Concurrently Although you can create the EntityDataSource control first and then create the binding control and link them up, ASP.NET also lets you create an EntityDataSource control as part of the wizard of the binding control that will consume the data, in this case a GridView . We'll use this latter method: 1. Drag a GridView from the Data section of the Toolbox onto the web page. 2. From the GridView Tasks window, choose from the Choose Data Source drop-down list (see Figure 11-1).
Figure 11-1. The GridView Tasks window
1. In the Data Source Configuration Wizard that appears, select Entity from the Choose a Data Source Type page, as shown in Figure 11-2, and click OK.
Figure 11-2. Defining the data source to be an EntityDataSource
11.1.3. Configuring an EntityDataSource Through Its Wizard The EntityDataSource will need to know where its entity comes from. The wizard will walk you through the critical properties that need to be configured.
NOTE You can configure many more properties of the EntityDataSource through its Properties window or directly in its markup. You will work with these additional properties further on in the chapter. The first page of the Data Source Configuration Wizard will allow you to select a named connection from the connections that the wizard finds in the web.config file: 1. Select BAEntities in the Named Connection drop-down. The wizard finds the container name by looking in the Conceptual Schema Definition Layer (CSDL) file listed in the connection string you selected in the first drop-down. This should automatically be populated with BAEntities . 2. Click Next. 3. On the Configure Data Selection page, choose Contacts from the EntitySetName drop-down. Again, the wizard has inspected the model to discover the available EntitySets. 4. Leave Select All checked. By default, all of the properties will be used. If you choose specific properties, you won't get an entity object back. Instead, you will get a DbDataRecord , which cannot be change-tracked and therefore cannot be updated. If you check any of the properties, you will notice that the checkboxes for enabling automatic inserts, updates, and deletes will be disabled. 5. Check the three boxes for enabling inserts, updates, and deletes so that you will be able to conduct a test edit with the EntityDataSource control. When you're finished, the page should look like Figure 11-3. 6. Click Finish.
Figure 11-3. Configuring the EntityDataSource to use the Customer entity type, and selecting all properties so that you can perform inserts, updates, and deletes
Figure 11-4 shows the grid and EntityDataSource control as they are displayed on the page after you finish configuring the EntityDataSource. The EntityDataSource control will not be displayed on the page at runtime.
Figure 11-4. The design-time GridView after it has been hooked up to the EntityDataSource
11.1.4. Formatting the GridView Even though you configured the EntityDataSource control to support inserts, updates, and deletes, you'll need to specifically enable the GridView to allow the same functionality. The EntityDataSource control also supports dynamic sorting and paging, but again, you need to enable the features in the grid so that you can take advantage of them: 1. Select the Grid view's Smart Tag to open its Tasks window again. 2. Check the Enable Paging, Enable Sorting, Enable Editing, and Enable Deleting checkboxes.
NOTE The ASP.NET GridView control does not support insertion even though the EntityDataSource control does. There are ways around this, but a solution is not something to get into in a Hello Entities demonstration. We'll discuss inserting in the next example. The grid should look similar to Figure 11-5.
Figure 11-5. The GridView with editing, deleting, sorting, and paging enabled
Dynamic Paging and Entity Framework Queries ASP.NET data sources support server-side paging. Although the GridView controls the actual paging settings, the DataSource needs to have its set to True to support the feature (AutoPage is True by default).
AutoPage property
With paging, rather than downloading and viewing all of the rows at once, the DataSource can query for only a certain number of rows at a time to be displayed in the GridView , and then can make another call to the database to get another subset of data as needed. The default number of rows it can query is 10. Depending on your application scenario, more calls to the database with less data to manage may be preferable to fewer calls to the database and more data to manage. See the "GridView Web Server Control Overview" topic in the MSDN documentation for more information on working with GridView s and paging.
11.1.5. Testing the Web Application Now you get to see the ObjectDataSource in action. Run the application and test the paging, sorting, and editing features. Notice that when you edit a row, the grid automatically knows not to allow the user to edit the EntityKey property, ContactID , as shown in Figure 11-6.
Figure 11-6. Editing a contact at runtime
NOTE If you attempt to delete a contact that has related data (addresses or a customer record), you'll get a Reference Constraint error. You do not have to worry about this in the Hello Entities application.
11.2. Understanding How the EntityDataSource Is Able to Retrieve and Update Your Data As you saw in this example, the grid was populated by the EntityDataSource without the need for you to write any code to define and execute a query. And it seemed to magically handle the update for you. How did the data get to the form? How did the changes get back to the database?
11.2.1. EntityDataSource and Its Query At runtime, when the EntityDataSource needs to populate itself, it begins by reading theEntityConnectionString,
EntityContainer, and EntitySet properties you defined. It then creates a newObjectContext using the EntityConnectionString name, and an ObjectQuery using the EntitySet . If you had chosen individual entity properties, such as FirstName and LastName, it would build an Entity SQL string using theEntityContainer name and the names of the selected properties. The query is built dynamically based on the properties of the EntityDataSource .
11.2.1.1. Other EntityDataSource properties that impact the query The wizard that you walked through configured only the most elemental properties of the EntityDataSource , but the control has many more properties, and some of those allow you to further define the query. A subset of these additional EntityDataSource properties mimic query builder methods: Where, GroupBy, Select, and OrderBy . At runtime, the same query pipeline that creates a query from the query builder methods creates a query based on these EntityDataSource properties. In fact, theEntityDataSource 's internal method uses the Entity SQL query builder methods to build its queries. The EntityDataSource control also has an Include property that emulates theObjectQuery.Include method.
EntityDataSource provides an EntityTypeFilter property that internally leverages Entity SQL'sOFTYPE ONLY operator to work with inherited entity types. You will learn more about inheritance in the Entity Data Model in the next chapter. By assigning values to the EntityDataSource properties, you can achieve the same results as though you had built a query using query builder methods. For example, an EntityDataSource with the property settings shown inTable 11-1 is equivalent to the ObjectQuery created by the following query builder methods:
context.Contacts.Include("Addresses") .Where("it.FirstName='Robert'").OrderBy("it.LastName")
Table 11-1. EntityDataSource property settings to create the query Property
Value
EntitySet
Contacts
Include
Addresses
Where
it.FirstName='Robert'
OrderBy
it.LastName
You'll learn more about these various properties as you read through this chapter.
11.2.2. EntityDataSource and Its ObjectContext In the previous example, you had a singleEntityDataSource on the page to manageContact entities. As you'll see
later in the chapter, you can have multiple EntityDataSource controls on a page. By default, eachEntityDataSource on a page creates its own ObjectContext . If you have more than oneEntityDataSource , you will get multiple
ObjectContext objects and multiple connections to the database. Every time a page posts back, the contexts that were created are dropped. When the page is re-created, the
EntityDataSource creates a new ObjectContext for itself. Because the previous context is no longer being used, .NET's garbage collector will eventually remove it. Like any ObjectContext object, the EntityDataSource 's context does not hold on to connections to the database once it has executed its command and retrieved the requested data, so this is not something to worry about, yet it's good to be aware of if you are focused on resource usage.
11.2.2.1. Using your own context You can override the creation of individual contexts and thereby have more control over the entities. One way to do this is to instantiate your own context in the form, and force the EntityDataSource to use that. The
EntityDataSource has a ContextCreating event, which fires just as theDataSource is about to create its own context. The signature of the event has a parameter that passes in the EntityDataSourceEventArgs , as shown in the following code:
EntityDataSource.ContextCreating(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls. _ EntityDataSourceContextCreatingEventArgs)
EntityDataSource.ContextCreating(object sender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs e)
The EventArgs has a context property that represents the context for the data source. When you set that context to your own context, your context becomes responsible for the DataSource and the entities it returns. The following code sample assumes that the context, EDS , has been instantiated elsewhere in the form and is declared as a class-level variable:
Private Sub EDS_ContextCreating(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls. _ EntityDataSourceContextCreatingEventArgs) Handles MyEDS.ContextCreating
e.Context = myContext End Sub
protected void EDS_ContextCreating(object sender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs e) { e.Context=myContext; }
Why use a single context for your EntityDataSource controls? Creating your own context has many benefits. When you have multiple EntityDataSource controls with objects that are related to one another, those objects are never connected, so you can't build a graph or update a graph. By creating a single context to manage multiple EntityDataSource controls, you would be able to work with graphs comprised from the entities in the various
EntityDataSource s. Relationships among entities will not be recognized unless the sameObjectContext is managing the entities. Otherwise, even if you have customers and reservations that do belong to one another, if separate contexts are managing them you will not be able to traverse from customers to reservations or from reservations to customers. In other words, Customer.Reservations would result in zero reservations andReservation.Customer would return null or nothing. Although you gain in resource usage by sharing a context, the gain won't necessarily be large. The ability to control which ObjectContext is managing your entities, however, is very powerful.
11.2.3. EntityDataSource Context Events A number of events are related to the context for the EntityDataSource . Each event offers an opportunity to have more control over the default context the data source uses. Here are some of the more interesting details exposed during these events:
ContextCreating
e.Context provides a hook to the context before it even exists. As you saw earlier, this is where you can tell the data source to use another context instead of creating its own. There is no Cancel argument in this event. There would be no point to canceling the creation of a context for the data source as it would not function at all.
ContextCreated The EventArgs of this event also returns theEntityDataSource 's context, whether this is the default context or one that you substituted in the ContextCreating event. You have an opportunity to work directly with this context in the page's code-behind.
ContextDisposing
e.Cancel allows you to stop the context from being disposed. You may need to use this if you are managing the context and know it will need to do more work before page creation is complete.
e.Context returns the context.
11.2.4. EntityDataSource and ViewState Although there is an ObjectContext for creating and executing queries and for saving changes to the database, an instantiated ObjectContext does not live across the many postbacks your page will perform. Therefore, the context itself is not able to track the changes to the objects. So, how does the ObjectDataSource control manage to send your changes to the database?
NOTE Chapter 21 will provide an in-depth look at how the life cycle of an ASP.NET page impacts ObjectContext as you prepare to build a layered web application with entities. ObjectDataSource hides all of those concerns from you. The EntityDataSource control not only maintains the current values of its data, but also (by default) keeps track of the original values as they were retrieved from the data store. This is necessary for performing the updates to any modified data so that the DataSource knows exactly which fields were modified, as well as whether any of the properties in the model have been flagged for concurrency checks. The original values are critical. The EntityDataSource maintains the state information by keeping the original and current values, as well as any other critical values such as those that are being used for concurrency checking, in ControlState of the ASP.NET page.
ControlState is a special subset ofViewState that you cannot disable. The values are retained across postbacks and are then available when it is time to perform an update. You can modify this behavior with two properties of the EntityDataSource : EnableViewState and StoreOriginalValuesinViewState. Both properties areTrue by default. If you are new to ASP.NET, you can learn more about ViewState in the MSDN documentation as well as a variety of other resources. As you will see shortly, even if you choose not to retain the original values inViewState , you have opportunities to define original values prior to data updates in the EntityDataSource.Updating event.
Taking Stock of the EntityDataSource's Database Hits EntityDataSource is a very convenient control. Because it is completely declarative, you, the programmer, need to make only a small investment in providing data to your website. But you should be aware of what it's doing in the background. The EntityDataSource makes a lot of hits to the database. If you have one EntityDataSource that retrieves contacts and you edit the contacts in a GridView , here is a rundown of the events that will happen when you use the default settings:
Page Load A single query is run to retrieve the set of entities required for the control. If it isGridView a that uses paging, the query will retrieve the number of records defined by the page count. If it is a DetailsView or FormView, it will retrieve a single entity. If the binding control does any type of paging at all, includingDetailsView and FormView , a query is run in advance that gets a count of how many records satisfy the query before the paging records are selected. This means that for most controls, two queries are run every time the page loads.
User Clicks Edit This causes a page refresh. The initial query (or queries) is run again. If you are binding to another DataSource—for example, the Activity EntityDataSource to populate the drop-down list—its query is run as well. The queries are run separately so that there will be a number of hits to the database. A query is run against the database to retrieve a fresh copy of the entity to be edited.
User Clicks Cancel This causes the page to refresh again so that the initial query (or queries) is run again.
User Clicks Update The page is refreshed. An Update command is sent to the database, and then the initial query (or queries) is run again. This does not represent every action on the page, but it should give you an idea of what's happening on the server side.
11.3. Working with Related EntityReference Data The Hello Entities sample worked with a single entity: Contact . What if you want to work with Customer s instead? As you have seen in some of the book's earlier examples, most of the customers' relevant information lies in related EntityReference data: FirstName and LastName are in Customer.Contact , and preferences are in Customer.PrimaryActivity
and the other preference properties.
You can still achieve all of this with a single EntityDataSource control, but you'll have to do some additional work to bring back the data related to a customer and to be able to view and edit that data on the form.
11.3.1. Using EntityDataSource.Include to Get Related Data The EntityDataSource.Include property, which you learned about earlier in this chapter, works the same way as the ObjectQuery.Include method. Although EntityDataSource works most easily with the individual object you return, you can manually code some of the markup and work directly in the code-behind to exert more control over how the EntityDataSource functions, including how it handles the related data returned by the Include property. There are some limitations to how this related data is realized and what you can do with it, however.
NOTE For the following examples, change the EntityDataSource.EntitySet name to Customers as this will provide some relationships that are more relevant. Also, you can refresh the GridView schema to force it to display the Customer properties. The EntityDataSource control will automatically "flatten" EntityReference data in *:1 or *:0..1 relationships. In other words, where there is an EntityReference (e.g., Customer.PrimaryActivity), EntityDataSource will automatically surface the value of the EntityReference's primary key (PrimaryActivity.ActivityID). In this case, ActivityID will act as though it is a scalar property of Customer . When binding this to a control, you will be able to view and edit the ActivityID field directly.
NOTE There is one exception to the rule that the EntityDataSource uses for flattening relationships. If the reference key is also a property of the entity and a member of the entity's EntityKey (when an EntityKey is a composite key), it won't be flattened. You can see an example of this scenario if you were to create a model from the AdventureWorksLT database. The SalesOrderID foreign key of the SalesOrderDetail
table is part of the table's primary key, and in the model, the SalesOrderID is a scalar property of SalesOrderDetail and part of its composite EntityKey.
Figure 11-7 shows a section of the grid with a Customer entity in edit mode. Here you can see two of the flattened EntityReference navigation properties: PrimaryActivity.ActivityID SecondaryActivity.ActivityID
Figure 11-7. EntityReference navigation properties "flattened" for easy access to the relevant keys
The Customer has the ActivityID from its PrimaryActivityReference.EntityKey , but it does not have the Activity entity loaded. You can eager-load related data using the Include property of the EntityDataSource. This will add an Include method to the query that results, along with whatever navigation path you define in the property. If you set Include to PrimaryActivity , the related entity for each contact will be included in the returned data. But it will not automatically be bound to the data grid.
NOTE Include
is very handy for displaying read-only data with the EntityDataSource control. As you move through this chapter, you'll find that editing related data will
most often require the use of additional EntityDataSource controls.
11.3.2. Displaying Data That Comes from EntityReference Navigation Properties By default, the individual columns in a GridView contain controls. However, you cannot access properties from the related entity with these BoundField
s, even if the related entity has been loaded. Instead, you need to use controls, which provide you with more flexibility. You can easily
convert BoundField controls to TemplateField controls in the UI if you don't want to build the markup by hand: 1. Open the GridView Tasks window and select Edit Columns.
2. In the Available Fields listbox, expand the Bound Field node and double-click PrimaryActivity so that it moves to the Selected Fields list. 3. Move the field up so that it is positioned just after the PrimaryActivity.ActivityID field. 4. Edit the new field's DataField property to read PrimaryActivity.ActivityName.
If you attempted to run the form now, you would get an error because the BoundData control is unable to resolve the PrimaryActivity.ActivityName
property.
1. Change the PrimaryActivity's ReadOnly property to True. This is an important step. Otherwise, the EntityDataSource will not be able to update the related entity properties and will throw an error. 2. Click the "Convert this field into a TemplateField" hyperlink. 3. Click OK. You can see the TemplateField in the markup that's generated, as shown in Example 11-1.
Example 11-1. The new TemplateField as seen in the page's markup
Notice that because you changed the ReadOnly property to True before converting, the EditItemTemplate is a Label, not a TextBox, and it won't be editable.
The generated template has syntax that will create a problem. Around both instances of PrimaryActivity.ActivityName, remove the square brackets.
When you run the application, you'll see that the ActivityName is displayed but is not editable when you edit a row, and therefore will be blank. You'll need to provide a drop-down list containing all of the possible activities in order to edit the PrimaryActivity property.
NOTE You may have noticed the TemplateField 's automatically generated SortExpression property in Example 11-1. When binding to an EntityDataSource , you can override this by specifying an Entity SQL expression such as it.PrimaryActivity.ActivityName to control how the data is sorted. This will also impact the Eval expressions in the individual templates.
11.3.3. Using a New EntityDataSource Control to Enable Editing of EntityReference Navigation Properties To edit the PrimaryActivity fields you will need two elements. The first is a new EntityDataSource control to provide a list of activities. The second is an control in the grid: 1. Add a new EntityDataSource control named ActivityDataSource to the form. 2. Configure it to use BAEntities as its ConnectionString and EntityContainer. 3. Choose Activities for the EntitySetName . Do not check the checkboxes for enabling inserts, updates, or deletes because this will be used only for selection. The Activity pick list will be more useful if it's sorted. You can use the OrderBy property of the EntityDataSource control to sort the data. Remember that you will need to use the same Entity SQL syntax you used with the query builder methods:
1. Complete the Data Source Wizard. 2. Change the Sort property to it.ActivityName. The property uses the same Entity SQL syntax that you use with query builder methods, which is why you use the it reference variable. This task is not specific to the EntityDataSource control, but rather is one that you would have to perform regardless of the data source. You can define the DropDownList in the UI, but you need to handle some of the binding in the code-behind. 3. In the Source view, replace the asp:Label inside the EditItemTemplate tags with a DropDownList. The DataSourceID binds the DropDownList to the new ActivityDataSource,
and the DataTextField and DataValueField define which Activity fields to use for the display and value. The SelectedValue property gives you
two-way data binding back to the PrimaryActivity.ActivityID property of Customer, as shown in the following code:
NOTE The Web Designer in Visual Studio 2008 makes navigating to markup easy. In Design view, select the control whose markup you want to see. Then click Source at the bottom of the Designer window. The Source view will open and the markup for the control you selected in the Designer will be automatically selected in the source. Now you can edit a customer, select a new PrimaryActivity, and then update with ease.
You will find that some of the PrimaryActivity selections for customers are null. This will cause a page error to be thrown when you attempt to edit those customers. There's a simple way to avoid the problem. Add the AppendDataBoundItems=True parameter to the drop-down list and an asp:ListItem as a child, as shown here: Select...
11.3.4. Editing EntityReferences That Cannot Be Satisfied with a Drop-Down List The preference properties that you can now edit directly in the grid are not the only flavor of EntityReference that a Customer entity points to. Customer has a relationship to Contact , which supplies properties such as FirstName and LastName, as well as accesses other data that is related to Contact. The BoundField control binding does not support navigating to or editing the Contact properties either. But neither does the solution you used for the preference properties, which was to embed a DropDownList into the grid. You can view the related Contact data in the same way you were able to view the ActivityName—by adding Contact to the Include property so that the property now reads as PrimaryActivity,Contact. Then you can create TemplateField s bound to Contact.LastName and Contact.FirstName . You can also make this column read-only by using a label in the EditTemplate as you saw with the initial rendering of the ActivityName column. But what about editing? An EntityDataSource will update only the specific entity to which it is bound. If you use Include to bring the additional Contact entity back from the database, it will be ignored during updates. Instead, you'll have to edit the Contact by creating an EntityDataSource specifically for contacts and binding that EntityDataSource to the selected item of the grid that displays the customers.
NOTE If you are following along in Visual Studio, remove Contact from the Include property of the CustomerDataSource and create a new EntityDataSource, named ContactDataSource,
for contact entities.
11.3.5. Binding an EntityDataSource to Another Control with WhereParameters The WhereParameters element is not the same as the Where clause in a query. It's a feature common to ASP.NET DataSource controls that enables filtering based on the values of other controls. This will help solve the problem of editing Customer.Contact entities. You'll create an EntityDataSource for Contacts and filter it based on the ContactID
of the currently selected Customer in EntityDataSource1.
You enter WhereParameters directly in the markup and it requires that the EntityDataSource.AutoGenerateWhereClause property be True. This will tell the EntityDataSource to generate the query's Where clause from WhereParameters . You can change this latter property in the Properties window or directly in the markup: 1. Create a new EntityDataSource named ContactDataSource. 2. Change the AutoGenerateWhereClause property to True. 3. Modify the source of the control, adding WhereParameters, so that the markup now looks like Example 11-2.
Example 11-2. The WhereParameters element used to bind the EntityDataSource to a GridView's SelectedValue
WhereParameters
is instructing the control to modify the query to look for Contacts with a ContactID equal to GridView1.SelectedValue . Because most of the data-binding
controls return their SelectedValue as a string, the additional DbType attribute ensures that this is passed in as an integer. This filter becomes part of the Entity SQL query that is translated and sent to the database each time a new selection is made in the GridView .
If you forget to set the AutoGenerateWhereClause to True, you will get an exception message that says you can't have the AutoGenerateWhereClause=False . Remember that AutoGenerateWhereClause=False plus the WhereParameters is
WhereParameters when
an alternative to using the Where property of the EntityDataSource.
Because this filter is dependent on the SelectedValue of the GridView , you'll need to enable Selection on the GridView . You can do this in the GridView Tasks window.
Dynamically or Programmatically Setting the EntityDataSource Control Values All of the values you have set through the EntityDataSource 's wizard or the Properties window are parameters of the control that you can also set directly in markup or programmatically. Here is what the control currently looks like in the Source view of the web page:
You can make changes directly in the markup and even use expressions to populate the values as you can with any other ASP.NET or HTML control. If you want the option to set any of the parameters at runtime, you can set them in the code-behind as well. For example: Customers.Include="Reservations.Trip.Location"
This enables you to change any of the parameters dynamically if you won't know the values until runtime.
11.3.6. Editing Related Data Concurrently with Multiple EntityDataSource Controls Editing customers in the grid view and editing a customer's contact information will occur as separate actions. This is just the nature of the ASP.NET DataSource controls. In the case of the EntityDataSource , this means it can create insert, update, and delete commands for only a single entity, not for graphs. There are two more steps to finish off this part of the example: 1. Open the GridView Tasks window and check Enable Selection if you haven't already done this. 2. Drag a DetailsView onto the form and bind it to the ContactDataSource. Run the form and you will see that as you select different customers, the contact details change to reflect the contact information of the selected customer. You can edit the contact information if you like. This example demonstrated how data binding works between EntityDataSources and data-binding controls. You can clean up the GridView and DetailsView by formatting the columns, but more important to keep in mind about this example is that separation of the customer's information in the GridView and DetailsView is not a user-friendly design. As long as Contact and Customer are in two separate entities, you won't be able to edit them as a single unit using the EntityDataSource control. It's still possible to make a logical UI, however. The page shown in Figure 11-8 has reversed the EntityDataSource s so that the Customer's WhereParameters defines a dependency on the ContactDataSource. The DetailsView that is bound to the Contacts has paging and is used for navigation. As the user navigates from one contact to another, the contact's customer data, if any, is displayed in the second DetailsView . Given the particular scenario, this makes more sense visually than using a GridView .
Figure 11-8. A data-driven form that is defined declaratively with EntityDataSource and DetailsView controls—not a single line of code
NOTE In the next chapter, you'll learn how to build inheritance into the model to make Customer and Contact blend into a single entity. However, modifying your model
is not the solution to making it easier to build your UI. EntityDataSource is not going to be the solution for every scenario. If you were using a business layer, as you'll learn to do in Chapter 20 , you won't be tied down to the rules of the EntityDataSource and you will have more flexibility in building your UI.
11.4. Working with Hierarchical Data in a Master/Detail Form Many data-focused applications are used to present hierarchical data, so this next example will focus on parent/child/grandchild data using EntityDataSource controls. As the previous example allowed you to work with related EntityReference data, this example will give you an opportunity to use EntityDataSource controls to work with related child entities. In this example, you will use a variety of methods to populate controls on a web form and take advantage of the EntityDataSource's editing capabilities. This form, shown in Figure 11-9 , will let BreakAway employees view customers and their reservations as well as add payments. You will get a chance to work with a variety of relationships and binding scenarios. And in the course of doing this, you will hit a few speed bumps and learn how to get around them.
Figure 11-9. A small web application that lets the user interact with hierarchical data
The form will use EntityDataSource controls; in addition, you will do some direct data binding to query results.
11.4.1. Setting Up the Web Application Now that you have an idea of the tasks that this application will teach you, let's start building it: 1.
In the same solution that you have been working in, create a new ASP.NET web application. As with all of the other applications you have built, copy the connection string from the model project's app.config file into the web.config file and add a reference to the BreakAwayModel project.
2.
Build the project so that the EntityDataSource controls will be able to find the entity connection string in the web.config file.
3. Drag a DropDownList onto the web form. 4. From the list's TaskList, create a new EntityDataSource, as you did from the GridView in the previous example. 5. Set the DataSource to BAEntities ConnectionString and EntityContainer. 6. Select the Contacts EntitySet and click Finish.
11.4.2. Specifying Your Own Entity SQL Query Expression for an EntityDataSource The Entity Data Source Wizard only allows you to select entire entities or properties from those entities. However, for this DropDownList you want to combine the LastName and FirstName properties.
It's possible to do this by using your own Entity SQL string rather than letting the query builder methods construct the string based on the various properties of the EntityDataSource.
NOTE Keep in mind that the results of these custom queries will be read-only. The Entity SQL string shown here will return a ContactID field and a Name field that you can bind to the drop-down list. It also returns only those contacts that have a Customer
record; therefore, you can be sure you'll be working only with customers, as noncustomer contacts do not have reservations, destination or activity
preferences, or other properties specific to a customer: SELECT c.contactid, TRIM(c.lastname) + ", " + c.firstname AS Name FROM BAEntities.Contacts AS c WHERE c.Customer IS NOT NULL ORDER BY Name
NOTE Using IS NULL or IS NOT NULL is useful for testing for the existence of an EntityReference . To do this with an EntityCollection , you would use EXISTS or NOT EXISTS , with a subquery into the collection to see whether there are any items in the collection. 1. Enter the Entity SQL query into the CommandText property of the EntityDataSource. EntityDataSource cannot
have an EntitySet designated if you want to override the query using the CommandText property. You selected it in the wizard because the Finish button is inactive until you select something. 2. Remove Contacts from the EntitySet property.
NOTE If you forget to do this, an exception will be thrown that specifically says you cannot have an EntitySet value if you also have a CommandText value.
11.4.3. Binding a DropDownList to an EntityDataSource Control The last task for the first pass at this web page is to wire up the new EntityDataSource to the DropDownList that you already added to the page, and then to test your progress by running the application.
NOTE You might consider a few options for populating the drop-down list. What's useful to realize is that, by default, the DropDownList will use ViewState to retain the values and display text for the items in the list. If you use an EntityDataSource to populate this drop-down list, you won't have to worry about that query being called every time the page is refreshed. 1. In the DropDownList's Tasks window, select the Choose Data Source option. 2. Be sure its data source is pointing to the new EntityDataSource you created. 3.
Type Name into the combo box that says "Select a data field to display in the DropDownList" and contactID into the box that says "Select a data field for the value of the DropDownList". Because the properties are coming from the Entity SQL expression, the wizard will not be able to detect the property names, and therefore they won't be available in the drop-downs. Just type them in directly.
4.
Run the application to test the drop-down list.
11.4.4. Creating a Parent EntityDataSource That Is Controlled by the DropDownList and Provides Data to a DetailsView The next step is to create a DetailsView that is dependent on the selection of the DropDownList. A new EntityDataSource will use the WhereParameters to bind to the SelectedValue of
the DropDownList.
1. Drag a DetailsView onto the form. 2. Use its Tasks window to create a new EntityDataSource control. 3. Set the EntityDataSource's ConnectionString and EntityContainer to BAEntities . 4. Select Contacts as the EntitySet. When you complete the Entity Data Source Wizard, the DetailsView should automatically populate with the Contact properties. 5. In the Source view of the page, add the WhereParameters directly into the markup to bind the new EntityDataSource control to the SelectedValue of the DropDownList, as shown in Example 11-3.
NOTE Don't forget to set the AutoGenerateWhereClause to True.
Example 11-3. Defining the WhereParameters for an EntityDataSource
Now the DetailsView will update every time an item is selected in the DropDownList. In Figure 11-10 a bit of formatting has been applied. Most notably, the DataFormatString
for the two date fields in the DetailsView was changed to {0:d} to affect the default display of the date values.
Figure 11-10. The DetailsView displaying the customer selected in the DropDownList
11.4.5. Using the EntityDataSource.Where Property to Filter Query Results Currently, the Customers EntityDataSource is defined to get all customers. It needs a filter. There are a few ways to filter. Similar to the OrderBy property, which you used in the Hello Entities demo earlier, the Where parameter allows you to insert query logic that will become part of the actual query the EntityDataSource builds. The value needs to be in the Entity SQL format that you would use with query builder method parameters. For example, if you wanted to duplicate the query that is called in the form load that filters only on customers who have reservations, you would first need to translate the query into a query builder method using Entity SQL syntax. Here is that rewritten query without the Select clause that was used to build the anonymous type in the form load: select value c from oftype(BreakAwayEntities.Contacts,BreakAwayModel.Customer) as c where count(select value r.ReservationID from c.Reservations as r)>0
The where clause for this query is count(select value r.ReservationID from c.Reservations as r)>0 , and that is what you would put into the value of the Where property in the EntityDataSource Properties
window.
11.4.6. Displaying Read-Only Child Data Through the Parent EntityDataSource In this form, the user will be viewing, but not editing, reservations. Therefore, you can take advantage of the Include property of the Contact DataSource and you won't need a separate EntityDataSource for the Reservations. We'll use a ListBox to display the reservations: 1. Drag a ListBox control onto the form. The model allows for navigation from contact to customer to reservations to trip, and then to destination. All of these relationships will make it possible to display the detailed reservation information for each contact who is a customer. 2.
Add the following to the Include property of the Contact EntityDataSource control: Customer.Reservations.Trip.Destination
Because the Customers EntityDataSource uses Include , all of the entities listed in the Include path, the reservations, the trip, and the destination associated with the selected customer will be retrieved from the database when the customer query is executed. But you still need to push the details into the ListBox , and you'll need to do this in code. The EntityDataSource control's Selected event is just the place to do this. The Selected event provides an EntityDataSourceEventArgs object as the parameter, e. This, in turn, provides you with a Results property that returns an object—in this case, a Contact entity. By casting that result to a Contact type, you can navigate to the customer's reservations, trip, and destination details.
11.4.6.1. Coding the EntityDataSource's Selected event to populate a control with navigation property details From the Events list in the customer's Properties window, double-click the Selected event. Because it is possible to select multiple items, the e.Results from the EntityDataSourceSelectedEventArgs
contains an IEnumerable of objects. To get the selected contact, you'll need to cast that collection to the correct entity type using the
LINQ Cast method and then extract the first item in the collection. Once you've done that, you can use the TripDetails property you created in the preceding chapter to display useful information about the reservations. If you didn't have that already, you would be able to write a LINQ to Objects query against the reservations and shape the data as you want to display it in the ListBox. Add the code shown in Example 11-4 to the Selected event method.
Example 11-4. Using the EntityDataSource.Selected event to populate a listbox control
Private Sub ContactDataSource_Selected _ (ByVal sender As Object, ByVal e As _ System.Web.UI.WebControls.EntityDataSourceSelectedEventArgs)_ Handles ContactDataSource.Selected Dim contact = e.Results.Cast(Of BAGA.Contact)() Dim res = contact.First.Customer.Reservations If res.Count > 0 Then With ListBox1 .DataSource = res.ToList() .DataTextField = "tripdetails" .DataValueField = "ReservationID" .DataBind() .SelectedIndex=0 End With Else ListBox1.Items.Clear() End If End Sub
protected void ContactDataSource_Selected (object sender, EntityDataSourceSelectedEventArgs e) { var contact = e.Results.Cast(); var res=contact.First().Customer.Reservations; if (res.Count > 0) { ListBox1.DataSource = res.ToList(); ListBox1.DataTextField = "tripdetails"; ListBox1.DataValueField = "ReservationID"; ListBox1.DataBind(); ListBox1.SelectedIndex=0 } }
NOTE
For the code in Example 11-4 to execute, you'll need a reference to System.Data.Entity . This is required because you are working with EntityCollection in a way that is not already supported by the namespace used by EntityDataSource.
11.4.7. Using a New EntityDataSource to Add a Third Level of Hierarchical Data to the Master/Detail Form When a customer is selected and her reservations are displayed, the next step is to select a reservation and view the payments for that reservation. You can do this by combining a ListView with another EntityDataSource. You'll be entering new payments, and therefore you'll need an EntityDataSource to perform those inserts. We're using the ListView in this example because unlike the GridView , the ListView control allows easy insertion. The new EntityDataSource will use WhereParameters to create a dependency on the selected reservation from the ListBox. 1. Set Enable AutoPostBack on the ListBox that displays reservations. This will force the page to respond to a user selecting an item in the ListBox. 2. Add a ListView to the form. 3. From the GridView Tasks window, create a new EntityDataSource control. 4. Set the EntityDataSource's Connection and EntityContainer to BAEntities . 5. Set the EntityDataSource's EntitySetName to Payments . 6. Check Enable Automatic Inserts. The ListView will look much better if you do some formatting. If you haven't used a ListView before, you might be surprised that most of its formatting is performed in the markup. The ListView creates templates for Select, Insert, Edit, Alternate, and Empty views. As you will need only Select and Insert views, you can delete the other sections. Example 11-5 shows the markup for the ListView after it has been trimmed down.
Example 11-5. Formatted ListView after deleting much of the default markup
| | |
| | |
NOTE Notice the formatting that's been added to the ReservationDate property. Date and currency formatting is controlled by culture info settings, which you can control programmatically or in your application's web.config file. Look for globalization topics in the MSDN documentation for more information on this. 1. Click the Payments EntityDataSource the wizard created. 2. Set the AutoGenerateWhereClause property to True. 3. Add the following WhereParameters to the Payments EntityDataSource markup in the source of the page:
This parameter tells the data source to query only for payments whose Reservations.ReservationID property is equal to the SelectedValue of ListBox1. Like the DropDownList ,
that value will be a string and needs to be converted to an integer.
NOTE The DefaultValue is set to 0 because even if there are no reservations, the Payments query will run. Without the default, all of the payments in the database will be returned. The default forces the query to search for payments whose ReservationID=0 , which will return no data. Check the previous section on WhereParameters if you need a reminder of exactly where this needs to be placed in the markup of the data source.
11.4.8. Using the EntityDataSource.Inserting Event to Help with Newly Added Entities The ListView has built-in functionality for inserting items, but one thing is missing. You will need to manually add the ReservationID . EntityDataSource
has a number of events that you can take advantage of. The Inserting event gives you an opportunity to impact the entity that is about to be inserted
into the database. Here is where you can add the ReservationID to the new payment before it goes to the database. The EntityDataSourceChangingEventArgs exposed by the Inserting event (as well as many of the control's events) has an Entity property. In the Inserting event, the Entity property refers to the entity that is about to be sent to the database. Example 11-6 shows the Payment.ReservationReference.EntityKey being used to identify the payment's ReservationID . The ReservationID comes from the selected item in the ListBox.
Example 11-6. Defining the payment's ReservationReference in the Inserting event
Private Sub PaymentsDataSource_Inserting _ (ByVal sender As Object, _ ByVal e As _ System.Web.UI.WebControls.EntityDataSourceChangingEventArgs) _ Handles PaymentsDataSource.Inserting
Dim newPmnt = CType(e.Entity, BAGA.Payment) newPmnt.ReservationReference.EntityKey = _ New EntityKey("BAEntities.Reservations", "ReservationID", _ CInt(ListBox1.SelectedValue)) End Sub
protected void PaymentsDataSource_Inserting (object sender, EntityDataSourceChangingEventArgs e) { var newPmnt = (BAGA.Payment)e.Entity; newPmnt.ReservationReference.EntityKey = new EntityKey("BAEntities.Reservations", "ReservationID", Convert.ToInt32(ListBox1.SelectedValue)); }
11.4.9. Testing the Application Finally, it is time to test your handiwork. Press F5 to run the application, which should look something like Figure 11-11 . Select various customers and reservations. You can see how the hierarchical data is automatically presented as you change selections—and all with a minimum of code, highlighting the RAD capabilities of the Entity Framework.
Figure 11-11. Parent, child, and grandchild hierarchical data being served up by EntityDataSource controls
NOTE In Chapter 7, you mapped Insert, Update, and Delete functions to the Payment entity. Now, every time you create a new payment, the InsertPayment stored procedure is executed in the database. If you were to look in SQL Server Profiler, you would see the following command:
exec [dbo].[InsertPayment] @date='2006-02-01 00:00:00:000', @reservationID=90,@amount=250.0000
11.5. Browsing Through the EntityDataSource Events EntityDataSource is a very rich control, with many events that you can use to have granular control over its behavior as well as how it relates to other entities in the application. You have had a chance to use only a few of them in this chapter, but here is an overview of the events that you can take advantage of as you build your own applications with the EntityDataSource control.
11.5.1. EntityDataSource Events and Page Events When a page with an EntityDataSource starts up, here is the order in which theEntityDataSource and Page events fire: 1. Page BeginLoad 2. EntityDataSource Load 3. Page BeginPreRender 4. EntityDataSource.ContextCreating 5. EntityDataSource.ContextCreated 6. Page EndPreRender 7. EDS ContextDisposing 8. Page Unload A number of additional events are related to data modification:
Deleting and Deleted Inserting and Inserted Updating and Updated Selecting and Selected All of these events represent opportunities to customize your control's behavior. For instance, in the preceding example you trapped the Inserting event to add an additional value to an entity that was about to be inserted. Additionally, you used the Selected event to determine which entity had been selected and then populated ListBox a with its related data. Each of these events provides relevant information through itsEventArgs variable, e. Here are the ones that are of the most interest:
Inserting
e.Entity returns the entity that is being inserted. If you want to work with this entity, you will need to cast it to its proper type as you did in the sample you just built.
e.Cancel gives you an opportunity to cancel the insert. You would do this by setting the value of e.Cancel to True.
Inserted This event fires after the item was inserted into the data store.
e.Entity returns the entity that was just inserted. This includes the newEntityKey because the data store returned the necessary value. Remember that if you are letting the Entity Framework generate the commands, it will get the new key by default. If you are using your own stored procedure, as we did for the payments, this value will be returned only if the procedure sends it back and if you have wired it up in the mappings, which you did at the beginning of the chapter.
e.Context gives you access to the context. e.Exception and e.ExceptionHandled give you an opportunity to trap any problems that may have occurred either by constraints in the model or by constraints in the database.
Updating
e.Entity returns the entity in its current state. You will need to cast theNewEntity to the correct entity type to interact with it in detail.
e.Cancel, e.Exception, and e.ExceptionHandled are available in this event.
Updated The EventArgs provides the same properties as withUpdating.
Selecting
e.DataSource provides a reference to the EntityDataSource and its properties so that you can affect them at runtime if necessary. By changing the properties in the Selecting event, you can redefine theDataSource prior to the retrieval of data from the store. The properties you can access or change during this event are CommandParameters, CommandText, OrderBy, OrderByParameters, Where, and WhereParameters.
e.SelectArguments provides you with an opportunity to tweak the properties of theEntityDataSource control, including properties that define its query. It also exposes some of the properties from the data-bound control (e.g., GridView) that the EntityDataSource is bound to, such asMaximumRows,
RetrieveTotalRowCount, SortExpression, StartRowIndex, and TotalRowCount. You can cancel this event with e.Cancel if you need to.
Selected
e.Results provides an array of entities. You worked with this property in the second example. This event also provides access to the context, any exceptions that were thrown, and SelectArguments.
Deleting
e.Cancel gives you an opportunity to cancel the delete. You would do this by setting the value of e.Cancel to True. Context.Entity and Exceptions are available in this event.
Deleted This provides access to the context, the entity, and any exceptions.
11.6. Summary In this chapter, you built two different types of small web applications using the EntityDataSource control, which gave you a hands-on opportunity to see how you can use entities and query results in some simple web application scenarios. The EntityDataSource control is perfect for Rapid Application Development and does provide you with a lot of control over its functionality if you take advantage of its properties and events.
EntityDataSource is very convenient for building quick web applications against the Entity Data Model. Although it's convenient, its use incurs some resource overhead that you may not desire. In addition, more complex applications require business layers, so defining the data in the UI might not even be an option.
Chapter 12. Customizing Entity Data Models So far in this book, you have worked with models that closely match the database. You have made some simple changes to the names of entities, and in one case you went deeper and leveraged inheritance in the model. The Entity Data Model (EDM) offers enormous flexibility when it comes to customizing models so that they are more than mere reflections of your database. This capability is one of the main reasons many developers choose to use the Entity Framework. In this chapter, you will learn about the many ways in which you can customize an EDM, the benefits of these customizations, and when you would want to take advantage of them. Although most customization occurs in the Conceptual Schema Definition Layer (CSDL), you can use additional mappings and even storage schema modifications to create a model that truly describes your data in a way that fits well with your vision of how the data should look. Customizations that are created in the conceptual layer are dependent on their mappings back to the database to function properly. Because of this, the customizations are more often referred to as mappings , as you will see throughout this chapter. You will also learn how to build queries using the new mappings and interact with the objects that are based on the various entities. A number of modeling techniques are related to stored procedures as well. I will cover those in the next chapter.
12.1. Designer Support for Mappings This chapter will first cover mappings that you can achieve using the Designer. These are the mappings that are more commonly used. The EDM allows other types of model customizations, but unfortunately the Designer does not currently support them. Later in this chapter, we'll look at additional modeling techniques that require you to manually modify the XML. Many of the stored procedure mappings covered in the next chapter will also require manually editing the EDMX file. The Designer-supported mappings are inheritance mapping (implemented in a number of ways), conditional mapping, and entity splitting (sometimes called vertical splitting), which allows you to build an entity that maps back to multiple tables. We'll start with the most common implementation for inheritance, Table per Type (TPT). While working through the TPT implementation, you'll also learn about inheritance in the model in general.
12.2. Mapping Table per Type Inheritance for Tables That Describe Derived Types The BreakAway business has a number of different types of contacts. TheContact table keeps track of the common information for all contacts, such as FirstName and LastName . Some of those contacts are customers, and a separate table keeps track of the additional information about these types of contacts—their preferences, notes, and the date they first became customers. In the past few chapters, when working with customers you have had to constantly go back to the Contacts entity to get the customers' names and addresses. In object-oriented programming, when one object is a type of another object, you can use inheritance to share properties so that the properties of a base type (e.g., Contact) are exposed directly in a derived type (e.g.,Customer ). The EDM supports inheritance as well. The inheritance mapping used to allow Customer to derive from Contact and absorb Contact's properties is called Table per Type inheritance . Let's investigate this one first, and modify the model to simplify working with customers. Table per Type (TPT) inheritance defines an inheritance that is described in the database with separate tables where one table provides additional details that describe a new type based on another table. Figure 12-1 depicts the concept of TPT inheritance.
Figure 12-1. Database tables that can be used for TPT inheritance
Figure 12-1 shows a 1:0..1 (One to Zero or One) relationship betweenContact and Customer in the database. This means aContact could have a related Customer entity, but it's not required. It also means aContact cannot have more than one relatedCustomer entity. The Customer table provides additional information about this subset of the contacts.
12.2.1. Mapping TPT Inheritance Let's replace the navigation that the Entity Data Model Wizard created betweenContact and Customer with an inheritance hierarchy that maps back to the database tables.
Samples used throughout the rest of this book will be dependent on most of the model changes that the mapping walkthroughs in the chapter describe. If you are following the examples, be sure to perform the steps described in this chapter. A few of the walkthroughs at the end of the chapter are not used by later examples (these are noted).
1.
Delete the association between Contact and Customer that the EDM Wizard created when you originally created the model in Chapter 7. You can do this by selecting the line that represents the association and deleting it.
2.
Add an inheritance object between the Contact and the Customer, with Contact as the base type andCustomer as the derived type. The Designer provides two ways to add inheritance. You can select an inheritance object from the Toolbox and click on the entity that will be derived from the other entity, or you can add it from an entity's context menu. Let's use the context menu method.
3.
Right-click the Contact entity. Choose Add and then Inheritance from the context menu.
4.
In the Add Inheritance window, select Contact as the base entity and Customer as the derived entity, as show in Figure 12-2. Customer will inherit properties from Contact.
5.
Delete the EntityKey (ContactID ) from the derived type (Customer). Customer will now inherit its EntityKey from Contact.
6.
Open the Mapping Details window forCustomer.
7.
Map the Customer's new ContactID property (which now comes from theContact entity) to the ContactID column in the Customers table.
When the inheritance is set up, the Customer entity will have a glyph at the top that indicates it is inheriting fromContact . There is an inheritance line between the two entities as well, with the arrow pointing to the base entity (see Figure 12-3).
Figure 12-2. Defining an inheritance between Contact and Customer
Figure 12-3. The new inheritance displayed in the model
12.2.2. Fixing the Impact of the New Inheritance on the Customer's Associations with Other Entities Because the Customer's ContactID was deleted so that it can now inherit fromContact, a number of the mappings for associations involving Customer were broken. If you compile the model, you'll find a list of mapping errors. The associations still exist, so you'll need to remap the associations.
NOTE Understanding how to map associations in the model is another important mapping technique. The inheritance you are building conveniently forces you to learn this as well. The Customer entity has six associations defined with other entities. This will be a good lesson in mapping associations, because this task is not very intuitive in the Designer. Select the association between Customer and CustomerType . You'll see that theCustomerTypeID mapping is still in place, but the ContactID mapping to the Customers table is gone. Select ContactID in the drop-down under Column to map from theContactID property, as shown in Figure 12-4.
Figure 12-4. Remapping the ContactID in the association between Customer and CustomerType
Remap the ContactID property in the association betweenCustomer and Reservation. Although you could edit the existing mappings for the associations, you will be better off completely removing and re-creating the four associations between Customer, Activity , and Destinations . As you delete the associations, the navigation properties will also disappear. Don't worry; they will return as you re-create the associations. There are two associations betweenCustomer and Activity . In the following exercise, you will create the firstActivity association and mapping: 1.
Create the first association by dragging anAssociation control from the Toolbox and attaching one end toCustomer and the other to Activity.
2.
Fix the navigation property names. In the Activity entity, rename the Customer navigation property to PrimaryPrefCustomers. In the Customer entity, rename Activity to PrimaryActivity.
3.
Define the multiplicity between the entities. The relationship between Activity and Customer is 0..1:*, which means you can have zero or one activity for a customer and each activity can be mapped to many customers. In the Properties window, edit the ends of the association so that the activity end is 0..1 (Zero or One) and the customer end is * (Many), as shown in Figure 12-5.
There is a bug in the Designer that is related to 0..1:* mappings. You should create the mapping first as a 1:* (with the Activity end as the "one" and Customer as the "many"), and then define the mappings in step 4. Then return to the association's properties and change the Activity end to 0..1 ("zero or one"). The Designer must insert a condition element for the mapping but neglects to when mapping a 0..1 end. By performing the steps in this order, the condition element will be inserted properly. Follow the same steps for the other 0..1:* associations.
1.
Create the mapping by right-clicking the association and selecting Table Mapping to get to the association mappings. When creating association mappings, if there is a Many end of the association, choose its table for the Maps To option. In this case, Customer is on the Many end of the relationship, so selectCustomers for the mapping. ContactID should map automatically. Map ActivityID to the Customers.PrimaryActivity column, as shown in Figure 12-6.
Figure 12-5. Multiplicity for the association
Figure 12-6. Association mapping for PrimaryActivity
Now you can create the second mapping between Customer and Activity . To begin, add another association betweenCustomer and Activity . Follow steps 1–4 in the preceding exercise, but this time change the navigation properties to SecondaryCustomerPrefs and SecondaryActivity , fix the multiplicity, and then map the association toCustomers.SecondaryActivity and Customers.ContactID. Follow steps 1–4 again to remap the associations used for theCustomer's PrimaryDestination and SecondaryDestination properties. The navigation properties for the first association will be PrimaryCustomerPrefs and PrimaryDestination. This association will map to the Customers.PrimaryDestination property. The navigation properties for the second association will beSecondaryCustomerPrefs and SecondaryDestination. This association will map to theCustomers.SecondaryDestination property.
12.2.3. Handling Properties with the Same Name Both the Customers and the Contact tables have aTimeStamp column for concurrency checking. However, because theCustomer is inheriting all of the properties of Contact, the Customer now has two TimeStamp properties. To avoid conflict, change the name of the
Customer's TimeStamp property to custTimeStamp. In Chapter 18, you'll learn more about concurrency with inherited types.
12.2.4. Querying Inherited Types As a result of the inheritance, the Customer object inherits the Contact properties. You no longer need to navigate to Contact to get the Customer's LastName, FirstName, or other Contact properties. You can also navigate directly to theAddresses EntityCollection through the Addresses property. In the model, this means the Customers EntitySet is now gone, and Customer is served up from the Contacts EntitySet. When you request Contacts, those Contacts that have aCustomer entity will be returned as Customer types. To query for customers specifically, you will need to use the OfType method to specify which type of contact you are seeking, as shown in the following code:
From c in Contacts.OfType(Of Customer) Select c
from c in Contacts.OfType select c;
You'll see many more examples of querying types in an inheritance hierarchy throughout this chapter and the rest of the book.
12.2.5. Creating a Project to Test the New Mappings To test this new TPT inheritance, as well as the various customizations you will be creating further on in this chapter, create a new Console Application project and then follow these steps: 1.
Set up the Console Application project to use the model, as you did with the previous projects: a.
Add a reference to System.Data.Entity.
b.
Add a reference to the model project.
c.
Copy the connection string from the model's app.config file into the new project's app.config file.
NOTE The .config file for a console application is not automatically created, so you'll need to use Add New Item to create it. If the new project is created for the same language as the model project, you can simply copy the entire app.config file from the model project into the new project. 2.
Open the project's main code file (Module1.vb or program.cs).
3.
Import the model's namespace at the top of the code file using the followingcommands:
Imports BAGA.BreakAwayModel
using BAGA.BreakAwayModel;
12.2.6. Testing the TPT Inheritance Let's see the inheritance in action. 1. Add the method in Example 12-1 to the test module. This will query for contacts who are customers.
Example 12-1. Querying a derived type
Private Sub TPTMap() Using context As New BAEntities Dim query = From c In context.Contacts.OfType(Of Customer)() _ Select c Console.WriteLine("Customers: " & query.Count.ToString) 'query all Contacts Console.WriteLine("All Contacts: " & _ context.Contacts.Count.ToString) Dim newCust As New Customer With newCust .FirstName = "Noam" .LastName = "Ben-Ami" End With context.AddToContacts(newCust) context.SaveChanges() End Using End Sub
private static void TPTMap()
{ using (BAEntities context = new BAEntities()) { var query = from c in context.Contacts.OfType() select c; Console.WriteLine("Customers: " + query.Count().ToString()); //query all Contacts Console.WriteLine("All Contacts: " + context.Contacts.Count().ToString()); Customer newCust = new Customer(); newCust.FirstName = "Noam"; newCust.LastName = "Ben-Ami"; context.AddToContacts(newCust); context.SaveChanges(); } }
2.
Call the TPTMap method from the module's Main method.
3.
Set a breakpoint at the line that instantiatesnewCust.
4.
Run the application.
When debugging the Customer results, you can see that the Customer has inherited the LastName and FirstName properties of Contact. When debugging the Contact results, you can see that only the Contact properties are there, even for contacts who are customers. Finally, looking at the counts displayed in the output, you'll find that the number of queried customers is much smaller than the number of contacts, and is, in fact, a subset of contacts.
12.2.7. SaveChanges and Newly Added Derived Types In Example 12-1, a Customer was created in memory, added to the context, and then saved to the database withcontext.SaveChanges. When SaveChanges is called, the Entity Framework constructs commands to first create a newContact record, and then, based on the newly generated ID returned from the database, to create the Customer record. Example 12-2 shows the two commands executed on the database as a result of the code inExample 12-1 . The first inserts a contact and does a SELECT to return the new ContactID and TimeStamp . The second inserts a new Customer using the new ContactID, 851.
Example 12-2. T-SQL commands created based on the new Customer created in the previous example
exec sp_executesql N'insert [dbo].[Contact]([FirstName], [LastName], [Title], [AddDate], [ModifiedDate]) values (@0, @1, null, @2, @3) select [ContactID], [TimeStamp] from [dbo].[Contact] where @@ROWCOUNT > 0 and [ContactID] = scope_identity()', N'@0 nchar(4),@1 nchar(7),@2 datetime2(7),@3 datetime2(7)', @0=N'Noam',@1=N'Ben-Ami',@2='2008-10-23 14:07:33.7290000', @3='2008-10-23 14:07:34.6000000'
exec sp_executesql N'insert [dbo].[Customers]([ContactID], [CustomerTypeID], [InitialDate], [PrimaryDesintation], [SecondaryDestination], [PrimaryActivity], [SecondaryActivity], [Notes]) values (@0, @1, null, null, null, null, null, null) select [timestamp] from [dbo].[Customers] where @@ROWCOUNT > 0 and [ContactID] = @0', N'@0 int,@1 int',@0=851,@1=1
Notice that the AddDate and ModifiedDate have values in the Contact insert, and the Customer insert has a value forCustomerTypeID. These values are coming from the custom SavingChanges event you built in the preceding chapter. The newCustomer record is seen as both a Contact type and a Customer type. Therefore, as SavingChanges tested for the entity type and populated values based on that, the new Customer entity got the required values forContact and for Customer.
12.2.8. Specifying or Excluding Derived Types in Queries You can explicitly query for different types within an inheritance structure. To specify a derived type with LINQ or Object Services you can append the OfType method to the entity set being queried:
Context.Contacts.OfType(Of Customer)
Context.Contacts.OfType
You can do this in a variety of other ways in LINQ, as well. In Visual Basic, you can use the TypeOf operator for type filtering:
From c In context.Contacts _ Where TypeOf c Is Customer Select c From c In context.Contacts _ Where Not TypeOf c Is Customer Select c
In C#, you can do direct type comparison:
from c in context.Contacts where c is Customer select c; from c in context.Contacts where !(c is Customer) select c;
Entity SQL also has operators for working with types, and in fact, it can filter out types in a way that is not possible with LINQ to Entities. The type operators you will use most commonly in Entity SQL areOFTYPE and IS [NOT] OF . The following code snippets represent examples of how you could rewrite the preceding queries with Entity SQL. Note that you could do this by using query builder methods, as well. To return only Customer types: SELECT VALUE c FROM OFTYPE(BAEntities.Contacts, BAModel.Customer) AS c
To return Contacts which are notCustomer types: SELECT VALUE c FROM BAEntities.Contacts AS c where c IS NOT OF(BAModel.Customer)
There is an additional Entity SQL operator called TREAT AS that allows you to do type casting directly in the Entity SQL expression. The preceding two Entity SQL expressions will return results that are still shaped likeContacts . To ensure that the results are shaped like the types that you are seeking, you'll need to use TREAT AS. As with the OFTYPE operator, be sure to use the assembly namespace in the strongly typed name of the type you are casting to. To return only Customer types that are type-cast asCustomer types: SELECT VALUE TREAT(c AS BAModel.Customer) FROM OFTYPE(BAEntities.Contacts, BAModel.Customer) AS c
As you can see, you can also use Object Services andEntityClient with Entity SQL to build more complex queries around types. In LINQ, the safest way to do type filtering is to use theOfType method, because the rest of the query will know you are working with Customer and not Contact , so you can do any filtering or projection based onCustomer properties. When you place the type filter in theWhere clause, the rest of the query is still based on the type being queried—in the preceding example, Contact . You won't be able to do projection or filtering onCustomer properties.
12.2.9. Creating New Derived Entities When the Base Entity Already Exists What if you have a contact that becomes a customer? This is an important business rule for BreakAway Geek Adventures, and one that TPT inheritance doesn't readily support. This isn't to say that the Entity Framework doesn't support this scenario, but TPT by definition doesn't support it.
Let's look at what may seem like logical options using the Entity Framework, and why they won't work. The counterpoints provide a lot of insight into the workings of Object Services:
Add a new Customer object As you have seen, adding a newCustomer object will cause a newContact to be created in the database. Therefore, you can't just add a new customer for an existing contact.
Create a new Customer and populate its ContactID with the ContactID of the Contact If the Contact is not in the context, the Entity Framework will still see this as a newCustomer and will try to add theContact to the database.
Get the Contact into the context and add a newCustomer with the same ContactID Both the Contact and the Customer are members of the Contacts entity set. You will not be able to add theCustomer to the context because a member of the Contacts entity set with the sameEntityKey already exists in the context.
Detach the Contact from the context, set Customer.EntityKey=Contact.EntityKey and Customer.ContactID=Contact.ContactID, detach the Contact from the context and attach the Customer instead, and then callSaveChanges You would be getting closer to a solution with this. However, the Customer will be seen as having no changes, and therefore nothing will happen when SaveChanges is called. If you do something to make theCustomer "modified," the database command that results will be to update a nonexistent Customer record, and that too would fail.
Use Entity SQL's TREAT operator to "upcast" the Contact to a Customer type Unfortunately, this won't work either. The Entity Framework cannot cast from one type to another.
NOTE In addition to OFTYPE and IS [NOT] OF, which you saw earlier, theTREAT operator is another operator you can use in Entity SQL queries for type inspection. Although you may want to continue banging your head against the wall with creative hacks, the reality is that TPT inheritance does not support this scenario, and even with all of the other benefits that came along with having Customer inherit from Contact, this is a big problem.
Locked into a Corner with Inheritance? Early in the classic programming book Design Patterns (Addison-Wesley Professional), authors Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides discuss inheritance versus composition and conclude that one should "favor composition over inheritance." Composition uses building blocks. This would mean changing the model so that the FirstName and LastName properties of Contact would be accessed from the Customer type using Customer.Contact.FirstName . Inheritance is definitely more convenient for many reasons, but it also has its drawbacks. As you can see with TPT inheritance, the derived type is completely bound to the base type and there is no way to separate the two. One example of a drawback is the inability to delete a Customer entity without also deleting its Contact . In the BreakAway business, it could be necessary to be able to do that.
Having Customer inherit from Contact is something you should consider prior to designing your EDM. TPT inheritance may be perfect for your business model; it may create some rare annoyances; or it may not be the right way to go at all. These are decisions you'll need to make. Given the existing model, the best way to create aCustomer for an existing Contact is to use a stored procedure. Not a stored procedure that is wired up to the Customer entity through mappings, but a separate one that can be called explicitly from code. This will allow you to have your cake (the convenience of the derived type) and eat it too (perform functions that TPT inheritance does not support). We will discuss stored procedures in the next chapter, and at that time you'll see how to leverage the EDM's flexibility to solve this problem.
12.2.10. TPT with Abstract Types In the current inheritance model, the base type,Contact , is instantiated for some entities, andCustomer is instantiated for others. It is possible to have base types that are abstract , which means they are there to help define the structure of entities that derive from them, but they will never be instantiated. If you turned Contact into an abstract type, however, a few hundred contacts (those that are not customers) will never appear in your application because they won't have an instantiated type to map to. You would have no way to access contacts who are not customers. To solve this you need to create derived entities to represent other types of contacts. What would a derived type that accesses the non-customer contacts look like? Let's modify the model to see: 1.
Open the model in the Designer and select theContact entity.
2.
In the Properties window, change the value of the Abstract property to true. You will receive a warning saying that existing function mappings will be removed. Since you haven't mapped any stored procedures to the Contact entity, this isn't a problem.
3.
Click OK. Now Contact is an abstract type.
4.
Run the TPTMap method again.
5.
When the breakpoint is hit, debug the results of theContact query and you will see that only customers are returned. The entire set of data for contacts who are not customers is missing.
Now it's time to create the new derived type: 1.
In the EDM Designer, create a new entity and name itNonCustomer.
2.
Select Contact from the "Base type" drop-down list. The other fields in the Add Entity window will become disabled since they don't pertain to a derived type.
3. Click OK. That's it. Because there are no additional fields in this new entity, there's no need to do any mapping.
If you were to look in the raw XML of the EDMX file, the only instance ofNonCustomer you will find in the XML (other than the Designer information) is this element in the CSDL:
NOTE If any fields in the Contact entity were relevant to a noncustomer but were not relevant to a customer, you could move them over to the new entity. That scenario would require additional mapping. But in this case, everything you need for NonCustomer is already provided by the Contact abstract type. 1.
Run the application again and check out theContact query results in the debugger when you hit the breakpoint. All of the additional contacts are back as NonCustomers.
Remember that because you queried for just the Customers first and then all Contacts, the Customer s that were pulled into the cache on the first query will be up front. As you can see in Figure 12-7 , when looking at the results in the debugger, you'll need to scroll down past the Customer entities before you see the NonCustomer entities.
Figure 12-7. A query on the abstract type, Contact, showing all of the derived types included in the result
With the NonCustomer entity in the model, the model will present an error in the Error List that reads "Entity type 'NonCustomer' is not mapped." This is an erroneous message due to a bug in the validators. You can ignore it and it will not impact your ability to compile or run the application.
I cover additional types of inheritance that the EDM supports later in this chapter.
12.3. Using Entity Splitting to Map a Single Entity to More Than One Table Entity splitting, also referred to as vertical splitting , allows you to map a single entity to more than one table. You can use entity splitting when tables share a common key; for example, if a contact's personal and business information is stored in separate tables. You can use entity splitting as long as the primary keys in the two database tables match. The model contains an entity that we have thus far ignored:ContactPersonalInfo , which has a ContactID property (see Figure 12-8 for the database representation and Figure 12-9 for the entity). The purpose of the database table from which the entity was created is to provide additional information about customers that might be useful for BreakAway employees to be aware of when these customers participate in trips.
Figure 12-8. Two database tables that share a primary key and can be represented as a single entity
Figure 12-9. The ContactPersonalInfo entity
One way in which you can link this new entity to a customer is to create a 1:1 association betweenCustomers and ContactPersonalInfo using ContactID. That would make Customer a navigation property ofContactPersonalInfo and ContactPersonalInfo a navigation property of Customer . However, this wouldn't be very convenient, because you would always have to traverse the navigation to get to the properties—for example, Customer.ContactPersonalInfo.BirthDate. Wouldn't it be nice to just call Customer.BirthDate ? Entity splitting can solve this problem very easily, by mapping both theCustomer table and the ContactPersonalInfo table to the Customer entity.
12.3.1. Merging Multiple Entities into One Thanks to the Designer's copy-and-paste functionality, you can easily copy theContactPersonalInfo properties into the Customer entity. Once you have done that, all that's left is to map the Customer entity's new properties back to the appropriate table: 1. Copy and paste all but the ContactID properties from ContactPersonalInfo into the Customer entity. 2. Delete the ContactPersonalInfo entity from the model. 3. Open the table mappings for the Customer entity. 4.
At the bottom of the property mappings, select Add a Table or View, which will cause a drop-down arrow to display to the right of the column, as you can see in Figure 12-10.
5.
Click the drop-down arrow and choose ContactPersonalInfo from the list of available tables in the Store schema. All of the column mappings except ContactID should populate automatically.
6. Map the ContactID column to the ContactID property. 7. Save the model.
Figure 12-10. Mapping an entity to additional tables
12.3.2. Testing Entity Splitting Now you can test the revised entity. In the following exercise, you'll query the new entity, modify the returned object, create a new entity, and then save your changes. These actions will allow you to see how the Entity Framework handles an update and an insert
involving multiple tables. 1.
Add the method in Example 12-3 to the project's main code file (Module1.vb or program.cs).
Example 12-3. Querying for and modifying a type that maps to multiple tables
Private Sub EntitySplit() Using context = New BreakAwayEntities Dim firstCust = (From c In context.Contacts.OfType(Of Customer) _ Select c).FirstOrDefault firstCust.BirthDate = New Date("1981", "1", "24") Dim newcust = Customer.CreateCustomer(0, "Nola", "Claire", Now, Now) With newcust .HeightInches = 68 .WeightPounds = 138 .DietaryRestrictions = "Vegetarian" End With context.AddToContacts(newcust) context.SaveChanges() End Using End Sub
private static void EntitySplit() { using (var context = new BAEntities()) { //query for a customer and modify a new property var firstCust = ( from c in context.Contacts.OfType() select c).FirstOrDefault(); firstCust.BirthDate = new System.DateTime(1981, 1, 24); //create a new customer var newcust = Customer.CreateCustomer (0, "Nola", "Claire",DateTime.Now,DateTime. Now); newcust.HeightInches = 68; newcust.WeightPounds = 138; newcust.DietaryRestrictions = "Vegetarian"; context.AddToContacts(newcust); //save modified customer and new customer to db context.SaveChanges(); } }
2.
Add code to call EntitySplit in the Main method.
3.
Set a breakpoint at the line that instantiates thenewcust variable.
4. Run the project. When the process stops at the breakpoint, debug thefirstCust variable and you can see in the QuickWatch window that the new properties of Customer are populated, as shown inFigure 12-11.
Figure 12-11. Inspecting the queried Customer in one of the QuickWatch windows
A quick check in SQL Profiler shows that when querying for the first customer, an inner join was used to include the values from the ContactPersonalInfo table. The SQL Profiler screenshot in Figure 12-12 shows the commands that are executed when editing aCustomer and when adding a new Customer. The first two commands update theModifiedDate field in Contact and the BirthDate field in ContactPersonalInfo for the first Customer that was queried and edited. The newly addedCustomer results in the creation of aContact, a ContactPersonalInfo record, and finally, a new row in the Customers table.
Figure 12-12. A screenshot from SQL Profiler showing the commands that are executed when editing a Customer and when adding a new Customer
The first insertion occurs because of the inheritance you created betweenCustomer and Contact , but the insertion to the ContactPersonalInfo table occurs thanks to the entity splitting you just defined in the model. The Entity Framework is able to work out this customization in the model and translate it into the correct commands in the database without the developer having to worry about modification operations or about the fact that a number of tables are involved in the query.
12.4. Using Conditional Mapping to Filter Entity Mappings The next area of customization to cover is conditional mapping. You can use conditional mapping directly when mapping an entity to the data store, or in inheritance scenarios. We'll look at the first situation in this section and the second situation later in the chapter. Conditional mapping places a permanent filter on an entity by defining that an entity will be mapped to data in the database under only certain conditions. Therefore, if you have a scenario in which you will need to filter data 100% of the time on a particular value, rather than having to add this filter to every single query you can define it as part of the mapping. Figure 12-13 depicts the concept of conditional mapping to ensure that only water-related activities are ever used in the application.
Figure 12-13. Conditional mapping, which provides a permanent filter on an entity
As an example, imagine that BreakAway Geek Adventures decides that from now on it will provide only water-related activities. However, it does not want to delete historical data from the database. The company can use conditional mapping to ensure that anytime activities are requested only water-related activities are brought into the application, and that anytime a new activity is created it will automatically be defined as a water activity. As another example, rather than filtering by activity type, you can introduce a Boolean field named Discontinued into the Activities table in the database. Then in the conditional mapping, you can create a filter that allows only activities to be returned from the database when Discontinued=0 or False. It is possible to use conditional mapping in the following ways: [value] Is Null [value] Is Not Null [integer value] (e.g., 1) [string value] (e.g., Water) The Designer supports conditional mapping, but in the Designer, you do not use the quotations around the integer or the string. In the XML, those values will be surrounded by quotations. The Activity entity does contain a Category property that is a string. In the following section, we will walk through the first scenario: working solely with activities whose category is "Water".
12.4.1. Creating a Conditional Mapping for the Activity Entity
The changes made to the model in this walkthrough will not be used going forward. At the end of the walkthrough, you will be instructed to undo this mapping.
You must remove from the entity's scalar properties whatever property you will be using for a conditional mapping: 1.
Select the Activity entity.
2.
Delete the Category property from the entity.
3.
Open its Mapping Details window.
4.
Click , and then click the drop-down arrow that appears.
5.
Select Category from the drop-down list, as shown in Figure 12-14.
6.
In this mapping, use the default operator (=) for the value comparison.
7.
Under Value/Property, type Water. Figure 12-15 shows what the settings should look like when you are finished.
Figure 12-14. Adding a conditional mapping
Figure 12-15. Adding a conditional mapping to the Activity entry indicating that only rows whose Category value is equal to Water should be returned when querying against this entity
Single Mappings Only, Please You can map a field in a table only once. Therefore, you can have either a mapping to a property or a conditional mapping, but not both. The model validation will be happy to let you know when you have broken this rule.
12.4.1.1. The Is Null/Not Null conditions If you wanted the condition to test for null values, you can change the operator by using the drop-down and selecting Is. When you set the operator to Is, Value/Property becomes a drop-down with the options Null and Not Null, as shown in Figure 12-16.
Figure 12-16. Changing the condition operator to Is, which turns Value/Property into a drop-down list with the options Not Null and Null
12.4.2. Testing the Conditional Mapping You'll see with the following exercise that the condition not only filters data coming from the database, but also impacts data going into the database. 1. Add to the test module the method shown inExample 12-4.
Example 12-4. Querying, creating, and saving conditionally mapped entities
Private Sub ConditionalMap() Using context = New BreakAwayEntities Dim query = From a In context.Activities Select a Dim activities = query.ToList Dim newAct = New Activity With newAct .ActivityName = "WindSurfing" End With context.AddToActivities(newAct) context.SaveChanges() End Using End Sub
private static void ConditionalMap() { using (var context = new BAEntities()) {
var query = from a in context.Activities select a; var activities = query.ToList(); var newAct = new Activity(); newAct.ActivityName = "WindSurfing"; context.AddToActivities(newAct); context.SaveChanges(); } }
2.
Call the ConditionalMap method from the module's Main method.
3.
Comment out the call to the EntitySplit method.
4.
Set a breakpoint on the code afterquery.ToList is called.
5.
Run the application.
When you hit the breakpoint, look at theactivities variable in the QuickWatch window. You will see that only activities in the Water category were retrieved.
The insert is even more interesting. Although the only property you set in code was theActivityName , look at the T-SQL that was generated and you will see that Water was inserted into the Category field: exec sp_executesql N'insert [dbo].[Activities]([Activity], [imagepath], [Category]) values (@0, null, @1) select [ActivityID] from [dbo].[Activities] where @@ROWCOUNT > 0 and [ActivityID] = scope_identity()', N'@0 nchar(50),@1 nchar(10)',@0=N'WindSurfing ',@1=N'Water
'
The condition was automatically used in the insert. The condition that allActivity entities should have a category of "Water" also means that any newly created Activity entities will also have a category of "Water".
12.4.2.1. Filtering on other types of conditions What if you wanted to include any activity except water-related activities? Unfortunately, it is not possible to map this directly in the model. There is no operator for "not equals" and it is not possible to map a table column more than once. What you see in the Designer—an equals sign combined with an integer or string, Is Null, and Is Not Null —is the full extent of what the model is capable of. This also means that in conditional mapping, you can't use operators such as greater than (>) or less than (< ), or filter on other types such as a date. However, deeper in the model there is still a way to achieve this, using a mapping element called QueryView. We will discuss QueryView in more detail in the next chapter. If it's an option, you may need to resort to adding a new column, such asWaterActivity or DiscontinuedActivity , into the database table. Then you can easily create a conditional mapping on the Boolean field.
12.4.2.2. Removing the conditional mapping from Activity and re-creating the Category property You may not want to have this conditional mapping in place going forward, so feel free to remove it. You'll need to add the Category property back into the Activity entity and map it to theCategory field in the Activities table. 1.
Click the When Category mapping in the Mapping Details window.
2.
Select from the drop-down list.
3.
Right-click the Activity entity in the Designer, and choose Add and then Scalar Property from the context menu.
4.
Rename the property to Category.
5.
Return to the Mapping Details window and map theCategory field of the Activities table to the Category property, as shown in Figure 12-17.
Figure 12-17. Mapping the Category field of the Activities table to the Category property
12.5. Implementing Table per Hierarchy Inheritance for Tables That Contain Multiple Types Another type of inheritance that the EDM supports is Table per Hierarchy (TPH). TPH inheritance depends on conditional mapping. Rather than including only records that match the condition, the condition is used to define records as different types. Figure 12-18 displays the Lodging table that uses the Boolean,Resort , to define lodgings that are resorts. You can use this Boolean to create a new type in your model, Resort , which will inherit fromLodging . This is very different from the tables that provided for TPT inheritance where the properties of the derived type were defined in a separate table.
Figure 12-18. The Resort column of the Lodging table, which suggests a new inherited type, Resort
As you'll see in the following walkthrough, TPH mapping uses conditional mapping to help determine which data describes a lodging that is not a resort and which data describes a resort.
12.5.1. Creating the Resort Derived Type The BreakAway Lodging entity has a Boolean property called Resort. Let's use this property to defineResort as a new type of lodging: 1. Right-click the background of the Designer. 2. From the context menu, choose Add and then Entity. 3. Change the entity name to Resort. 4. Select Lodging from the "Base type" drop-down.
NOTE
Notice that the EntitySet automatically becomes Lodgings and is disabled so that you cannot modify it. Since
Resort will inherit from Lodging, it will be part of theLodgings EntitySet. Notice also that the section for the Key property has become disabled. The Lodging entity will still control the entity key, even for derived types. Now that you have the new type defined, how will the Entity Framework decide which Lodging records go into the Lodging entity and which go into theResort entity? The answer is conditional mapping. First, we'll use conditional mapping to filterLodging records into the base or derived type: 1. Delete the Resort property from the Lodging entity. NOTE As you learned when creating the conditional mapping earlier, you can't map a table column more than once. Since you will be using the Resort property for conditional mapping, you can't use it in the property mapping. Therefore, there is no need for the Resort property. 1.
Open the Mapping Details window for theLodging entity and click .
2.
Select Resort from the Condition drop down and change the condition value to 0. This condition states that records that are filtered into the Lodging entity will be records whose Resort property equals 0 or False.
3. Select the Resort entity and open its Mapping Details window. 4.
Map the entity to the Lodging table. Then create a condition forResort = 1 (or True).
NOTE The ContactID and LocationID are foreign keys in theLodging table. The navigation property/association combinations in the Lodging entity take care of them. Next, we'll move resort-specific properties to the Resort entity type: 1. The ResortChainOwner and LuxuryResort properties don't make sense in theLodging entity. They belong in the Resort entity. So, cut and paste these two properties from the Lodging entity into the Resort entity. 2. Open the Mapping Details window for Resort, and map theResortChainOwner and LuxuryResort properties to the appropriate columns in the Lodging table. When you're done, the Lodging and Resort types should look as they do inFigure 12-19.
Figure 12-19. A conditional mapping used to determine which rows from the Lodging table belong in Lodging or its derived entity, Resort
12.5.2. Setting a Default Value on the Table Schema If you try to run code against Lodging at this point, you will encounter a problem. TheLuxuryResort field is a Boolean field. In the database, it is non-nullable and has a default value of 0. The EDM Wizard does not bring the default value over to the model's Store Schema Definition Layer (SSDL). This creates a problem for theLodging entity. The
Lodging entity maps to theLodging table but does not map theLuxuryResort or ResortChainOwner column because we removed the properties from the Lodging entity. Only the Resort entity maps those fields. Because Lodging does not map those fields, the model will throw a runtime exception telling you that Lodging doesn't know how to deal with LuxuryResort because it is non-nullable and has no default value. Therefore, it wants to populate it. But because the properties don't exist in Lodging , the field is not mapped, and therefore theLodging entity is unable to modify the value. The only way to correct this is to add theStoreGeneratedPattern attribute manually into the SSDL to let the Entity Framework know that the database will take care of this value. This is especially important if you are creating new Lodging entities and saving them back to the database.
Remember that if you run the Update Model from Database Wizard, this manual modification to the SSDL will be lost and you will need to add it back in manually again.
1.
Open the model in the XML Editor.
2. Search for LuxuryResort as a quick way to find theLodging table. 3.
Verify that you are in the SSDL section of the model. You can tell by the property types, which will be database types, such as int, nchar , and bit.
4. Add StoreGeneratedPattern="Computed" to the LuxuryResort property, as shown inExample 12-5.
Example 12-5. Adding the StoreGeneratedPattern attribute for an unmapped non-nullable field
12.5.3. Testing the TPH Mapping The following method will help you see the effect of the TPH mapping. You can query for all lodgings, including any derived types, or for a specific derived type. It's a little trickier to query for a subset that is not a derived type. The following queries are executed in unique contexts so that entities that are a result of one query do not merge with entities of another query. In this way, you can more easily see the full impact of each of the various queries: 1. Add the method in Example 12-6 to the test module.
Example 12-6. Querying types in a TPH mapping
Private Sub TPHMap() Using context = New BreakAwayEntities Dim query = From lodge In context.Lodgings Console.WriteLine("All Lodgings Results: " & query.Count.ToString) End Using Using context = New BreakAwayEntities Dim query = From lodge In context.Lodgings.OfType(Of Lodging)() Console.WriteLine("NonResort Only Results: " & query.Count.ToString) End Using Using context = New BreakAwayEntities Dim query = From lodge In context.Lodgings.OfType(Of Resort)() Console.WriteLine("Resort Only Results: " & query.Count.ToString) End Using End Sub
private static void TPHMap() { using (var context = new BAEntities())
{ var query = from lodge in context.Lodgings select lodge; Console.WriteLine("All Lodgings: " + query.Count().ToString()); } using (var context = new BAEntities()) { var query = from lodge in context.Lodgings.OfType() select lodge; Console.WriteLine("NonResort Results: " + query.Count().ToString()); } using (var context = new BAEntities()) { var query = from lodge in context.Lodgings.OfType() select lodge; Console.WriteLine("Resort Results: " + query.Count().ToString()); } }
2. Call the TPHMap method from the module'sMain method. 3. Run the application. When you see the output of the console window, you may be surprised that the second query, which you may have expected to return only nonresort lodgings, returned all of the lodgings, regardless of the Resort filter:
All Lodgings Results: 101 NonResort Type Only Results: 101 Resort Type Only Results: 10
Why is this? Even though you put a condition on Lodging that states Resort=0 (false ), Lodging is a base type. No matter what,
Lodging will return itself and all types that derive from it. With a simple query it is not easy to say "give me the base type but none of its derived types." So, even though the condition is there, you'll continue to receive all of the Lodgings, even with Resort=1.
12.5.4. Abstract Entity Types If you want an easy way to retrieve nonresort lodgings, you can create a second derived type that inherits from Lodging to retrieve all of the Lodging entities that are not resorts. In this case, the actualLodging entity would become an abstract type because it will never be instantiated. The Lodging entity itself cannot be instantiated and will never return
Lodging entities. Instead, the Lodgings EntitySet will return only those entities that come from its derived types:Resort and NonResort .
Although you performed this task when creating theNonCustomer entity, the following walkthrough will act as a reminder to show you how to turn Lodging into an abstract type and then let you see how the abstract and derived types behave in code: 1. In the Designer, create another new entity type, name it NonResort , and set itsBaseType to Lodging. 2. Open the Mapping Details window for the NonResort entity. 3. Map the NonResort entity to the Lodging table. Because no fields are specific to this type, you won't need to do any field-to-property mapping. 4. Create a conditional mapping for the Resort property using the = operator and the value0 for the comparison. 5. Validate the model using the Designer's context menu. You will see two errors regarding overlapping partitions. This is because theLodging entity still has the conditional mapping that matches the conditional mapping you just created for NonResort . Because all of the records are now covered by the conditions in NonResort and Resort , you can turn theLodging entity into an abstract type and remove the conditional mapping. 6.
Open the Mapping Details window forLodging.
7.
Delete the conditional mapping. Remember that you can do this by selecting from the drop-down.
8. In the Properties window for the Lodging entity change the value of Abstract to True. You will get a warning that all function mappings will be removed. This refers to stored procedures, but there are none for this type, and therefore it's not a problem. 9. Rebuild the model project. 10.
Open the code module and modify the second query so that it gets NonResort types, as shown in the following code:
From lodge In context.Lodgings.OfType(Of NonResort)()
from lodge in context.Lodgings.OfType() select lodge
1. Run the application again. This time you can see that the derivedNonResort type makes it simpler to get that group as a whole:
All Lodgings Results: 101 NonResort Type Only Results: 91
Resort Type Only Results: 10
12.5.5. Which of These Mappings Is Better? You just saw a demonstration of how TPH inheritance works. If your business rules define that you would never want to get the entire set of types (e.g., all of the lodgings at once), it makes sense to have the abstract class in the model and to use the derived types to interact with the objects. If your business rules define that in many cases you will want to work with all lodgings, regardless of type, using the base type without defining it as an abstract class may be preferable.
12.6. Implementing Customizations That Are Not Supported by the EDMDesigner A number of mappings are not supported by the Designer. We will cover some of these in the rest of the chapter. How this impacts your work depends on which unsupported customization you are using. Non-supported features can affect the use of the Designer in the following ways: The feature does not appear in the Designer. The Designer goes into Safe Mode when you attempt to open the model in the Designer. Safe Mode presents a message that indicates the model cannot be opened in the Designer, and displays a link to open the model in XML view. The Mapping Designer goes into Safe Mode, but the CSDL Designer displays. An error is thrown when you attempt to open the model in the Designer. As we walk through the following mappings, I will indicate how each mapping is handled (or not handled) by the Designer.
12.7. Mapping Table per Concrete (TPC) Type Inheritance for Tables with Overlapping Fields Another scenario where you can use inheritance mapping is when you have database tables with overlapping fields. A classic example of this appears in Figure 12-20 , where a copy of theReservations table was created to store old reservations that are rarely accessed.
Figure 12-20. Reservations split into two tables in the database
The inheritance implementation used for this mapping is calledTable per Concrete Type or TPC inheritance. You can define the inheritance between the two in the Designer, but you will have to manually map the OldReservations entity to its table in the XML.
NOTE The OldReservations table does not exist in the BreakAway database. This example is not meant to be implemented in your sample model. To create the inheritance, you will need to remove all of the overlapping properties from the derived entity. In this case, that means every property. Figure 12-21 displays what the inheritance looks like in the EDM Designer.
Figure 12-21. Base and derived entities in TPC inheritance mapping
You'll find that none of the OldReservations table fields are mapped after you make these modifications. You can map the ReservationID field to the ReservationID property, but the rest must be mapped in the XML of the EDMX file. Example 12-7 shows the mapping. TheReservation EntityTypeMapping contains a mapping for theReservation entity and another mapping for the derived OldReservationEntity .
Example 12-7. TPC mapping
With this mapping, you would be able to work with theOldReservations table when you need to. Also with this mapping, you will get the OldReservations anytime you query forReservation without specifically excluding them. Therefore, you may want to consider turning Reservation into an abstract type and creating another entity to represent current reservations as you did to solve a similar problem with Lodging entities that are not resorts. Although you can't see the mapping in the Designer, you will still be able to use the model in the Designer when TPC is implemented.
12.8. Creating Complex Types to Encapsulate Sets of Properties Complex types are a very convenient way of encapsulating a set of properties. You may want to do this when you have properties that are common among entities (e.g., different entities that have properties to contain addresses). You may just want to use a complex type to create a better structure in your entity. Imagine that in your model you had a Contact entity that contained address properties. You may prefer to navigate through the contact with the address fields tucked inside a complex type. Therefore, rather than having all of this to deal with when programming: Customer FirstName LastName Street City State Zip Phone
you could encapsulate those properties related to the address into a complex type called Address , and then insert Address as a property into the Customer type: Customer FirstName LastName Address Phone
Then, to get at the address information, you can drill further: Customer.Address.City Customer.Address.State
What's really nice is that the complex types are still types, so you can instantiate them and use them outside their parent entity. However, complex types are not but ComplexObjects. They don't have EntityKey s and are not contained in their own EntitySet ; therefore, they cannot be queried for directly or persisted into
EntityObject s,
the database.
12.8.1. Complex Types and the EDM Designer Complex types are one of the mapping types that the Designer does not support, but you can still create them manually. As mentioned at the start of this chapter, "not supported by the Designer" can mean different things. In this case, when a complex type is defined in the model, you will not be able to open the model in the Designer. When you try to open it in the Designer, you will get the Designer's "Safe Mode" display that says the Designer is unable to display the file, but provides a link to open the model in the XML Editor.
NOTE The Visual Studio version of the EDM Designer will fully support complex types. This will be highly inconvenient if you are still in the process of designing your model, but otherwise it should not be a showstopper if you find that you will get a lot of benefit from using complex types.
12.8.2. Defining a Complex Type You can create the new complex type in the XML Editor by copying the properties from the entity that originally contains them and pasting them into a ComplexType element. ComplexType elements are siblings of EntityType elements.
Because we have a lot more work to do in the Designer after this, you'll need to unwind these changes at the end of this walkthrough. You will be guided to comment out XML rather than deleting it while you modify the model. At the end of the walkthrough, you will be able to remove the changes and uncomment the original XML.
As an example, you can create an AddressDetail type that encapsulates the specific properties of the Address entity that are part of the mailing address, leaving the AddressType and
modified date as scalar properties of Address.
The new complex type would look like Example 12-8.
Example 12-8. A ComplexType defined in the CSDL
As shown in Figure 12-22, the ComplexType element is positioned in the CSDL section as a sibling of the EntityType s. It is not critical where it is placed relative to other entities.
Figure 12-22. The ComplexType element positioned as a sibling of the EntityTypes
12.8.3. Replacing Properties with a Complex Type In the Address EntityType , you can now replace those properties with a single property to represent the AddressDetails , as shown in Example 12-9. Note the new Detail property in the address and entities.
Example 12-9. Using a complex type in an entity type
NOTE Rather than deleting the properties that are being replaced by the complex type, comment them out in the XML. That way, you can easily undo these changes and open the model in the Designer as you continue to progress through the book.
12.8.4. Mapping Entities with Complex Types The required change to the mappings is not as complicated as you might think. All you need to do is wrap those properties inside a ComplexProperty tag, as shown in Example 12-10 . Note that the name of the ComplexProperty element, Detail , matches the name used for the property in the entities, and that the TypeName attribute points directly to the complex types.
Example 12-10. Mapping the complex type
yyy
If you are using AddressDetail inside other entities, you will need to map the properties in EntitySetMapping for that entity. Rebuild the model project so that the projects using it see the changes.
12.8.5. Complex Types Are Not EntityObjects Looking at the generated class for AddressDetail you will see that it is not an EntityObject , but rather a ComplexObject:
Partial Public Class AddressDetail Inherits Global.System.Data.Objects.DataClasses.ComplexObject
public partial class AddressDetail : global::System.Data.Objects.DataClasses.ComplexObject
Although you can instantiate and use these types directly in code, they do not have EntityKey s, cannot be queried for directly, and cannot be persisted to the database. ComplexObject
does allow the properties of the ComplexType to be change-tracked along with the other properties of its parent entity, though. You can look further at
the generated class and even drill into the System.Data.Objects.DataClasses.ComplexObject class in Visual Studio's Object Browser or in another tool such as Reflector.
12.8.6. Using Complex Types The method in Example 12-11 shows the ComplexType in action.
Example 12-11. Querying, creating, and saving entities that contain a complex type
Private Sub ComplexType() Using context As New BreakAwayEntities Dim contact = (From c In context.Contacts.Include("Addresses") _ Take 1).FirstOrDefault Dim addDetail as AddressDetail = contact.Addresses(0).Detail Console.WriteLine("Street: {0}, City: {1}, State: {2}", _ addDetail.Street1, addDetail.City, _ addDetail.StateProvince) Dim newAD = New AddressDetail With newAD .Street1 = "1 Rue Cardinale" .City = "Montreal" .StateProvince = "Quebec" End With contact.Addresses(0).Detail = newAD context.SaveChanges() End Using End Sub
private void ComplexType() { using (var context = new BreakAwayEntities()) { var contact = (from c in context.Contacts.Include("Addresses") select c).Take(1).FirstOrDefault(); AddressDetail addDetail = contact.Addresses[0].Detail; Console.WriteLine("Street: {0}, City: {1}, State: {2}", addDetail.Street1, addDetail.City, addDetail.StateProvince); var newAD = new AddressDetail(); newAD.Street1 = "1 Rue Cardinale"; newAD.City = "Montreal"; newAD.StateProvince = "Quebec"; contact.Addresses(0).Detail = newAD; context.SaveChanges(); } }
This method first queries the model for a single Contact entity, along with its addresses. It then extracts the AddressDetail from the first address and displays some of its properties, demonstrating that you can create an instance of the complex type. Next, it instantiates a new AddressDetail, and sets that instance as the Detail property of the first address. Finally, SaveChanges is called, which updates the address information for the contact. Here is the T-SQL that was executed on the server. You can see that the change tracking does take into account the property values of the complex type: exec sp_executesql N'update [dbo].[Address] set [Street1] = @0, [Street2] = null, [City] = @1, [StateProvince] = @2, [CountryRegion] = null, [PostalCode] = null where ([addressID] = @3)', N'@0 nchar(50),@1 nchar(50),@2 nchar(50),@3 int',@0=N'1 Rue Cardinale',
@1=N'Montreal',@2=N'Quebec',@3=2513
NOTE Trailing blanks have been removed from the SQL statement for readability.
12.8.7. Complex Types in Data-Binding Scenarios The complex type may not behave the way you would expect it to in data binding. Therefore, the next few pages will take a look at a number of data-binding scenarios.
12.8.7.1. ASP.NET EntityDataSource When you use complex types with the EntityDataSource, the EntityDataSource "flattens" the properties within the complex type to make them easily accessible. When configuring the EntityDataSource , you will see the type, but not the properties, as you can see in Figure 12-23 . However, when binding controls to the data source, the properties of the complex type appear as though they were simply properties of the parent type. You can see this in the screenshot in Figure 12-24.
Figure 12-23. The complex type surfaced by the EntityDataSource
Figure 12-24. The complex type properties automatically flattened
This flattening of the properties is a feature of the EntityDataSource , though it will occur only under specific conditions. For details, see the blog post "EntityDataSource: To wrap or not to wrap" by Diego Vega, EntityDataSource program manager at Microsoft (http://blogs.msdn.com/diego/archive/2008/05/13/entitydatasource-to-wrap-or-not-to-wrap.aspx/).
12.8.8. Data Binding Complex Types in ASP.NET Without the EntityDataSource When you attempt to perform data binding against query results where complex types are involved and DataSource controls are not, you won't have such easy access to the properties. For example, the following code in an ASP.NET page will fail, with a message saying that Address does not contain a property with the name Detail.City:
Dim addresses = context.Addresses.ToList With DropDownList1 .DataTextField = "Detail.City" .DataValueField = "addressID" .DataSource = addresses .DataBind() End With
var addresses = context.Addresses.ToList(); DropDownList1.DataTextField = "Detail.City"; DropDownList1.DataValueField = "addressID"; DropDownList1.DataSource = addresses; DropDownList1.DataBind();
Attempting a similar binding to a ComboBox in a Windows form will have a different effect. In the following code, the addressID will be displayed in the drop-down list, rather than the ComplexType property that is used for DisplayMember:
Dim addresses = context.Addresses With ComboBox1 .DataSource = addresses .DisplayMember = "Detail.City" .ValueMember = "addressID" End With
var addresses = context.Addresses; ComboBox1.DataSource = addresses; ComboBox1.DisplayMember = "Detail.City"; ComboBox1.ValueMember = "addressID";
Yet, if you were to debug into the results of the query and request the properties from the complex type, you would see that they are definitely available, just not for these data-binding scenarios. In a Windows form, if you bound the results of a query programmatically, such as in the following code:
Using context = New BAGA.BreakAwayModel.BreakAwayEntities Dim addresses = From a In context.Addresses Select a Me.DataGridView1.DataSource = addresses End Using
using (var context = new BAGA.BreakAwayModel.BreakAwayEntities()) { var addresses = from a in context.Addresses select a; this.DataGridView1.DataSource = addresses; }
the Detail property would be represented incorrectly as a single column. You'll get the same effect even if you create a Windows Forms DataSource and bind to that. Even if you explicitly bind properties to the columns in this way:
DataGridView1.Columns(1).DataPropertyName = "Detail.Street1"
DataGridView1.Columns[1].DataPropertyName = "Detail.Street1";
the binding will fail, with the columns that result being empty. So, how can you get at these properties in these scenarios?
12.8.8.1. Complex types with ASP.NET binding controls With ASP.NET, you have three paths to follow: list controls, data-bound controls, and templated controls. With each, you will need to take a different route for using a complex type.
12.8.8.1.1. List controls DropDownList
is not actually a data-bound control. It is a list web server control. Other controls in the category are ListBox, CheckBoxList , RadioButtonList , and BulletedList.
Instead of returning the objects that contain complex types (which can't be displayed), your best bet is to use projections to flatten the properties yourself. As an example, here is a LINQ query that returns a list of distinct cities. You can bind this to a drop-down list and, upon selection, query for contacts from the selected city:
Dim uniqueCities = From a In context.Addresses _ Select City = a.Detail.City Distinct With DropDownList1 .DataSource = uniqueCities .DataBind() End With
var uniqueCities = (from a in context.Addresses select City == a.Detail.City) .Distinct(); DropDownList1.DataSource = uniqueCities; DropDownList1.DataBind();
12.8.8.1.2. Data-bound controls GridView
and FormView are bound controls and have the same limitation as list controls. If you are not able to use the EntityDataSource, you will need to do projection to
flatten the ComplexType properties. With projection, you lose your ability to do updating, so you may want to consider the EntityDataSource for this scenario. You will have more control over the entities using a business layer, which you will learn to do in Chapter 19.
12.8.8.1.3. Templated controls With templated controls, such as ListView , you can access the ComplexType properties using inline script. Reverting back to the query: context.Addresses
you can bind directly to the results with the following markup in a ListView (see Example 12-12).
Example 12-12. Formatting the markup of a ListView to display complex type properties
| |
12.8.9. Windows Forms DataSource and Complex Types Like the EntityDataSource , data sources in Windows Forms let you work with entities and their properties that are complex types fairly easily. Figure 12-25 shows an Object data source created from the revised Address entity.
Figure 12-25. Windows Forms data source reading complex type properties such as the Detail property of Address
You can use the complex type in a Windows form, which is displayed and updated along with the rest of the entity. You can see in the simple form shown in Figure 12-26 that the complex type properties blend in as though they were scalar properties of Address.
Figure 12-26. The AddressDetails complex type being used with a data source in a Windows form
The code for this form doesn't make any special accommodation for the Detail property (see Example 12-13).
Example 12-13. Querying for entities with a complex type—which is no different from entities without a complex type
Public Class Form1 Private _context As BAEntities
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load _context = New BAEntities Dim query = From a In _context.Addresses Select a
AddressBindingSource.DataSource = query End Sub
Private Sub AddressBindingNavigatorSaveItem_Click _ (ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles AddressBindingNavigatorSaveItem.Click _context.SaveChanges() End Sub
End Class
public partial class Form1 : Form { BAEntities _context; public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e) { _context = new BAEntities(); var query = from a in _context.Addresses select a; addressBindingSource.DataSource = query; }
private void addressBindingNavigatorSaveItem_Click (object sender, EventArgs e) { _context.SaveChanges(); } }
12.8.10. Removing the Complex Types from the Model If you have followed along and modified the model, you may want to undo these changes so that you'll be able to open the model while working through more sample code in this book: 1. Comment out or delete the ComplexType definition for AddressDetail. 2. Comment out or delete the Detail property in the Address entity type. 3. Uncomment the original properties in the Address entity type that you commented out when you began these modifications. 4. Comment out or delete the ComplexProperty elements in the EntitySetMapping for Addresses so that the ScalarProperty elements that you enclosed go back to their original positions. You should now be able to open the model in the Designer again. You may also need to remove or comment out any code that relates to the AddressDetail complex type. The compiler will point them out in the Error List window for you.
12.9. Using QueryView to Create Read-Only Entities and Other Specialized Mappings QueryView is a mapping that allows you to override the default mapping for an entity set and return read-only data.QueryView is something you need to enter manually in the XML, and it belongs in the mapping layer. A QueryView is a query that is expressed using Entity SQL syntax. However, rather than creating the Entity SQL expression against the conceptual layer of the model, the target of the expression is the store (SSDL) layer. In other words, when you construct the Entity SQL for a QueryView , the query is written against the elements of the SSDL.
Entities from QueryViews Don't Have to Be Read-Only Although QueryView returns read-only entities, if you want to useQueryView for some of its other benefits, you can force the entity to be updatable. Entities that are mapped with QueryView are still change-tracked by the ObjectContext . However, the Entity Framework is not able to automatically generateInsert, Update, and Delete commands for these entities. Instead, you can always create function mappings, as you did for the Payment entity. Then the entity that came from a QueryView will be affected by the call toSaveChanges.
In addition to returning read-only entities, another benefit of QueryView is that you can overcome the limitations of conditional mapping. As you saw earlier, conditional mapping lets you filter using =, Is Null, and Is Not Null. Using a QueryView you can filter with a much wider variety of operators, including > and = DATETIME'2007-01-1 00:00'
QVModelStoreContainer is the SSDL's EntityContainer name that the wizard generated automatically. Just as you need to use the model's EntityContainer name when constructing regular Entity SQL queries, you need to use the store's EntityContainer name with the Entity SQL expressions you create forQueryViews.
NOTE Notice that the WHERE parameter uses a DATETIME literal, which you learned about inChapter 4. What's really nice here is that the Designer is able to validate the syntax of the query, something you can't get when you write Entity SQL strings in your application. 6. To test that, remove the as c from the end of the query and build the project. The entire EntitySetMapping section will be underlined and in the Error List you will see the following error: The query view specified for the EntitySet 'Contact' is not valid. The query parser threw the following error : 'c.ContactID' could not be resolved in the current scope or context. Make sure that all referenced variables are in scope, that required schemas are loaded, and that namespaces are referenced correctly., near multipart identifier, line 1, column 30.
The cause of the error is that the c in c.ContactID can't be identified (i.e., resolved) because you removed the definition ofc.
NOTE In some cases, you may have to open the model in the Designer to highlight theQueryView errors. 7. Replace the as c and rebuild the project. The error message will go away.
A design-time validation bug will cause the entire model in XML view to be underlined as though there is a serious problem. This happens when you have a QueryView in the model and any real errors have been eliminated. The syntax is actually valid and you can ignore the error. The error message in the Error List will read "No mapping transformations were generated for any EntityContainerMapping." You should be able to disregard this and run your application with no problems.
12.9.2. Testing the QueryView In the main module of this project, add the following simple code to verify theQueryView:
Using context = New BAModel.BAEntities Dim contacts = context.Contacts.ToList End Using
using (var context = new BAModel.BAEntities()) { var contacts = context.Contacts.ToList(); }
Set a breakpoint at the end of theUsing clause and run the test. When the debugger stops at the breakpoint, check out thecontacts variable in the QuickWatch window. If you drill into the different contacts in the list, you will see only contacts whose AddDate is 1/1/2007 or later.
12.9.3. Deconstructing the QueryView The order of the projected columns in the preceding example is not random. Since you no longer have any property mappings, the Entity Framework relies on the QueryView to provide the values in the order in which the entity expects. The following expression is different from those that you have written against the conceptual layer: SELECT VALUE QVModel.Contact(c.ContactID,c.FirstName,c.LastName,c.Title c.AddDate,c.ModifiedDate,c.TimeStamp) FROM QVModelStoreContainer.Contact as c
Using VALUE designates that you will be returning an object, as you have seen before. Following that is a type constructor, similar to what you would use in .NET code. In fact, you can see this in action if you return to the XML and modify the query, perhaps by removing one of the fields or changing the order. Removing a field will throw an obvious mapping exception at runtime that reads as follows: The query view specified for the EntitySet 'Contacts' is not valid. The query parser threw the following error : The type constructor argument 'ModifiedDate' is missing., near type 'BAModel.Contact' constructor, line 1, column 29.
Remember that the returned types are read-only. If you modify them and callSaveChanges , no commands will be created to update the database. However, if you explicitly map stored procedures to the entity using function mapping, any changes will be persisted to the database when SaveChanges is called. The database has stored procedures called InsertContact, UpdateContact, and DeleteContact . If you want to try this out, update the model, adding these three stored procedures, and then create the function mappings as you did in Chapter 6 . You can modify the code in the main module to test the change tracking and updates.
12.9.4. QueryView with Inherited Types One of the rules noted earlier is that if you use aQueryView on an entity that is part of an inheritance hierarchy, you need to have QueryViews for all other entities in thathierarchy. Let's add some inheritance into this sample model and see how to set up theQueryView s for base and derived entities: 1.
Define a TPT inheritance betweenContact and Customer by right-clicking the Contact entity and selecting Add Inheritance from the context menu. Use the earlier example for help with this task. Don't forget to delete ContactID from Customer now that it is inheriting from Contact. You'll also need to either change the name of the TimeStamp property in Customer, or just delete it for this sample. Because these are the only two entities, the New Inheritance Wizard should default to showing Contact as the base entity and Customers as the derived entity. This is correct.
2.
Close the model and open it in the XML Editor.
3.
Scroll down to the spot where you added theQueryView before.
4. Comment out the entire EntityTypeMapping section that was created for theCustomer entity, including the EntityTypeMapping tags. 5. Beneath the existing QueryView, add the QueryView shown in Example 12-14 for the Customer type.
Example 12-14. The Customer QueryView
SELECT VALUE QVModel.Customer(c.ContactID,c.FirstName,c.LastName, c.Title,c.AddDate,c.ModifiedDate,c.TimeStamp, cu.CustomerTypeID,cu.InitialDate, cu.PrimaryDesintation, cu.SecondaryDestination,cu.PrimaryActivity, cu.SecondaryActivity,cu.Notes) FROM QVModelStoreContainer.Contact as c JOIN QVModelStoreContainer.Customers as cu ON c.ContactID=cu.ContactID
WHERE c.AddDate>= DATETIME'2007-01-1 00:00'
The hierarchy should now be as follows: (the contact query) = DATETIME'2007-01-1 00:00'
This query performs an outer join between Contact and Customer, which gives all Contacts regardless of whether there are any Customers. The CASE statement tests to see whether the Customer exists by evaluating whetherCustomer.ContactID is null. If it is null, the SELECT is completed with a type constructor for aContact type. This is the same expression that was used for the firstContact QueryView. If the test forCustomer is not null, the SELECT is completed with a type constructor for aCustomer type using the same expression you wrote for the Customer's QueryView. The query results, therefore, are a collection of Contact and Customer types.
12.9.5. Testing the New QueryView Now that you have modified the QueryView to account for the inheritance hierarchy, let's test it out with some code: 1. In the module, modify the query to return only Customer types:
Context.Contacts.OfType(Of BAModel.Customer).ToList
Context.Contacts.OfType.ToList()
2. Run the application again. 3.
When you hit the breakpoint, check out the results in the QuickWatch window and you will see that they contain Customer types, as shown in Figure 12-27.
Figure 12-27. The QueryView returning properly typed entities
12.10. Additional Customization Options There are yet more ways to customize the EDM. This section details some interesting ones to be aware of. In addition, the Entity Framework team created a tool called the Entity Framework Mapping Helper, which is on their Code Gallery site at MSDN. It can give you a good view of the various mappings. See http://code.msdn.microsoft.com/EFMappingHelper/.
12.10.1. Mapping Stored Procedures In addition to the function mapping you used earlier in the book, you can map stored procedures manually using a number of other methods. This includes mapping those that are already in your database and those that you can create directly in the model. We'll cover these in the next chapter.
12.10.2. Multiple Entity Sets per Type Multiple Entity Sets per Type (MEST) allows you to contain a single entity in different types, which could allow you to have different views of the same type without using an inheritance model. However, MEST gets tricky pretty quickly when you start to introduce entities that have relationships with other entities. Alex James from the Entity Framework team has a great blog post about MEST and its gotchas in his May 16, 2008, post, "MEST—What is it and how does it work?" (http://blogs.msdn.com/alexj/archive/2008/05/16/mest-what-is-it-and-how-does-it-work.aspx/).
12.10.3. Self-Referencing Associations You can find a great example of self-referencing associations in the Northwind database, where employees and their supervisors (who are also employees) are contained in the same table. A field called ReportsTo points back to other employees in the table. When you use the EDM Wizard to create a model from AdventureWorksLT, you will see that an association has been created that links the SupervisorID back to the EmployeeID in the same table.Figure 12-28 shows this association.
Figure 12-28. An example of a self-referencing association in the Employee entity, which is created from the Employees table in the Northwind database
12.11. Summary This chapter covered a lot of territory under the single topic of advanced EDMs. The real power of the EDM lies in its ability to go beyond the simplistic representation of the database to allow you myriad ways to shape your data model so that it is much better suited to your business and your applications. Although the Designer supports some of the advanced techniques, you can achieve even more by working directly with the XML. And although the modeling capabilities are very sophisticated, unfortunately the Designer still has some catching up to do in the next version of the Entity Framework, but there's no reason to miss out on the flexibility of the model. You can take advantage of these features in far more ways than I discussed here, so don't stop with this book. Keep your eyes open for blog posts and articles by the many people who are learning more and more about the Entity Framework to expand your understanding.
Chapter 13. Working with Stored Procedures When Function Mapping Won't Do In Chapter 6 , you learned about function mapping for stored procedures in the Entity Data Model (EDM). Mapping insert, update, and delete stored procedures to entities is the simplest way to use stored procedures in the EDM. But the scenarios in Chapter 6 cover only a narrow scope of the types of stored procedures many developers leverage in their applications. Although version 1 of the Entity Framework has plenty of support for stored procedures, the Designer doesn't support everything the model can do, which in some cases makes implementing stored procedures somewhat taxing. This chapter will cover a number of different ways to implement stored procedures beyond the function mapping you already performed in the Designer that overrides the SaveChanges behavior. These additional implementations will create functions that you can call directly in your code. In addition to implementing stored procedures from your database, you'll also learn how to create native stored procedures directly in your model. The first part of the chapter will focus on stored procedures that are used for querying the database. The latter part of the chapter will address stored procedures for performing inserts, updates, and deletes in your database.
13.1. Does the Procedure Line Up with an Entity? In previous chapters, you learned to use functions for database stored procedures—for example, theInsertPayment and
CustomersbyState functions, which line up with thePayment entity and Customer entity, respectively. But what about procedures whose columns do not precisely match a particular entity type, such as a query stored procedure whose results do not match an entity, or an update procedure that takes values from a number of entities? First we'll look at read stored procedures that fall into this category, and later in the chapter you'll see how to work with stored procedures that perform inserts, updates, and deletes, or even a combination of commands.
Are Stored Procedures Second-Class Citizens in the Entity Framework? It's important to not lose sight of the EDM and the Entity Framework's bigger benefits when thinking about stored procedures. Two of the Entity Framework's core features are the ability it gives you to compose queries, and the command generation it can perform for queries and updates. The Entity Framework is very good at both tasks, and it can do so regardless of the backend database. Another benefit, of course, is that it lets you use an EDM to describe your data. Yet stored procedures are a critical part of many organizations' databases. Although the Entity Framework supports the use of stored procedures in the EDM and API, those stored procedures are treated as functions. As you have learned in earlier chapters, some of these functions can be mapped to entities and used to override the SaveChanges behavior, but others can be called directly in your code.
13.2. Overview of Procedures, UDFs, and TVFs in the EDM The Entity Framework supports stored procedures and user-defined functions (UDFs). Version 1 of the Entity Framework does not provide support for table-valued functions (TVFs), but this is planned for the next release. You can map stored procedures to entities, as you have seen in previous chapters. But you will find that many stored procedures can't be mapped to entities. Instead, you will call their functions directly. You can call some of the functions as a method of the ObjectContext , but others you can call only withEntityClient (EntityConnection, EntityCommand, etc.). You can define UDFs in the store layer of your EDM, and the Entity Data Model Wizard and Update Model Wizard will pick them up. You can call UDFs only as part of an Entity SQL string, but not with LINQ. We'll look at UDFs at the end of this chapter.
13.3. Composing Queries Against Functions You can include functions in queries; however, only the UDF functions are truly composable. When the function is from a stored procedure only the procedure itself will be processed on the server side. The rest of the query is processed on the client side in memory. This is because in most databases, stored procedures are not composable. For example, if you have a stored procedure that returns all orders for a particular company, and you write a LINQ to Entities query adding an additional filter to it, such as the following:
From o in context.OrdersforaCustomer(12345) Where order.Total>10000 Select order
the stored procedure will execute on the database, returning all orders for the customer; then, in memory, LINQ will query all of those orders and return only the subset. This is not a limitation of the Entity Framework, but the nature of stored procedures. UDFs are composable, and therefore their EDM functions are composable as well.
13.4. Mapping and Executing Query Stored Procedures A number of different query (or read) stored procedures need special handling in the EDM. Those that return randomly shaped resultsets might be an obvious target, but there's also another scenario that is the result of your ability to customize the model. If you modify the property names of an entity, this can cause a conflict with stored procedures that return the fields that match those properties. In this section, we'll explore these scenarios and other query stored procedures that require special mapping in the EDM.
13.4.1. Using Functions That Match an Entity Whose Property Names Have Been Changed If the schema of the return type matches up exactly with an existing type in your model, you are a few clicks away from mapping the function. However, there is one caveat to this. The function truly expects an exact match. If you have changed property names in entities and they do not match column names being returned, the function will fail. One function in the model that demonstrates this problem isActivitiesonaTrip. Example 13-1 shows the database procedure for this function. Because of the SELECT *, the procedure returns all of the columns fromActivities.
Example 13-1. The ActivitiessonaTrip stored procedure
SELECT * FROM [Activities] WHERE activityid IN (SELECT ActivityID FROM [EventActivities] WHERE eventid=@tripid)
In the model, the Activity entity has a one-to-one mapping to theActivities table, so the fields and properties should line up exactly. The Activity entity has the same fields; or does it? The field names in theActivities table are ActivityID,
Activity, imagepath, and Category . You may recall that when changing the original entity name fromActivities to Activity , there was a conflict with the property namedActivity, so you changed the property name toActivityName . Even this minor change causes the function to fail when it attempts to match up the results of the returned data with the Activity entity. You'll be able to implement the mapping function in the model, but when you try to execute the function you will get this error:
The data reader is incompatible with the specified 'BAModel.Activity'. A member of the type, 'ActivityName', does not have a corresponding column in the data reader with the same name.
Because neither the model nor the Designer gives you an opportunity to define the mapping between the results and
Activity , you can't provide the necessary information to make this work.
NOTE The next version of the Entity Framework Design Tools, part of Visual Studio 2010, will have this capability.
13.4.1.1. How can you get around this problem? One solution to this problem takes advantage ofEntityClient. You can perform anEntityClient query to call the function. Remember that EntityClient does not materialize its results as objects. But you can read through the results and coerce them into the entity you want. This is the same solution you will be using later in this chapter when we look at solutions for using stored procedures that return scalar values or randomly shaped results. You could also leverage a DefiningQuery , which you will learn about a bit later in this chapter. You'll be able to rename the fields that result so that they can automatically line up with the Activity entity.
13.4.2. Query Stored Procedures and Inherited Types What about inherited types? If you have a procedure whose results match up with a derived type such as Customer , you can map the function in the Designer with no problem. The CustomersWhoTravelledinDateRange stored procedure returns all of the appropriate fields to match up with the Customer type. This includes fields from theCustomer table, fields from the Contact table, and fields from theContactPersonalInfo table.
NOTE You will see the originally misspelledCustomer table field, PrimaryDesintation , in the stored procedure as a nice reminder that you don't have to live with these problems in your EDM.
PROCEDURE CustomersWhoTravelledinDateRange --returns customer records with contact info for customers @startdate DATETIME, @enddate datetime AS SELECT Customers.ContactID, Customers.PrimaryDesintation, Customers.CustomerTypeID, Customers.InitialDate, Customers.SecondaryDestination, Customers.PrimaryActivity, Customers.SecondaryActivity, Customers.Notes, Contact.FirstName, Contact.LastName, Contact.Title, Contact.AddDate, Contact.ModifiedDate, ContactPersonalInfo.BirthDate, ContactPersonalInfo.HeightInches, ContactPersonalInfo.WeightPounds, ContactPersonalInfo.DietaryRestrictions,Contact.TimeStamp FROM Customers INNER JOIN Contact ON Customers.ContactID = Contact.ContactID INNER JOIN ContactPersonalInfo ON Customers.ContactID = ContactPersonalInfo.ContactID WHERE customers.contactid IN (SELECT Customers.ContactID FROM Customers INNER JOIN Reservations ON Customers.ContactID = Reservations.ContactID INNER JOIN Events ON Reservations.EventID = Events.EventID WHERE events.startdate>=@startdate AND events.startdate
The fact that the EntitySet is defined with a DefiningQuery has no other impact on the entity in the CSDL or the mappings. Figures Figure 13-2 and Figure 13-3 show the entity in the model and its mappings back to the entity defined in the SSDL. The only difference from table-based entities is the inability to persist changes to the database from the view-based entities without using stored procedures for updating.
Figure 13-2. An entity that represents a view in the database and is mapped to a DefiningQuery in the SSDL of the model
Figure 13-3. The mappings for the view entity, which are the same as any other entity's mappings
13.6.2. Using DefiningQuery to Create Your Own Views "DefiningQuery provides an ultimate escape hatch for cases where the mapping is too complex to define in MSL." —Mike Pizzo, principal architect on the Data Programmability team at Microsoft, in the MSDN forums for the Entity Framework DefiningQuery really is the ultimate escape hatch. Even with the incredible flexibility that the model's various mapping capabilities provide, there still may be some things that you just cannot manage to pull off.
A DefiningQuery lets you add queries using the store's native language—for example, T-SQL or PL/SQL—directly to the store layer of the model. It's the last step before swallowing your modeling wizardry pride and asking the database administrator to add another view or stored procedure to the database; or in cases where modifying the database is not a possibility. In addition to creating completely new database views with aDefiningQuery , there are other uses forDefiningQuery. One example is to write a DefiningQuery that returns an entity with properties that don't exist in the database, such as a calculated property. Although you could achieve the same effect using partial classes, that does not get the property into the model itself, and this could make a big difference if the model is to be shared with other application developers, report builders, or additional EDM consumers in your enterprise. Also, when you create your own DefiningQuery , if the model does not already have an entity that lines up with its results, you will have to create all of the model elements yourself: the Entity and EntitySet in the CSDL, theEntity and EntitySet in the SSDL, and the mappings. You already did that the easy way by creating a new view in the database. In the next walkthrough, along with creating a DefiningQuery , you will see how to implement these additional necessary elements manually in the model. A view that would be very useful for BreakAway's team to have is one that displays reservations that have not been fully paid. Although you can express the query in Entity SQL, as shown in Example 13-7 , it is somewhat challenging to construct.
Example 13-7. A complicated Entity SQL query
(SELECT r.reservationID,r.Customer.ContactID,r.Trip.TripID, SUM(SELECT VALUE p.amount from r.Payments as p) AS PaymentsTotal, r.Trip.TripCostUSD AS TripCost FROM BAEntities.reservations AS r WHERE SUM(SELECT VALUE p.amount FROM r.Payments AS p) < r.Trip.TripCostUSD) UNION (SELECT r.reservationID,r.Customer.ContactID,r.Trip.TripID,0, r.Trip.TripCostUSD FROM BAEntities.Reservations AS r WHERE EXISTS(SELECT p from r.Payments AS p)= false)
It would be nice to have this view be part of the model, since it would come in handy in a number of places. You could get at this particular data in other ways, such as by having a PaidinFull Boolean added to the Reservation table in the database and then creating a derived type, but that would result in the need for additional logic to keep that Boolean valid. Another option might be to create a QueryView . But because it is a complex query, you might want to have more control over the SQL that hits the database. More importantly, you may be unable to work out the Entity SQL even if you are able to express the query in your native query syntax (e.g., T-SQL). A DefiningQuery allows you to do just that: add the native query into your model.
13.6.3. Implementing a DefiningQuery To create the DefiningQuery you'll need the following elements in your model: 1.
The native command to express the query
2.
An Entity, and an EntitySet in the CSDL
3.
A virtual table in the SSDL in the form of anEntity
4.
An EntitySet in the SSDL to contain the DefiningQuery
5.
A mapping between the entity and the virtual table
You can create items 2 and 5 in the preceding list using the Designer, whereas you must create the others using the XML Editor. The first step to implementing the UnpaidReservations DefiningQuery is to rewrite the Entity SQL expression in Example 13-7 as a
native database query. For SQL Server, that would look like Example 13-8 . This is much simpler than the query that would be constructed from the Entity SQL expression shown earlier.
Example 13-8. The Entity SQL query from Example 13-7 expressed in T-SQL
SELECT r.reservationid, SUM(amount) AS PaymentTotal, MIN(events.[TripCostUSD]) AS cost FROM reservations r JOIN payments p ON r.[ReservationID]=p.[ReservationID] JOIN events ON r.[EventID]=events.[EventID] GROUP BY r.[ReservationID] HAVING SUM(amount) c.ContactID == contactID) .FirstOrDefault(); if (contact != null) { if (!((contact) is Customer)) { //remove contact from the context and then from memory context.Detach(contact); contact = null; //call the function which returns a customer return context.CreateCustomerfromContact(contactID).First(); } } }
return null; }
NOTE It's necessary to remove the contact from memory before returning the new customer, because you would otherwise have a conflict. When you first queried for the contact, the query returned a non-Customer type of contact. When you call the function and it attempts to return a contact with the same ContactID but of a different type, you will get an exception. The customer will have been created; the problem is just a conflict when an attempt is made to add the newly returned Customer into the context. This is nice for a test module, but what about the real world? This function gives you the ability to turn a contact into a customer, and you have a number of options for leveraging it. You may want it to be an explicit action whereby a user would select a contact and do something in the UI to tell the system to create a customer from the contact. Another option is to have it happen implicitly, whereby if the user attempts to perform a Customer-like action on a non-Customer (e.g., add a reservation), the system could, in the background, go ahead and create the customer identity for that contact if necessary.
13.7.2. Insert, Update, and Delete Functions That Don't Return an Entity What if you have a stored procedure in your database that performs a modification to the database, but doesn't return any results? Or returns a scalar value as the result? Or returns randomly shaped results? Only functions that return entities will be built into the classes that are generated from the model. Functions that return scalars or nothing won't be available as methods of the context. Instead, you'll need to call them from EntityClient. You can use EntityClient and one ofEntityCommand's Execute methods to call these functions. Like the other ADO.NET Command classes, you can callExecuteNonQuery, ExecuteScalar, or ExecuteReader from
EntityCommand. You already usedExecuteReader to perform queries. Let's take a look at these commands for calling functions.
13.7.2.1. Returning a scalar value
CancelTrip is a procedure in the database that cancels all reservations and adds negative payments (to balance out the existing payments) for all clients who were on a particular trip. The procedure returns a Boolean indicating whether the procedure executed successfully. The stored procedure takes a single parameter, the EventID (remember that in thedatabase, the table for trips is called
Events ), and then performs all of the necessary actions. The procedure would also appear in the SSDL as a function such as the following:
You can call this function in the same way you called theScalarFunction in Example 13-7 . The only difference is that you are capturing a Boolean as the return:
Private Sub CancelTrip(ByVal TripID As Integer) Dim result As Boolean Using eConn = New EntityConnection("Name = BAEntities") Dim eComm = eConn.CreateCommand With eComm .CommandType = CommandType.StoredProcedure .CommandText = "BAEntities.CancelTrip" .Parameters.AddWithValue("eventid", TripID) eConn.Open() result = Convert.ToBoolean(.ExecuteScalar()) eConn.Close() End With End Using End Sub
private static void CancelTrip(int TripID) { bool result = false; using (var eConn = new EntityConnection("Name = BAEntities")) { var eComm = eConn.CreateCommand(); eComm.CommandType = CommandType.StoredProcedure; eComm.CommandText = "BAEntities.CancelTrip"; eComm.Parameters.AddWithValue("eventid", TripID); eConn.Open(); result = Convert.ToBoolean(eComm.ExecuteScalar()); eConn.Close(); } }
The stored procedure needs to return its data using theSELECT clause, not
RETURN. If you use RETURN , you will get the following error: The data reader returned by the store data provider does not have enough columns for the query requested.
13.7.2.2. Returning nothing In the FunctionImport mapping, make sure the return type is set toNone . In your code, execute the command using the
EntityCommand.NonQuery method.
13.8. Defining Insert, Update, and Delete Stored Procedures Directly in the Model Earlier in this chapter, you defined a T-SQL query directly in the SSDL by using the element of
. That example was for a read stored procedure. You can do the same for insert, update, and delete procedures, as well. And as with the other functions, how you call these functions depends on the return type.
13.8.1. What Do the Functions Look Like? You already have worked with functions for database stored procedures in previous chapters. For example, the
InsertPayment stored procedure is wrapped by theInsertPayment function in the model.
If you want to try out this function on your own, place it in the extra model you created to test out QueryView rather than in the BreakAway model.
Imagine that the BreakAway database didn't already have stored procedures for inserting contacts, or that you wanted to change the definition of the InsertContact procedure that exists in the database. Here's how you would construct a new function:
INSERT INTO Contact ([FirstName] ,[LastName] ,[Title] ,[AddDate] ,[ModifiedDate]) VALUES (@FirstName,@LastName,@Title,GETDATE(),GETDATE()) SELECT SCOPE_IDENTITY() as ContactID
You can then map this function to the Contact entity using the Designer just as you would map other functions. Compare this to the function the wizard created from the existingInsertContact function:
As described in Chapter 6 , the two required attributes for a function areName and IsComposable . Like the DefiningQuery , the Designer only partially supports custom functions. You need to create them in the XML Editor, but once they are there you can still open the Designer and use it to map the virtual functions to entities. Additionally, because these are not contained in actual database objects, they should remain intact if you use the Update Model Wizard to refresh the database elements in the store layer of your model.
13.9. Mapping Insert/Update/Delete to Types Within an Inheritance Structure One more rule regarding stored procedures in the EDM may come as a surprise, whether you are using the Designer or implementing the stored procedures by hand. When mapping stored procedures to base or derived types, you are also required to map the stored procedures to any other type within the inheritance structure. Therefore, if you map a function to a base type, you must also map a function to its derived types. Conversely, mapping to a derived type, such as Customer , requires that you also map functions to the base type (Contact ) and any other derived types (NonCustomer ). If you forget this rule, the compiler will happily remind you with an error message. The following error message, which results when you have mapped to the Customer entity but not the Contact, is an example:
If an EntitySet mapping includes a function binding, function bindings must be included for all types. The following types do not have function bindings: BreakAwayModel.Contact.
Default Command Generation Versus Stored Procedures for Inherited Types When SaveChanges performs insert and delete operations on aCustomer in the BreakAway model, it will create and execute three separate commands: one for the Contact table, one for theCustomer table, and one for the ContactPersonalInfo table. The command(s) created for an update depend on which properties have been modified. Using a stored procedure, which internalizes the work on these three tables, you can reduce Customer modifications to single commands. For example, an insert stored procedure would call a single Insert command. That would mean one trip to the database instead of three. On the other hand, an update stored procedure predefines which properties are passed to the database for an update, regardless of which properties have changed. When the Entity Framework creates the commands, the commands are more efficient because only the modified properties are sent as parameters. Depending on how your stored procedure is written, this could be seen as a benefit of using the Entity Framework's default query and command processing over stored procedures.
13.9.1. What If Stored Procedures Affect Multiple Entities in an Inheritance Structure? You can take a few approaches when working with stored procedures. The procedures in the BreakAway database take the more standard route, which is to simply perform the tasks at hand. The database contains stored procedures for performing inserts, updates, and deletes on customers as well as contacts. InsertCustomer, UpdateCustomer, and
DeleteCustomer interact with theCustomer, Contact, and ContactPersonalInfo tables, andInsertContact, UpdateContact, and DeleteContact interact with only theContact table. When the Entity Framework uses these stored procedures it will not overlap them. When saving changes to Customer s it will call only the Customer entity's functions. When saving changes toContacts it will call only theContact entity's functions. You can try to map these functions, or just be prepared for when you are defining your own model with inherited entities and stored procedures.
13.10. Implementing and Querying with User-Defined Functions (UDFs) Many databases allow you to create your own functions, called user-defined functions, or UDFs. In SQL Server, these can be table-valued functions, scalar functions, or array functions. The Entity Framework's EDM supports UDFs, with the exception of table-valued functions. The EDM Wizard and the Update Model Wizard list UDFs along with stored procedures in the Stored Procedures node. Like stored procedures, UDFs are resolved as functions in the store layer of your model. In the BreakAway database, because a customer's weight is stored in U.S. pounds, a function is defined to convert pounds into kilograms. It's called ufnLBtoKG . If you were to select this UDF in either of the wizards, you would find the following function in the SSDL section of the EDMX file:
Notice that the IsComposable attribute is true . This is different from the stored procedures whoseIsComposable attribute must be false . You can use UDFs as parts ofqueries. Another big difference between UDFs and stored procedures is that you call them directly from the store layer rather than doing function mapping and calling them from the conceptual model. This means that the functions are not available in LINQ. You can access them only in Entity SQL statements. You can use this with ObjectQuery or with EntityClient. Example 13-11 uses the ufnLBtoKG function with Entity SQL. You will find that there is a surprising difference between this expression and those you wrote earlier. This is due to the fact that the function is only in the store. The example will return a list of customer names, their weight in pounds, and their weight in kilograms.
NOTE Because Customer is a derived type, you will need to use theTREAT AS Entity SQL operator that you learned about in Chapter 12 . Remember that Entity SQL points back to the assembly namespace, not the model namespace when casting to derived types.
Example 13-11. Querying with a UDF
Dim esql = "select TREAT(c as BAGA.Customer).WeightPounds," & _ "BAModel.Store.ufnLBtoKG(TREAT(c as BAGA.Customer).WeightPounds) " & _ "from BAEntities.Contacts AS c where c is of(BAGA.Customer)"
Dim query = context.CreateQuery(Of DbDataRecord)(esql) Dim weightList = query.ToList
var esql = "select TREAT(c as BAGA.Customer).WeightPounds," + "BAModel.Store.ufnLBtoKG(TREAT(c as BAGA.Customer).WeightPounds) " + "from BAEntities.Contacts AS c where c is of(BAGA.Customer)" Dim query == context.CreateQuery(esql); var weightList = query.ToList();
Notice how the function is called:BAModel.Store.ufnLBtoKG . It is using the strongly typed name of the function in the store layer, not the CSDL.
NOTE Why do you need to use the SSDL schema to reference the UDF function in the query? This is definitely not expected, and I may not have even figured it out on my own if it weren't for an example I happened to find hiding in the MSDN forums. You'll learn the answer at the end of this section. In fact, if you use function mapping to map this function back to the conceptual layer, at runtime you will get the following error when executing a query that uses the function:
"A FunctionImport is mapped to a storage function 'BAModel.Store.ufnLBtoKG' that can be composed. Only stored procedure functions may be mapped."
Entity Framework does not currently support mapping UDFs into the model. This is why you must access it directly from the SSDL.
13.11. Summary As you learned in this chapter, the Entity Framework supports stored procedures in many more ways than the Designer-supported function mappings. And there's not much that you can't pull off. The only drawback is that in some cases, much more manual effort may be involved than you might want to employ. For some read stored procedures, you may find that it is easier to create a view that returns a similarly shaped result and implement that in your model instead of the stored procedure. You can also define native stored procedures directly in your model for reading or writing to the database. Some of the functions that result from stored procedures can be called as a method of the ObjectContext , whereas others must be called fromEntityConnection. For organizations that have an investment in stored procedures but want to leverage the model and the change tracking of the Entity Framework, additional effort will be required to get the best of both worlds. If you have myriad stored procedures that you must use, you may find the amount of manual work in the model to be too much. Although version 1 of the Entity Framework is focused more on query composition and query processing—which is why many of these scenarios require more effort to implement—Microsoft promises better support for stored procedures in future versions of the Entity Framework, starting with the next version, which will be part of the Visual Studio 2010 release.
Chapter 14. Using Entities with Web and WCF Services Services are a critical part of today's (and tomorrow's) application environments. You can use entities in service applications, and depending on your needs you can approach the task of using the Entity Framework with services in a number of ways. You can build your own services in the form of ASMX ("classic") and Windows Communication Foundation (WCF) services, which were introduced in .NET 3.0. In this chapter, you will write an ASMX Web Service and a WCF service leveraging what you've already learned about the Entity Framework. In Chapter 22 , we will revisit WCF services, and there you will use some of the more in-depth knowledge you will gain in the latter part of this book to write a more streamlined WCF service that will impact how the service is consumed. The services in this chapter will be consumed by a simple Windows Forms clientapplication.
NOTE If you have never built either a web service or a WCF service before, have no fear. The walkthroughs will provide you with step-by-step details.
14.1. Building a Client That Is Ignorant of the Entity Framework In this chapter, the samples depend on the Entity Framework only on the server side. The clients that consume the services use a simplified version of the classes that the services provide. The client will not perform any database connections, change tracking, relationship management, or anything else that depends on Object Services. This means that not only does your client not have to install the Entity Framework APIs—or your own model, for that matter—but also that you can create clients that are not even written in .NET, as long as they follow the web services' rules. You will use a .NET client in this chapter so that you can get some hands-on experience manipulating the objects returned by these services as well as interacting with the services. In Chapter 22, you'll learn how you can share some of your model's business logic with the consuming client. For now, we'll keep it a bit simpler to get the basic concepts down. Unless you want to take advantage of Object Services on the client side, for the sake of either change tracking or relationship management, there's no reason to reference your model assembly or the Entity Framework on the client side. Even if you do perform change tracking on the client side, you will lose those changes when you send the objects back to the service.
14.1.1. Pros and Cons of an Entity Framework-Agnostic Consumer Without the Entity Framework APIs in the client application, you will be faced with a few additional challenges that you should have some experience handling. The second example in this chapter, which covers use of WCF services, has a lot of relationships; you will find that the lack of references to the model and System.Data.Entity are noticeable and educational. For one thing, EntityCollection s won't exist on the client, as they are a class inSystem.Data.Entity assembly. As a result, the children of an object are contained in a List rather than an EntityCollection . You'll see toward the end of the chapter how it is necessary to explicitly instantiate the Reservations property of a new customer by calling
Customer.Reservations = new List. Another example is that you don't automatically get two-way relationship navigation. If you add Reservation a to a Customer, you will find thatReservation in Customer.Reservations, but Reservation.Customer will return null. If you needed to navigate in both directions, you would have to explicitly bind them in both ways (e.g., additionally calling Reservation.Customer=myCustomer). This is not to say that excluding the references in the model assembly andSystem.Data.Entity is a bad thing. In many scenarios your business rules may prevent you from having the client depend on these things, so it's very useful to see how to build clients in this way. On the other hand, those references can be a welcome inclusion in many situations. The relationship management will be simpler, and although you will get change tracking on the client, keep in mind that the changes stored in the ObjectStateEntry objects will be lost when transferring the objects back to the service. Although you already learned about the ObjectStateEntry objects, you will learn more about this problem, and solutions for it, in later chapters.
14.2. Using the Entity Framework with ASMX Web Services In this section, you will build the ASMX Web Service and client. The service will have the ability to return a list of customer names as well as a complete individual customer. It also provides operations to update an existing customer and to insert new customers. The client application shown in Figure 14-1 has features that use each of the service's operations. The client application will have no knowledge of the Entity Framework, the BreakAway model, or their classes.
Figure 14-1. A Windows form that will consume a web service that uses the Entity Framework
14.2.1. Building the ASMX Service Your first task will be to create and implement the web service, which will need to provide the following features: Return a customer list Return an individual customer Insert a new customer Update an existing customer Once you've built the web service, you'll build a client to consume it.
14.2.1.1. Creating the web service Follow these steps to create the web service: 1. Add a new ASP.NET Web Service Application project to your solution. 2. Add references to the BreakAwayModel project and to System.Data.Entity. 3. Copy the ConnectionString section from the BreakAwayModel's app.config file into the new application's web.config file. The different operations (methods) will be easier to write if you have imported the namespace of the BreakAway model into the code file for the service, which you can do by following these steps: 1. Open Service1.asmx. By default, this should open the code file Service1.asmx.vb or Service1.asmx.cs. 2. Import the BreakAwayModel's namespace to simplify coding the methods (see Example 14-1).
Example 14-1. Importing the BreakAway model's assembly namespace
Imports BAGA
using BAGA;
3. Visual Studio adds a HelloWorld WebMethod to new service files. You can remove that if you like.
14.2.1.2. The GetCustomers method The first method to create will be GetCustomers , which will return a list of the IDs and names for all of the customers in the database. On the client side, you'll use this to populate a drop-down list for selecting a customer. Since there is no reason to return entire Customer objects, the method will use projection in the query. The projection will cause you to hit the first bump in the road, though it is not an Entity Framework problem. Instead, the problem is related to anonymous types. The method needs to identify the type that it will return. In this case, because of the projection, it will be returning a List of anonymous types, but there is no way to declare a List of anonymous types in the method signature. There is no such thing as List because anonymous types are transient objects. You have three options, listed here in the order of best to worst: Create a class to support the schema of the query results. Return complete customer objects rather than using a projection. However, this will send much more data over the wire than necessary. Use Entity SQL with either EntityClient or an ObjectQuery and return a List of dbDataRecords.
Anonymous Types in Web Services You cannot use anonymous types as a return type or a received type in web services. Therefore, if you want to return the results of a query projection from a web service, you have a few options. For one, you can create a class to support the schema of the query results and use that as the return type. Alternatively, in the case of the Entity Framework you could use Entity SQL instead of LINQ to Entities. Combined with either EntityClient or an ObjectQuery
, this will return a List of DbDataRecord s. By doing this, however, you won't have a proper contract for the web service, which is a
description of the type being transmitted. This may be an easy solution, but it goes against some of the basic tenets of web services.
In this sample, since the return type is a simple type with only two fields, creating a new class won't be too cumbersome, so that's what we'll do.
NOTE Classes that are created solely for the purpose of transferring object values between processes follow the pattern called Data Transfer Objects , or DTOs. You'll be using a simple DTO in this service and again in the WCF service later in the chapter. Chapter 22 uses DTOs more heavily to help solve some of the problems we encounter when transferring entities across tiers. You can define the class in the same code file as the service; just place it after the end of the Service1 class. With Visual Basic, you will need to define the class variables and properties. With C#, you can take advantage of auto-implemented properties. After the end of the Service1 class, add the class shown in Example 14-2.
Example 14-2. The ShortContact class for transferring data from the service
Public Class ShortContact Private _ContactID As Integer Private _Name As String
Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property
Public Property ContactID() As Integer Get Return _ContactID End Get Set(ByVal value As Integer) _ContactID = value End Set End Property
End Class
public class ShortContact { public string Name { get; set; } public int ContactID { get; set; } }
Once the new class exists, you can add the GetCustomers method to the web service class (see Example 14-3). GetCustomers will now return a List of ShortContact types.
Example 14-3. The GetCustomers WebMethod
_ Public Function GetCustomers() As List(Of ShortContact) Using Context As New BAEntities Dim query = From cust In Context.Contacts.OfType(Of Customer)() _ Order By cust.LastName, cust.FirstName _ Select New ShortContact With {.ContactID = cust.ContactID, _ .Name = cust.LastName.Trim & ", " & cust.FirstName} Return query.ToList End Using End Function
[WebMethod()] public List GetCustomers() { using (BAEntities Context = new BAEntities()) { var query = from cust in Context.Contacts.OfType() orderby cust.LastName, cust.FirstName select new ShortContact{ContactID = cust.ContactID, Name = cust.LastName.Trim() + ", " + cust.FirstName }; return query.ToList(); } }
A few aspects of the query are worth pointing out: Notice the LINQ syntax for projecting into a known type ( ShortContact ). You need to define what type will be returned from the projection, and then explicitly assign values to the variables in the object. This syntax is not specific to LINQ to Entities, but it is a general LINQ construct. Notice the OfType method in the LINQ query. Though you saw OfType in Chapter 12 , this is the first time you will see it in action in an application. OfType forces the EntitySet to return only those contacts who are Customer types. Although in the console application tests you frequently used the Using construct to wrap your code, you are using it here for a very explicit purpose. When executing queries in the web service, you want your context and connection to be as short-lived as possible. You should create the context, execute the query (causing the context to open the connection and then close it when the data has been retrieved), and then get rid of the context immediately. With the possibility that many clients will make many calls to your services, you don't want to have any of those contexts or connections hanging around in memory. The last notable piece of code is the ToList call. This forces the query to execute all the way to the end, and the List will contain the objects that result. Remember that the query's job is to execute, and that the query is not serializable. You need to force the objects to give up their dependency on the query. Because you have pushed them into a List , they are free and can easily be serialized and passed to the client application.
14.2.1.3. Testing the GetCustomers service operation One of the nice benefits of creating web services in Visual Studio is that Visual Studio will automatically generate a web service interface in HTML where you can see a list of the methods in the service and execute them. This will allow you to easily see what the results of the GetCustomers operation look like: 1. In the Solution Explorer, right-click the Service1.asmx file and select View in Browser from its context menu. 2. Click the GetCustomers method, and on the next page click the Invoke button. This will show you the results of the GetCustomers method, which will look like Figure 14-2.
Figure 14-2. The XML payload returned by GetCustomers
14.2.1.4. Adding a method to return a single customer The next method you'll need is GetCustomer , to return a single customer. Here we will take the easy road and return a Customer entity. Add the method in Example 14-4 into the web service.
Example 14-4. The GetCustomer WebMethod
_ Public Function GetCustomer(ByVal contactID As Integer) As Customer Using Context As New BAEntities Dim customers = From c In Context.Contacts.OfType(Of Customer) _ Where c.ContactID = contactID _ Select c Return customers.FirstOrDefault End Using End Function
[WebMethod()] public Customer GetCustomer(int contactID) { using (BAEntities Context = new BAEntities()) { var customers = from c in Context.Contacts.OfType() where c.ContactID == contactID select c; return customers.FirstOrDefault(); } }
This method receives a ContactID value and then queries for a Customer , this time returning the entire Customer object. Use the FirstOrDefault method to return a single
object, rather than returning a list. I've suggested FirstOrDefault rather than First because First will throw an Exception if the record cannot be found in the database. FirstOrDefault
would return a null object, which may or may not be preferable in your scenario.
14.2.1.5. Testing the new method Once again, you can view the ASMX file in the browser: 1. Open Service1.asmx in the browser. 2. Select the GetCustomer method. 3.
Enter the ID for one of the contacts—for example, 582 for Catherine Abel.
4. Click Invoke. Now you can see the payload of an entity object in web services (see Example 14-5 ). I have removed any whitespace for readability.
Example 14-5. A Customer entity with its EntityObject schema
Contacts BAEntities ContactID 582 582 Catherine Abel Ms. 2003-12-14T12:57:40.893 2008-08-07T08:27:07.033 AAAAAAAAF/8= 2007-06-29T05:56:51.593 test43 1994-01-05T00:00:00 55 124 CustomerTypes BAEntities CustomerTypeID 3 Destinations BAEntities DestinationID 49 Destinations
BAEntities DestinationID 52 Activities BAEntities ActivityID 16 Activities BAEntities ActivityID 19
This is an awfully large payload for what amounts to a small quantity of data. Even though the method is not returning a full graph, it is returning the complete schema of the Customer object, which includes references to all of the related data. In many applications, this won't be an issue. In others you most likely will want to create another, simpler class, as you did forShortContact , and return that rather than the EntityObject . This type of class is referred to as a Data Transfer Object (DTO), also known as a value object . DTO is a well-known design pattern for transferring objects between applications. For this example, the EntityObject is perfectly fine, but it's still important to be aware of what you are sending through the pipe.
XML Serialization, Web Services, and Graphs This demo is specifically returning only a single object, and not graphs. The Customer does not have Reservations or Trips attached. The reason is that XML serialization is not able to retain the whole graph. Even if you created a graph showing a customer plus his reservations on the server side, when the data arrives on the client side the reservations will be gone. This is not an Entity Framework problem, but an age-old problem with services and XML serialization. You can solve the problem in a number of ways—for example, by using the Façade pattern. However, because WCF's DataContract serializer is able to serialize graphs, it makes more sense to just use WCF when graphs are involved. The WCF demo later in this chapter will handle graphs.
14.2.1.6. What about interoperability? Seeing the output of this web method highlights a question that is very important to many developers: what about interoperability? If you are creating web services that consuming applications written in various technologies will use (e.g., .NET, PHP, and Java), you do not want to return entity objects from your web services. Instead, you should use DTOs. In the ASMX sample, the GetCustomers method does just that, returning ShortContact rather than the Customer entity object. Its payload was simple XML. Example 14-6 shows a section of that payload for one customer. This is easily consumed by any technology.
Example 14-6. The XML for a ShortContact type returned by the service
Abel, Catherine 582
The GetCustomer method, however, returned a Customer entity object that was chock-full of metadata from the Entity Data Model (EDM). It gets really scary when you start dealing with entity references, which in a DTO might be surfaced and would simply be foreign key values, if you even need to return them to the client. Digging a little deeper, if you look at the WSDL description of the method that returns the Customer entity, things get even more complicated as it contains information regarding the Contact entity type from which the Customer type derives.
WSDL WSDL stands for Web Services Description Language and is used to provide a full description of the web service, its location, and its operations (methods). When you view the ASMX file in the web service interface, you will see a Service Description link at the top of the page. That will open the service in the WSDL view. You can always browse directly to a web service WSDL by adding ?WSDL to the end of the URL—for example, http://localhost/MyWebService.asmx?wsdl/ . The WSDL describes the web service in detail using XML. Try it out!
Although you can leverage this in a .NET client in a number of ways (and even more easily using Visual Studio, because it can generate proxy classes), XML that contains all of this logic can be a nightmare for some consumers. Figures Figure 14-3 and Figure 14-4 show all of the classes in the web service payload. This should give you a feeling for what a consumer will be dealing with when she isn't using Visual Studio, which, as you will see when you build the client, hides most of this from developers.
Figure 14-3. Dependency of EntityObject on Entity Framework classes
Figure 14-4. The ShortContact class, along with some more Entity Framework classes and the CompletedEventArgs for the four web methods
Notice the difference between the Customer object, which is dependent on a number of Entity Framework classes, and the ShortContact DTO, which is completely independent.
NOTE The more generalized you need your service to be, the more it might make sense to consider ADO.NET Data Services, which can expose data through an EDM in a simplified way over the Web. Minimally, services that are designed for interoperability need to return data that is not bound to entities.
14.2.1.7. The insert and update web methods The web service now has two web methods that return data. It also needs the ability to save data. This web service will allow for updates and inserts: 1. Add to the web service the insert method shown in Example 14-7.
Example 14-7. The InsertCustomer WebMethod
_ Public Function InsertCustomer(ByVal cust As Customer) As String Try Using context As New BAEntities context.AddToContacts(cust) context.SaveChanges() End Using Return cust.ContactID.ToString Catch ex As Exception Return "Error: " & ex.Message End Try End Function
[WebMethod()] public string InsertCustomer(Customer cust)
{ try { using (BAEntities context = new BAEntities()) { context.AddToContacts(cust); context.SaveChanges(); } return cust.ContactID.ToString(); } catch (Exception ex) { return "Error: " + ex.Message; } }
This method will insert a new customer and return the contactID that comes back from the SaveChanges method. If something goes wrong, it will return the exception message instead. The return type of the function is String in order to accommodate returning a status message. The client-side code can then react based on whether a value or an error is returned. 2. Add to the web service the update method shown in Example 14-8.
Example 14-8. The UpdateCustomer WebMethod
_ Public Function UpdateCustomer(ByVal custEdit As Customer) As String Try Using context As New BAEntities Dim custtoUpdate = (From c In Context.Contacts.OfType(Of Customer) _ Where c.ContactID = custEdit.ContactID) _ .FirstOrDefault If Not custtoUpdate Is Nothing Then context.ApplyPropertyChanges("Contacts", custEdit) context.SaveChanges() End If Return "Success" End Using Catch ex As Exception Return "Error: " & ex.Message End Try End Function
[WebMethod()] public string UpdateCustomer(Customer custEdit) { try { using (BAEntities context = new BAEntities()) { var custtoUpdate = (from c in context.Contacts.OfType() where c.ContactID == custEdit.ContactID select c) .FirstOrDefault();
if (custtoUpdate != null) { context.ApplyPropertyChanges("Contacts", custEdit); context.SaveChanges();
} return "Success"; } } catch (Exception ex) { return "Error: " + ex.Message; } }
This update method queries the database to get a current version of the customer, and places an "original" version of the customer in theObjectContext . Then, ApplyPropertyChanges
, which you learned about in Chapter 9, takes the Customer that just came back from the client and updates the one that is being tracked by the
context. When SaveChanges is called, any values from the incoming Customer that were different from the server values will be updated in the database. Why were all of these steps necessary? Because the object that comes back from the client will have only its current values, and will have no knowledge of its original values. ObjectContext would not recognize that any updates need to be made. This is the impact of moving objects across tiers. To anyone who is used to just calling a stored procedure and sending in the new values, it may seem confusing because of the extra step. The reasoning behind the code in this method requires further explanation, which I provide in the next sidebar.
The Problem with Change Tracking Across Tiers The simple example in Example 14-8 exposed one of the biggest challenges for enterprise developers in version 1 of the Entity Framework: change tracking in multi-tiered applications. In Chapter 9 , you learned about the role of the ObjectStateEntry within the ObjectContext . The ObjectStateEntry keeps track of the original and current states of each object. The objects themselves know only their current values. When entity objects are serialized to be passed between services and clients (or for other reasons), only the objects are serialized. Neither the nor the ObjectStateEntry is serialized. Therefore, when the customer is deserialized on the receiving end, it has only its current values.
ObjectContext
As you have seen, SaveChanges depends on the original values to determine what has changed, and therefore what needs to be sent to the database for updates. You can solve this problem in a number of ways, and the choices depend on your goals and the code investment you are willing to make. Because this is an introductory sample, you will see the simplest way to perform updates in this scenario, but it is not necessarily the way that is most efficient or provides the best performance.
14.2.1.8. The path of least resistance The simplest way to handle this scenario, if you don't mind the extra call to the database, is the method you see in the UpdateCustomer web method: query the database to get the current stored version of the customer into the ObjectContext , update the object with the new values using ApplyPropertyChanges, and then call ObjectContext.SaveChanges.
Again, different business rules will demand different solutions, but in this scenario, there is no need to know what the original values were when that customer was first queried. Let's take a closer look at the two critical steps performed by the UpdateCustomer method: 1.
The method queries the EDM to get the current server values for the customer. The object from the database has the following values: ContactID : 123 FirstName: Julie LastName: Lerman Note:
[null]
Because the object came directly from the database, the original values and current values for the object are the same. 2. The method then calls ApplyPropertyChanges("Contacts", custEdit): a.
The context inspects the EDM's metadata and learns that in the Contacts EntitySet, the CustomerID property is used for the EntityKey.
b. Next, the context looks at custEdit and determines that the CustomerID value is 123. c.
Now it can find the cached object that is tied to the Contacts entity and has the value 123 in the CustomerID field. This is the object that needs to be changed.
d.
The processor sets the current value for each property of the object to the values that it extracts from the changed object.
In the end, the object's values are updated as shown in Table 14-1.
Table 14-1. Original and current values of the entity after ApplyPropertyChanges has been called Object
Original value
Current value
ContactID
123
123
FirstName
Julie
Julie
LastName
Lerman
Lerman
Notes
[null]
"Finally learned to roll her kayak"
When it's time to call SaveChanges , Object Services will find that only the Notes field has changed and the query that goes to the database will be a parameterized command that would translate to the following: Update Customers Set Notes='Finally learned to roll her kayak' Where ContactID=123
NOTE Because these operations involve receiving serialized objects, you can't test them in the browser interface the way you tested the previous operations. That's it for the web service. Now it's time to build the client to consume it.
14.2.2. Building the Client Application The client will have a simple interface, shown in Figure 14-1 , with a drop-down to show a list of customers and fields for editing a currently selected customer, which can be saved. The UI also allows a user to create and save a new customer. The steps for creating the application will be as follows: 1.
Create a reference to the web service and proxy classes.
2.
Add the logic for interacting with the service.
3.
Design the user interface.
4.
Tie the UI elements to the various functions for interacting with the service.
14.2.2.1. Setting up the client project and the proxy to the service To create a reference to the web service and proxy classes, follow these steps: 1. Create a new Windows Forms Application project. Note that you will not be referencing System.Data.Entity or the BreakAwayModel project in this application. All of that knowledge will come from the web service. 2.
In the Solution Explorer, right-click the new project and select Add Service Reference.
NOTE If you have used web services in versions of Visual Studio prior to Visual Studio 2008, note that the Web References dialog is now buried within the Service Reference dialog. 1.
Click the Advanced button at the bottom of the Service Reference dialog to open the Service Reference Settings page.
2.
Click Add Web Reference at the bottom of the settings page.
3.
In the Web Reference dialog, choose the option to browse to web services in this solution.
4.
Select the service you just created. If you didn't change the service name, it will be Service1 . If you had more than one service in the solution, you may need to rely on the project name to properly identify the correct service. After you select the service, the dialog will display the same HTML interface showing the operations that are in the service that you saw when testing the service.
5.
On the right, change the web reference name to BAWebService and then click the Add Reference button.
Visual Studio will automatically create proxy classes so that you can work with the web services in the client-side code. If you click the Show All Files icon in the Solution Explorer, you can expand the web reference to see what files were created, as shown in Figure 14-5.
Figure 14-5. Expanding the newly created web reference to reveal all of the files Visual Studio created
Reference.vb/Reference.cs contains the proxy class against which you will code. If you haven't used web services before, you might be interested in opening that file to see what's in there. Note the Customer.DataSource and ShortContact.DataSource . Visual Studio read the WSDL and determined what types were being returned or received by the methods in the web service. Based on this information, it created these DataSource s so that you can easily use the two classes in data-binding scenarios, which you will do in the client application.
14.2.2.2. Adding methods to interact with the web service Your form will need one method for each different web method you'll be calling. The form will also need to keep track of the current customer being edited. Add to the form the variable declaration shown in Example 14-9.
Example 14-9. The _currentCustomer form variable
Private _currentCustomer As BAWebService.Customer
BAWebServices.Customer _currentCustomer;
Add to the form's code-behind the methods shown in Example 14-10 . Each method will instantiate the web service, call the necessary method, and return any results from the service. The UpdateCustomer method will call the appropriate service method based on the state of the _currentCustomer object. If it's new, which is easy to tell by its unassigned ContactID , the object is passed to the InsertCustomer method; otherwise, it is passed to the UpdateCustomer method.
Example 14-10. Form methods for interacting with the web service
Private Function GetCustomers() As Array Using proxy As New BAWebService.Service1 Return proxy.GetCustomers.ToArray End Using End Function
Private Function GetCustomer(ByVal id As Integer) _ As BAWebService.Customer Using proxy As New BAWebService.Service1 Return proxy.GetCustomer(id) End Using End Function
Private Function UpdateCustomer() As String Dim status As String Using proxy As New BAWebService.Service1 If _currentCustomer.ContactID = 0 Then status = proxy.InsertCustomer(_currentCustomer) Else status = proxy.UpdateCustomer(_currentCustomer) End If Return status End Using End Function
private Array GetCustomers() { using (BAWebService.Service1 proxy = new BAWebService.Service1()) { return proxy.GetCustomers().ToArray(); } }
private BAWebService.Customer GetCustomer(int id) { using (BAWebService.Service1 proxy = new BAWebService.Service1()) { return proxy.GetCustomer(id); } }
private string UpdateCustomer() { string status = null; using (BAWebService.Service1 proxy = new BAWebService.Service1()) { if (_currentCustomer.ContactID == 0) { status = proxy.InsertCustomer(_currentCustomer); } else { status = proxy.UpdateCustomer(_currentCustomer); } return status; } }
14.2.2.3. Designing the form You'll be designing the form to look something like the form shown in Figure 14-1: 1. Open the form and drag a ComboBox onto it. 2. Add the FillCombo method, which will populate the ComboBox using the GetCustomers method shown in Example 14-11.
Example 14-11. Method for filling the ComboBox with the Customer list
Private Sub FillCombo() With ComboBox1 .DisplayMember = "Name" .ValueMember = "ContactID" .DataSource = GetCustomers End With End Sub
private void FillCombo() { comboBox1.DisplayMember = "Name"; comboBox1.ValueMember = "ContactID"; comboBox1.DataSource = GetCustomers(); }
3. Modify the form's Load event to call the method shown in Example 14-12.
Example 14-12. Calling FillCombo in the form's Load event
Private Sub Form1_Load( _ ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load FillCombo() End Sub
private void Form1_Load(object sender, EventArgs e) { FillCombo(); }
4. Return to the Design view of the form. To simplify editing the selected Customer, you'll use a DataSource as you did in the Windows Forms application in Chapter 8 . Thanks to the web reference generation, data sources have already been created for the Customer and ShortContact types exposed by the web service. 5. Open the Data Sources window and click the Customer object to select it. This will cause a drop-down arrow to appear. 6. Select Details from the drop-down, as shown in Figure 14-6. By default, Customer would normally be displayed as a DataGridView . This Windows Forms feature lets you change the default display to a Details form. 7.
Drag the Customer data source onto the form. This action will create a set of labels and text boxes for each property, a CustomerDataSource, and a navigation toolbar.
8.
Delete the navigation toolbar since you will be using the combo box to control which customer is displayed.
NOTE The Customer data source also added fields for the entity container name and the EntitySet . Where did they come from? If you open the Customer in the DataSource you'll find that these came from inside the EntityKey property. 1.
Remove any unnecessary labels and text boxes so that your form looks like that shown earlier in Figure 14-1.
Figure 14-6. Changing the Customer data source to default as a Details view in the Data Sources window
The new form controls will be populated with the selected Customer each time the user changes the selection in the combo box. To trigger this, you'll call the method in the ComboBox's SelectIndexChanged event and bind the results to the _currentCustomer variable, which in turn you will bind to the
GetCustomer
CustomerBindingSource .
Example 14-13 shows how to do this.
Example 14-13. Retrieving a new Customer when the user makes a selection in the combo box
Private Sub ComboBox1_SelectedIndexChanged( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles ComboBox1.SelectedIndexChanged _currentCustomer = GetCustomer(CInt(ComboBox1.SelectedValue)) CustomerBindingSource.DataSource = currentCustomer End Sub
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { _currentCustomer = GetCustomer((Int32)comboBox1.SelectedValue); customerBindingSource.DataSource = _currentCustomer; }
CustomerBindingSource
will use its data source, the Customer, to populate the text boxes on the form.
14.2.2.4. Adding the New and Save buttons Now you'll need to allow the user to create new customers and save changes to existing or new customers: 1.
Add two buttons to the form. Change their Text fields to New and Save, respectively.
2. Add the code in Example 14-14 to the Click event of the New button.
Example 14-14. Creating a new Customer
Private Sub NewCust_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles NewCust.Click (currentCustomer = New BAWebService.Customer CustomerBindingSource.DataSource = currentCustomer End Sub
private void NewCust_Click(object sender, EventArgs e) { _currentCustomer = new Customer(); customerBindingSource.DataSource = _currentCustomer; }
3. Add the code in Example 14-15 to the Click event of the Save button. Remember that the InsertCustomer service will return either the new ID of the Customer or an error message, whereas UpdateCustomer returns the "Success"
or "Error" string plus the error message. The bulk of the code in this method is for dealing with the result of the InsertCustomer method.
Example 14-15. Handling new and modified Customers
Private Sub SaveCust_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles SaveCust.Click
Dim newCust = (_currentCustomer.ContactID = 0) Dim status = UpdateCustomer() 'insert customer returns the id of the new customer If newCust Then Dim newid As Integer If Not Integer.TryParse(status, newid) Then MessageBox.Show(status, "Web Service Error") Else _currentCustomer.ContactID = newid 'get fresh data for list FillCombo() ComboBox1.SelectedValue = newid End If Else MessageBox.Show("Update: " & status) End If End Sub
private void SaveCust_Click(object sender, EventArgs e) { var newCust = (_currentCustomer.ContactID == 0); var status = UpdateCustomer(); //insert customer returns the id of the new customer if (newCust) { int newid = 0; if (!(int.TryParse(status, out newid))) { MessageBox.Show(status, "Web Service Error"); } else { _currentCustomer.ContactID = newid; //get fresh data for list FillCombo(); comboBox1.SelectedValue = newid; } } else { MessageBox.Show("Update: " + status); } }
If successful, the InsertCustomer method returns the new ID, which is applied to the _currentCustomer in case the user continues to make changes to the object. If the user then clicks Save again, the UpdateCustomer method will be called rather than inserting that customer to the database a second time. Although you could add the new customer into the combo box, this method takes the easy route, which also requires another trip to the server, calling the FillCombo method to populate the Customers ComboBox with fresh data.
14.2.2.5. Testing your application You have now added all of the logic necessary to get the form to consume the web service. Run the form and watch it work. Be sure to put breakpoints in the form's code as well as the code for the service so that you can debug into the objects and take a closer look at what's going on under the covers.
14.2.2.6. The client-side Customer versus the server-side Customer It's important to keep in mind that this client application is not aware of the Entity Framework at all. The definition of theCustomer type it is using comes directly from the WSDL description and it is merely an object, not an entity object. No change tracking is occurring on the client side. Only the service tier has any knowledge of the Entity Framework. If you wanted to use the Entity Framework on the UI side of this solution, you would need to keep in mind that the BreakAwayModel's Customer type is very different from BAWebService.Customer . A common point of confusion for .NET developers using ASMX Web Services is that they attempt to cast the returned object to a local version of what should be the same object. But if you look at the Customer.DataSource in the Data Sources window or open the Reference.vb/Reference.cs class in the Web Reference section of the project, you can see that it is a very different object than the Customer entity.
14.3. Using the Entity Framework with WCF Services WCF is a much more sophisticated paradigm for building .NET services that .NET or other clients can consume. With regard to the Entity Framework, a big difference from using ASMX Web Services is that WCF's DataContract serializer is able to persist graphs. The WCF sample you'll build in this section will work with graphs and deal with the challenges introduced by performing updates on graphs that have come across a tier. In this walkthrough, you will build a WCF service that is similar to the web service you already built, but because you will be working with graphs, the methods will be a bit more complex. Rather than building a whole new client application, you will modify the existing Windows form from the web service walkthrough to work with the WCF service.
NOTE Chapter 22 revisits building WCF services with a different pattern. Although the complexity in the service you are about to build here is concentrated in the service methods, in Chapter 22 the service itself is more efficient, and the solution leans even more heavily on Data Transfer Objects. Figure 14-7 shows what the new client application will look like so that you have an idea of what is needed in the service.
Figure 14-7. The client application that will consume the WCF service
14.3.1. Building the WCF Service Creating a WCF service is very different from creating an ASMX Web Service. First you will define a set of interfaces that represent contracts for the necessary operations and then you will implement those interfaces as methods in a separate code file.
14.3.1.1. Creating the service application We'll start by creating the service application project: 1. Add a new WCF Service Application project to the solution. Note that the project has one file named IService1 and another named Service1 . You will use IService1 to describe what the service will do. It contains a list of the operations, but doesn't have the code for implementing them. This type of file is referred to as an interface , which is a very common programming construct. In WCF, this interface acts as a "contract," or a promise regarding what to expect of the service. It is also a place where you can define new data types that will be used to send data to or from the service. These are referred to as DataContract s and are related to the classes you saw created when you referenced the web service from the Windows Forms project earlier. 2. Add project references to System.Data.Entity and to the BreakAwayModel project. 3. Replace the ConnectionStrings section in the web.config file with the ConnectionStrings section from the BreakAwayModel project's app.config file.
NOTE Unless you are experienced with WCF, if you don't like the default name of Service1 , I wouldn't recommend renaming the class because you will have to make a number of changes elsewhere in the project and in the .config file. It's simpler in WCF to just create a new service. You can easily add a new WCF service item to the project and name it yourself, and then use that for the walkthrough.
14.3.1.2. Defining the operations the service will provide The operation contract is defined in the interface, IService1. Each method has an OperationContract attribute to indicate that it is an operation that is part of the contract for your service: 1. Open the IService1 file. 2. Delete the default (example) operations for GetData and GetDataUsingDataContract. 3. Add an Imports or using statement to the class for BAGA. 4. Add the OperationContract methods in Example 14-16 into the IService1 interface. Note that the String for the return type of GetCustomers and GetTrips is a placeholder.
Example 14-16. Defining the service operation contracts
_ Function GetCustsandTrips() As String
_ Function GetCustomer(ByVal custID As Integer) As Customer
_ Function UpdateCustomer(ByVal cust As Customer) As String
_ Function InsertCustomer(ByVal cust As Customer) As String
_ Function DeleteCustomer(ByVal custID As Integer) As String
[OperationContract] string GetCustsandTrips();
[OperationContract] Customer GetCustomer(int custID);
[OperationContract] string UpdateCustomer(Customer cust);
[OperationContract] string InsertCustomer(Customer cust);
[OperationContract] string DeleteCustomer(int custID);
14.3.1.3. Defining the DataContract classes the service will use In the web service example, you created a DTO called ShortContact. In this WCF service, you'll need ShortContact as well as a few other custom classes. In WCF these are called DataContracts and each is defined by a DataContract attribute.
DataContract
classes become part of the contract of what you can expect from the service. The contract will say, "Not only will I provide this set of operations, but I
also will send and receive data that has the following schema." By creating classes that are DataContract s, you can provide this information in the WSDL of the service, and both the service and the client can use it easily. Each property you need to serialize is flagged as a DataMember. For C#, because of the need for the DataMember attribute, you don't get to use the auto-implemented properties this time. Sorry.
NOTE The entity classes an EDM generates are marked as DataContract classes so that they can automatically be used with WCF solutions. Therefore, the Customer class you'll use will automatically become part of the service contract. You'll need to create three classes: The first is familiar; it's the same ShortContact you used in the web service. The second, CustsandTrips , is designed to allow you to pass some pick lists back to the client in one call, rather than in two. It has one property that will encapsulate a list of ShortContacts and another that will encapsulate a list of Trips objects. The last, CustEdit , is for customers that the client will return and that need to be updated. It contains one property that encapsulates the Customer object (which will be a graph including the customer's reservations and the trip details for each reservation). The second property, ReservationstoDelete, is a List of integers that will allow the user to delete reservations from a customer. (We'll discuss this in more detail later in this chapter.) Add the classes in Example 14-17 to the IService1 file below the interface. There is a default CompositeType class that you can delete.
Example 14-17. Creating the service's DataContract classes
= 0; i--) { var res = custtoDelete.Reservations.ToArray()[i]; context.DeleteObject(res); } context.DeleteObject(custtoDelete); } context.SaveChanges(); return "Success"; } catch (Exception ex) { return ex.Message; } } }
What Exactly Is Being Deleted When You Delete Inherited Objects? Although customers are in a separate database table than contacts, because they derive from contacts in the model, when the Entity Framework sees an instruction to delete a Customer it will delete the Contact record as well, even though this doesn't make sense in the database
schema or even in the business logic—it would be handy to remove a customer but leave the contact information intact. If you did want to perform this action, your best bet would be to use a stored procedure. This would be represented as a function in the model and can be called with EntityClient, as you saw in Chapter 13.
14.3.1.8. Updating the ObjectGraph The last method to fill out is the UpdateCustomer method. Updating just the Customer entity is simple. You did this in the web service in the previous sample. But this is not a single object; it is a graph. Not only will you need to update the customer, but you will also need to deal with its reservations. The reservations might be modified, new, or even deleted. So, although you are updating the Customer overall, you have a lot more logic to consider in this method.
14.3.1.9. Client rules for identifying changes in an EntityCollection The states for the reservations that need to be dealt with are newly added reservations, preexisting reservations that have been modified, or reservations that need to be deleted. Remember that an object's EntityState is stored in ObjectStateEntries. The Reservation objects will have no idea about their state, which means the service will need to determine the state of the Reservation s based on a number of assumptions. These assumptions will require that the consuming client follow some rules to ensure that the service will come up with the correct conclusions about the state of each Reservation. New Reservation s do not need to be too challenging, as you can identify them by the fact that their ReservationID has not been created yet and therefore is equal to 0. As long as the client does not populate the ID or does not remove the ID value from preexisting reservations, this assumption will work. Reservation s
with a ReservationID value that is greater than 0 should be those that preexisted. These will be either modified or unchanged.
NOTE The service won't need to do anything with unchanged reservations, so the client could remove these before returning the graph to the service, thereby reducing the amount of data sent over the wire. Another rule the client needs to follow is that it must not delete an object if it wants it to be deleted from the database. If the object is deleted on the client side, it will not be returned to the service and will therefore be ignored. In this service, we will attack the deleted-object problem by requiring that the client sends back a list of the IDs of objects that should be deleted. That is the purpose of the ReservationstoDelete property of the CusttoEdit class you defined in the service interface.
14.3.1.10. The UpdateCustomer method This code for the UpdateCustomer method begins similarly to the Update method in the web service. It first extracts the Customer object from the incoming type into the customer
variable. Then it performs a query to get a current version of that Customer from the database, along with the Customer's Reservations.
Finally it uses ApplyPropertyChanges to update that Customer entity with the values of the Customer that came from the client. Enter the code in Example 14-25 into the UpdateCustomer method.
Example 14-25. Code for the UpdateCustomer method, with placeholders
Public Function UpdateCustomer(ByVal cust As CusttoEdit) As String _ Implements IService1.UpdateCustomer Try Dim customer = custEdit.Customer Using context As New BreakAwayEntities Dim custtoUpdate = (From c _ In context.Contacts.OfType(Of Customer) _ .Include("Reservations") _ Where c.ContactID = customer.ContactID) _ .FirstOrDefault If Not custtoUpdate Is Nothing Then context.ApplyPropertyChanges("Contacts", customer)
'[Code for Existing Reservations will go here] '[Code for New Reservations will go here] '[Code for Deleted Reservations will go here]
Context.SaveChanges()
End If End Using Catch ex As Exception Return "error: " & ex.Message End Try End Function
public string UpdateCustomer(CusttoEdit cust) { try { var customer = cust.Customer; using (BAEntities context = new BAEntities()) { var custtoUpdate = ( from c in context.Contacts.OfType() .Include("Reservations") where c.ContactID == customer.ContactID select c) .FirstOrDefault();
if (custtoUpdate != null) { context.ApplyPropertyChanges("Contacts", customer);
//Code for Existing Reservations will go here; //Code for new Reservations() will go here; //Code for Deleted Reservations will go here;
context.SaveChanges(); } return "Success"; } } catch (Exception ex) { return "Error: " + ex.Message; } }
14.3.1.11. Why call ApplyPropertyChanges if there were no changes? If the client has returned unchanged objects, after calling ApplyPropertyChanges with these, it will be clear to the SaveChanges method that there is no reason to construct update commands. So, although the ApplyPropertyChanges call might seem unnecessary for those objects, SaveChanges will ignore it and no wasted calls will be made to the database.
14.3.1.12. Handling existing reservations The first placeholder is for updating existing reservations. Remember that ApplyPropertyChanges only affects the scalar values. You will need to update any related data separately. Using the same logic as you used earlier, call ApplyPropertyChanges for each Reservation . The bulk of the code here is for handling the possibility that another user or process has deleted the reservation from the database. Replace the Existing Reservations placeholder with the code in Example 14-26.
Example 14-26. Existing Reservations logic for the UpdateCustomer method
For Each res In customer.Reservations _ .Where(Function(r) r.ReservationID > 0) Try context.ApplyPropertyChanges("Reservations", res) Catch ex As InvalidOperationException If ex.Message.Contains("does not contain an ObjectStateEntry") Then 'Reservation no longer exists in database 'ignore or insert business logic End If End Try Next
foreach (var res in customer.Reservations .Where((r) => r.ReservationID > 0)) { try { context.ApplyPropertyChanges("Reservations", res); } catch (InvalidOperationException ex) { if (ex.Message.Contains("does not contain an ObjectStateEntry")) { //Reservation no longer exists in database //ignore or insert business logic } } }
The preceding code iterates through all reservations coming in from the client that have an existing ID, and then uses ApplyPropertyChanges to update the reservation's matching entity in the context. Recall that the query at the beginning of the method eager-loaded reservations using Include ; therefore, the customer's reservations will be in the context. If a user is editing a customer, and another user or process deleted the reservation since the first user called the GetCustomer operation, ApplyPropertyChanges will throw an exception. Unfortunately, it doesn't throw a specific exception, just anInvalidOperationException with no inner exception. Therefore, you must have a way to determine the cause of the problem. The exception's message will read as follows: "The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'BAGA.BreakAwayModel.Reservation'."
The preceding code tests for part of this string, and in this scenario it chooses to just ignore the reservation since someone else has deleted it.
14.3.1.13. Dealing with new reservations The code for handling new reservations is next. Replace the New Reservations placeholder with the code in Example 14-27.
Example 14-27. New Reservations logic for the UpdateCustomer method
Dim newres = customer.Reservations _ .Where(Function(r) r.ReservationID = 0) For resi = 0 To newres.Count - 1 Dim res = newres(resi)
Dim tripEntityKey = res.Trip.EntityKey res.Trip = Nothing custtoUpdate.Reservations.Add(res) res.TripReference.EntityKey = tripEntityKey Next
var newres = customer.Reservations .Where((r) => r.ReservationID == 0) .ToArray(); for (var resi = 0; resi < newres.Count(); resi++) { var res = newres[resi]; var tripEntityKey = res.Trip.EntityKey; res.Trip = null; custtoUpdate.Reservations.Add(res); res.TripReference.EntityKey = tripEntityKey; }
This code begins by using a For Next block rather than a For Each block. This is not random. Within the block, you will be taking the new reservation from the incoming Customer and attaching it to the Customer in the context. As a result, it will no longer be part of the incoming Customer's Reservations collection and the enumeration will break.
NOTE The pattern you see in this code is similar to the pattern in the RemoveTripsfromGraph method, except that you are adding the reservation to the Customer entity before reapplying the TripReference.EntityKey value.
14.3.1.14. Deleting reservations The last piece of the UpdateCustomer method deals with reservations the user deleted. The client application must send a list of ReservationID s that need to be deleted. The list is contained in the ReservationstoDelete property of the CusttoEdit type. Replace the Delete Reservations placeholder with the code in Example 14-28.
Example 14-28. Deleted Reservations logic for the UpdateCustomer method
For resi = 0 To cust.ReservationsToDelete.Count - 1 Dim resid = cust.ReservationsToDelete(0) Dim oldReservation As New Object Dim resEKey = New EntityKey("BAEntities.Reservations", _ "ReservationID", resid) If context.TryGetObjectByKey(resEKey, oldReservation) Then context.DeleteObject(CType(oldReservation, Reservation)) End If Next
for (var resi = 0; resi < cust.ReservationsToDelete.Count; resi++) { var resid = cust.ReservationsToDelete[0];
object oldReservation = new object(); var resEKey = new EntityKey("BAEntities.Reservations", "ReservationID", resid); if (context.TryGetObjectByKey(resEKey, out oldReservation)) context.DeleteObject((Reservation)oldReservation); }
In this chunk of code you will use the IDs the client has passed up to locate the Reservation in the context. TryGetObjectByKey is a twist on the GetObjectByKey method, which you learned about in Chapter 9, but this version emulates the TryParse methods of the .NET Framework and prevents an exception from being thrown if the object is not found.
NOTE Visual Basic's Option Strict On forces you to declare oldReservation as an object and not as a reservation. The reason is that when you pass oldReservation to TryGetObjectByKey
, the method is unable to perform a narrowing conversion from the expected Object type to a Reservation type. This doesn't make much sense,
but it's what you have to work with. You'll see that oldReservation is cast back to a Reservation type when it's passed into DeleteObject. Once the Reservation is located in the cache, you can use the DeleteObject method to ensure that it will be deleted from the database when SaveChanges is called.
14.3.1.15. Wrapping up the UpdateCustomer method with SaveChanges After all of these procedures have been performed on the context, it's time to call SaveChanges and send the required commands to the database. Once you've done that, we'll build the client so that you can test all of the functionality of this WCF service.
14.3.2. Building the Client to Consume the WCF Service Rather than starting from scratch, in this part of the walkthrough you can either modify the Windows client you built for the ASMX Web Service, or create a new Windows Forms Application project and copy the form from the previous client into the new project. The walkthrough assumes you have already performed one of these steps. Add two buttons, one for a listbox and the other for a combo box, to the form. Figure 14-10 shows the new form. Everything above the dotted line should already exist on the form from the earlier sample. Controls below the line are new for this example.
Figure 14-10. The modified client form
As you write the client code, you will encounter a handful of instances in which not having the business logic of the classes available on the client side forces you
to write a bit of extra code. This will mostly be for the sake of setting default values. In this walkthrough, because the focus is on learning about what to expect when interacting with a WCF service, it's not worth the effort of creating a business layer. The WCF solution in Chapter 22 will demonstrate how to provide some trimmed-down business classes for the WCF client to use. This will reduce (though not eliminate) the number of rules that the developer of consuming apps needs to be aware of. First you will add to the project a service reference for the new WCF service: 1. Right-click the new project in the Solution Explorer and select Add Service Reference from the context menu. This will open the Service Reference Wizard. 2. Click Discover to display all of the services in the solution. 3. Select Service1.svc and change its namespace to BAWCFService. 4. Click the Advanced button to open the Service Reference Settings page. 5. Under DataType, change the collection type to System.Collections.Generic.List. This will ensure that the collections returned by the service arrive in the client as lists rather than the default array. 6. Click OK to close the settings. 7. Click OK to close the wizard and add the service reference. Now you can bind the CustomerBindingSource to the new Customer class: 1. Click the CustomerBindingSource at the bottom of the Form Design window. 2. In the Properties window for the BindingSource , select the DataSource property and then click the drop-down arrow. 3. Expand the data sources until you see BAWCFService.Customer , as shown in Figure 14-11, and then select it. 4. Open the code window for the form. 5. Add an Imports /Using statement for the WCF service at the top of the form's code file (Example 14-29 ). It must be prefaced by the namespace of the client application. Note that the client project's assembly name is required and here you are seeing the name of my example projects—Chapter14WCFClientVB and Chapter14WCFClientCS.
Example 14-29. Importing the namespace for the service into the form
Imports Chapter14WCFClientVB.BAWCFService
using Chapter14WCFClientCS.BAWCFService;
6. Add a declaration for the _resDeleteList variable to the form, as shown in Example 14-30. This will be used to contain the list ofReservationID s that need to be deleted.
Example 14-30. Declaring the variable to contain the list of deleted reservations
Dim resDeleteList As List(Of Integer)
List resDeleteList;
Figure 14-11. Wiring up the WCF service's Customer object
At this point, you're ready to update the methods that call the proxy. Essentially, you will be retrofitting the existing methods to work with the WCF service, rather than the ASMX web service of the previous example: 1. Modify the Imports /using statement that points to BAWebService so that it points to BAWCFService. 2. Replace the GetCustomers method with the GetCustsandTrips method shown in Example 14-31.
Example 14-31. The new GetCustsandTrips method
Private Function GetCustsandTrips() As CustsandTrips Using proxy As New Service1Client() Return proxy.GetCustsandTrips() End Using End Function
Private CustsandTrips GetCustsandTrips() { using (Service1Client proxy = new Service1Client()) { return proxy.GetCustsandTrips(); } }
3. Update the GetCustomer method to instantiate the new WCF proxy class, Service1Client, instead of the proxy class from the previous service, Service1 , as shown in Example 14-32.
Example 14-32. The modified GetCustomer method
Private Function GetCustomer(ByVal id As Integer) As Customer Using proxy As New Service1Client() Return proxy.GetCustomer(id) End Using End Function
private Customer GetCustomer(int id) { using (Service1Client proxy = new Service1Client()) { return proxy.GetCustomer(id); } }
4. The UpdateCustomer method now takes a CusttoEdit object as a parameter. Replace the call to UpdateCustomer with the code in Example 14-33 , which will construct the object and then pass it to the service operation.
Example 14-33. The modified UpdateCustomer method
Private Function UpdateCustomer() As String Dim status As String = Nothing Dim custedit = New CusttoEdit() custedit.Customer = _currentCustomer custedit.ReservationsToDelete = _resDeleteList Using proxy As New Service1Client() status = proxy.UpdateCustomer(custedit) End Using Return status End Function
private string UpdateCustomer() { string status = null;
var custedit = new CusttoEdit(); custedit.Customer = _currentCustomer; custedit.ReservationsToDelete = _resDeleteList;
using (Service1Client proxy = new Service1Client()) { status = proxy.UpdateCustomer(custedit); } return status; }
5. Delete the InsertCustomer method as it is no longer necessary. 6. Replace the FillCombo with the code in Example 14-34. The new combo box is named cboTrips and is populated with the list of trips that the service inside the CustsandTrips object returned.
Example 14-34. The modified FillCombo method
Private Sub FillCombo() Dim custtrips = GetCustsandTrips() With ComboBox1 .ValueMember = "ContactID" .DisplayMember = "Name" .DataSource = custtrips.Custs End With With cboTrips .ValueMember = "ID" .DisplayMember = "TripDetails" .DataSource = custtrips.Trips End With End Sub
private void FillCombo() { var custtrips = GetCustsandTrips();
comboBox1.ValueMember = "ContactID"; comboBox1.DisplayMember = "Name"; comboBox1.DataSource = custtrips.Custs;
cboTrips.ValueMember = "ID"; cboTrips.DisplayMember = "TripDetails"; cboTrips.DataSource = custtrips.Trips; }
14.3.2.1. Editing the configuration for the web service client There's an important task that will be much more memorable if you see the impact of not performing it: 1. Press the F5 key to run the Windows Forms application. You will quickly hit a Communication Exception telling you that you have exceeded the maximum message size quote for incoming messages. This is simple, but important, to fix.
When you created the reference to the WCF service, Visual Studio added some configuration information to the app.config file for the Windows application. These configuration settings are specific to communicating with the web service. You could edit the particular setting manually, or use the GUI tool for editing service configurations. Here is how to do the latter. 2. Right-click the app.config file for the Windows application in the Solution Explorer. 3. Look for Edit WCF Configuration on the context menu and select it.
NOTE If this option is not on the menu, you'll need to take a different route. Once you have used this editor, which is called the WCF Service Configuration Editor GUI, it will be on the context menu. From the Visual Studio menu, select Tools WCF Service Configuration Editor. From the editor's File menu, choose Open; then browse to the app.config file in the filesystem and open it for editing. 1. Expand the Bindings section to expose the single binding that was automatically created when the WCF service was referenced. 2. Click the binding. 3. In the binding's Properties window, locate the MaxReceivedMessageSize property and increase it by adding a 0 to the end of the default value, as shown in Figure 14-12. 4. Save the file and close the window.
Figure 14-12. Increasing the value of the MaxReceivedMessageSize property in the client's app.config file
14.3.2.2. Testing the form again With the message size problem fixed, you should be able to see how the form is interacting with the service at this point if you'd like. The Customer and Trips combo boxes will be filled and you can select customers to see the text boxes get updated.
14.3.2.3. Adding functionality to view the reservations In the new form, when a Customer is selected you will be displaying those reservations in the ListBox . We'll use a separate method for wiring up the ListBox and then call it from the SelectedIndexChanged method.
Add the ShowReservations method in Example 14-35 to the form's code.
Example 14-35. The new ShowReservations method
Private Sub ShowReservations() ListBox1.DataSource = Nothing If _currentCustomer.Reservations.Count > 0 Then With ListBox1 .ValueMember = "ReservationID" .DisplayMember = "TripDetails" .DataSource = _currentCustomer.Reservations End With End If End Sub
private void ShowReservations() { listBox1.DataSource = null; if (_currentCustomer.Reservations.Count > 0) { listBox1.ValueMember = "ReservationID"; listBox1.DisplayMember = "TripDetails"; listBox1.DataSource = _currentCustomer.Reservations; } }
NOTE DisplayMember
depends on the TripDetails property of Reservation , which will be available if you marked the property as a DataMember , as recommended earlier. If
you need to do that now, you will need to rebuild the model project, then rebuild the service, and then update the service reference. Update Service Reference is a context menu option when you right-click the service reference you already created. After this, the TripDetails property should be available. Now, call ShowReservations from the SelectedIndexChanged event of the Customers ComboBox. You'll also need to instantiate the resDeleteList variable in case the user deletes any of the customer's reservations. The method should look like Example 14-36 after adding the two lines shown in bold.
Example 14-36. The modified ComboBox1_SelectedIndexChanged method
Private Sub ComboBox1_SelectedIndexChanged _ (ByVal sender As Object, ByVal e As System.EventArgs) _ Handles ComboBox1.SelectedIndexChanged If ComboBox1.SelectedIndex > -1 Then _currentCustomer = _ GetCustomer(CType(ComboBox1.SelectedValue, Integer)) CustomerBindingSource.DataSource = _currentCustomer 'create list of customer's reservations and fill ListBox _resDeleteList = New List(Of Integer) ShowReservations() End If End Sub
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { if (comboBox1.SelectedIndex > -1) _currentCustomer = GetCustomer((Int32)comboBox1.SelectedValue); customerBindingSource.DataSource = _currentCustomer; _resDeleteList=new List(); ShowReservations(); } }
resDeleteList
needs to be reinstantiated for each customer so that you don't carry over IDs of deleted reservations from a previously selected customer.
14.3.2.4. Testing the form so far You are at another point where you can see in action what you've done so far. This time you'll see the currently selected customer's existing reservations populated in the listbox. The form should look something like Figure 14-13.
Figure 14-13. The form displaying the selected customer's reservations
14.3.2.5. Finishing the form Only a few tasks are left for the form's code. First, you need to make small changes to the Add Customer and Save Customer event handlers and add the code for adding and deleting reservations. Let's start with the Add Customer event handler, which needs to accommodate the fact that Reservations is no longer an EntityCollection . When the Entity Framework is available, the Reservations property of Customer is an EntityCollection . But there is no EntityCollection in the client application. Instead, the
property is a list of reservations, and if no reservations came across the wire with the customer, that list will need to be instantiated if you want to work
Reservations
with it. You'll need to do this in the method called when the user clicks the Add Customer button: 1. Modify the Click event method for the Add Customer button by adding a line of code to instantiate the Reservations property. This new code goes in between the two existing code lines, as shown in Example 14-37 in bold.
NOTE Because this solution is not sharing business logic with the consumer, the consumer needs to follow another rule. The TimeStamp fields for new customers and new reservations must be set to a default or WCF will not serialize them. You'll see this in the NewCust_Click method, and a bit later in the code for creating a new reservation.
Example 14-37. Modifying the code for adding a new customer
Private Sub NewCust_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles NewCust.Click
_currentCustomer = New Customer() 'explicitly instantiate a list for the Reservations property _currentCustomer.Reservations = New List(Of Reservation)() customerBindingSource.DataSource = _currentCustomer 'clear the reservations listbox and the customer selection listBox1.DataSource = Nothing comboBox1.SelectedIndex = -1 Dim newTimeStamp = System.Text.Encoding.Default.GetBytes("0x123") _currentCustomer.TimeStamp = newTimeStamp _currentCustomer.CustTimeStamp = newTimeStamp End Sub
private void NewCust_Click(object sender, EventArgs e) { _currentCustomer = new Customer(); //explicitly instantiate a list for the Reservations property _currentCustomer.Reservations = new List(); customerBindingSource.DataSource = _currentCustomer; //clear the reservations listbox and the customer selection listBox1.DataSource = null; comboBox1.SelectedIndex = -1; var newTimeStamp=System.Text.Encoding.Default.GetBytes("0x123"); _currentCustomer.TimeStamp = newTimeStamp; _currentCustomer.CustTimeStamp = newTimeStamp; }
2. Modify the Click event for the Save Customer button, which will call the UpdateCustomer method (see Example 14-38 ). If an error is returned to the status variable, you can display it with the MessageBox.
Example 14-38. Modifying the code for saving the customer record
Private Sub SaveCust_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles SaveCust.Click Dim status = UpdateCustomer() 'insert customer returns the id of the new customer If Not String.IsNullOrEmpty(status) Then MessageBox.Show("Update: " & status) End If
End Sub
private void SaveCust_Click(object sender, EventArgs e) { var status = UpdateCustomer(); //insert customer returns the id of the new customer if (string.IsNullOrEmpty(status)==false) MessageBox.Show("Update: " + status); }
14.3.2.6. Adding and deleting the reservations Adding reservations is similar in functionality to what you built in the WPF form where you added activities to a scheduled trip. The user will select a trip from the control and then click the new Add button to create a reservation using the selected trip. Then the reservation will be the current customer's list of reservations. The other new button will be used to delete a reservation that is highlighted in the Reservations listbox: cboTrips
1. Rename the new buttons so that one is for adding a reservation and the other is for deleting a reservation. 2. Add the code in Example 14-39 to the Click event handler for the Add button. In addition to setting the Trip property of the new Reservation, you'll need to set a few defaults such as ReservationDate and TimeStamp. Reservation.TripDetail does not know how to automatically formulate itself from Trip.TripDetails ; therefore, you need to explicitly set the value for the benefit of the listbox.
Example 14-39. Adding reservations
Private Sub AddRes_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles AddRes.Click Dim selTrip = CType(cboTrips.SelectedItem, Trip) Dim newRes As New Reservation() newRes.Trip = selTrip newRes.TripDetails = selTrip.TripDetails newRes.ReservationDate = System.DateTime.Now newRes.TimeStamp = System.Text.Encoding.Default.GetBytes("0x123") _currentCustomer.Reservations.Add(newRes) ShowReservations() End Sub
private void AddRes_Click(object sender, EventArgs e) { var selTrip = (Trip)cboTrips.SelectedItem;
Reservation newRes = new Reservation();
newRes.Trip = selTrip; newRes.TripDetails = selTrip.TripDetails; newRes.ReservationDate = System.DateTime.Now; newRes.TimeStamp = System.Text.Encoding.Default.GetBytes("0x123"); _currentCustomer.Reservations.Add(newRes); ShowReservations(); }
Note that you have bound the reservation to the customer by adding it to the Customer.Reservations collection and not by setting Customer to the Reservation.Customer
property. This is intentional. Without the ObjectContext , you don't get the automatic two-way relationship. Because you set the
Reservation.Customer
property, the reservation will still not be seen as part of the customer's collection of reservations. Therefore, since none of the code in
this Windows form relies on navigating from Reservations back to Customer, but you do need to go from Customer to its Reservations, you join the two by adding the Reservation to the Customer's EntityCollection . The final bit of code for modifying Reservations enables users to delete a reservation. It's important to remember here that you can't just delete the reservation without providing the service with some clue that it needs to delete the reservation from the database. This is where you will keep track of the IDs of any deleted reservations for a customer. 3. Add the code in Example 14-40 to the Click event of the Delete Reservation button.
Example 14-40. Deleting reservations
Private Sub DelRes_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles DelRes.Click If ListBox1.SelectedIndex >= 0 Then _resDeleteList.Add(CInt(Fix(ListBox1.SelectedValue))) 'find the reservation in the customer's Reservations & remove it Dim delRes = _currentCustomer.Reservations. _ Where(Function(r) r.ReservationID = CInt(ListBox1.SelectedValue)) _currentCustomer.Reservations.Remove(delRes.First) ShowReservations() End If End Sub
private void DelRes_Click(object sender, EventArgs e) { if (listBox1.SelectedIndex >= 0) { _resDeleteList.Add((int)listBox1.SelectedValue);
//find the reservation in the customer's Reservations & remove it var delRes = _currentCustomer.Reservations. Where(r => r.ReservationID == (int)listBox1.SelectedValue); _currentCustomer.Reservations.Remove(delRes.FirstOrDefault()); ShowReservations(); } }
After you add the reservation ID to _resDeleteList, the code removes the reservation from the Customer's Reservations and refreshes the listbox.
Why No EntityKey and EntityReference in the Client? Because the client is depending on the service to provide the classes and does not reference System.Data.Entity , it is not possible to create
EntityReferences
using EntityKeys as you have done in previous samples. The Trip class does not have an EntityKey property because in this client, it
does not inherit System.Data.EntityKey. So, there is no way to use the EntityKey for the TripReference.EntityKey value as you have done before. It is possible, however, to build complex types, as you can see in the method where newRes.Customer uses a Customer object as its value. This goes back to the logic in the web service where you had to find a way to remove the Trip entity from the Reservation , but redefine the relationship with an EntityKey . The service knows that a client will not be able to create an EntityKey , and therefore is able to assume that any relationships are in the form of actual objects.
14.3.2.7. Running the application and saving your changes Now you can truly run the client application and test things out. There is a big difference between the Windows Forms application you wrote in Chapter 8 and this one. In Chapter 8 , you were able to rely on an ObjectContext on the client to keep track of all of your changes. This made it possible to perform data entry on multiple customers before calling SaveChanges . In this application, however, you don't have that advantage. When you select a new customer, the current one and any changes you made to that customer will disappear. It's important to save before moving to a new customer. In a production application, you would have checks in place to ensure that the user doesn't inadvertently lose his changes. You can make plenty of additional tweaks to the Windows form to make it more user-friendly, but you've come to the end of the lessons that are related to consuming entities from a WCF service, so we'll move on.
14.3.2.8. Peeking under the covers Don't forget to set breakpoints and watch what is happening throughout the client code and the service code. The most interesting thing that happens under the covers is what you'll see when you check the SQL Profiler during the call to SaveChanges. This is where you can again witness the order of command events when you are dealing with inherited objects. When you add a customer, first a contact is added and then the customer using the newly generated contactID is added. If you created any reservations for the new customer, those are added last. You can see this order in the SQL Profiler screenshot shown in Figure 14-14.
Figure 14-14. Watching the insert in SQL Profiler
14.4. Summary In this chapter, you learned some of the patterns for using the Entity Framework in a service tier, and built an ASMX Web Service and a WCF service using tools in Visual Studio 2008. When objects are moved across tiers to services, they are serialized, which adds a few challenges to the solution. XML serialization used for web services does not serialize a full graph, whereas the serializer for WCF is able to do so. Therefore, the web service used explicit operations for inserts, updates, and deletes and dealt with saving only a single entity at a time, whereas the web service, in saving a graph, had to be prepared for a variety of EntityState s in the parent and children of the graph. The most daunting of the challenges when working across tiers is that although EntityObjects are serialized, the ObjectStateEntry objects that contain the change tracking information are not. This leaves you with no state information when your object reaches its destination. In the examples in this chapter, you solved this problem by retrieving values from the server prior to calling SaveChanges . This is one pattern for overcoming this problem, and you will learn more in later chapters. Web services and WCF are big topics unto themselves, and wonderful books are devoted solely to these technologies. The samples in this chapter provide some patterns that will be great for many scenarios, but not all. Later in this book you will learn more patterns, but more importantly, throughout the book you will gain the knowledge to achieve whatever architecture you choose for your service-based applications.
Chapter 15. Working with Relationships and Associations At this point in the book, you have worked extensively with entities that are related to one another. You have also experienced a number of scenarios where it was necessary to do extra work to an object because of its associations. For example, when you map stored procedures to entities, you are required to account for any associations the entity was involved in, even when it doesn't make sense to do so. Remember seeing the ReservationID of the BreakAwayModel as a parameter of the DeletePayment function? You learned that if anEntitySet is mapped using a
QueryView , every other related entity also needs to be mapped using aQueryView . In building a WCF service in Chapter 14 , you had to do a little extra work to make sure that when inserting new contacts, the context did not also attempt to create a new Trip entity. So much is going on behind the scenes with respect to how the Entity Framework manages relationships that it causes a lot of unexpected behavior, seemingly strange errors, and many rules that we need to follow. The rules are in place to maintain the integrity of these relationships. It's important to remember that the Entity Data Model (EDM)—because it is based on an Entity Relationship Model (ERM)—is a model of entities and relationships, not a model of entities that happen to have relationships. In the EDM, relationships are represented as associations , which are actual objects in the model and have the sameimportance as the model's entities. This is why it's so important to work within the boundaries that keep these relationships in sync with the entities. When you use the Entity Framework to interact with an EDM, its relationships are instantiated as objects, even though you have to do a little digging to see them. In this chapter, you'll learn how relationships and associations fit into the EDM, how they work both inside and outside the
ObjectContext , and what it means for a relationship to be an object. With this knowledge, you will be better prepared to manipulate relationships between entities, adding or removing relationships between objects in the way that the Entity Framework expects; you will be able to solve problems that arise because of relationships; you will even enhance your ability to build more meaningful Entity Data Models. You will also see this knowledge pay off in a big way when working with multi-tiered applications such as web services. The chapter is divided into two parts. The first part is devoted to teaching you how relationships and associations work in the Entity Framework, from the EDM to the EntityObject s. The second part will teach you how to perform a number of critical tasks relating to entity graphs.
15.1. Deconstructing Relationships in the Entity Data Model Many developers new to the Entity Framework have a lot of questions about relationships as they attempt to build Entity Data Models and write code to interact with entities. Having a better understanding of the fundamentals of these associations and how they work will allow you to create more powerful applications and better comprehend the Entity Framework's behavior. First we'll look at the model and then at Object Services. In the Designer, Association s are represented as lines between related entities. The Designer displays the multiplicity between the entities. Multiplicity defines how many items a particular end can have. The multiplicity of an end can be defined in one of three ways:
One The end must have one item, no less and no more. This is quite often a parent in a parent–child relationship.
Many The end can have many items. This is generally a collection of children in a parent–child relationship and it's possible that no items (zero) exist in this collection.
Zero or One The end can have either zero items or one item but no more than one. Many of the entity references you have worked with in the BreakAway model have Zero or One ends. For example, the Customer.PrimaryDestination field may not have a destination defined, and therefore there will be zero items at that end. If a destination is defined, there can be no more than one. As you learned in Chapter 2 , the common notations for these are 1 (One), * (Many), and 0..1 (Zero or One). The EDM Designer displays the relationships with this notation. When you hover your mouse pointer over a line representing an Association , you can see some additional details of the Association, as shown Figure 15-1.
Figure 15-1. An association represented in the Designer
In the Properties window of the Association, shown in Figure 15-2, you can see even more details and make modifications if necessary.
Figure 15-2. The Association's Properties window
By default, the AssociationSet has the same name as the Association . You may find it helpful to change the name of the AssociationSet , as shown in Figure 15-2 , so that when you're looking at the EDMX in the XML Editor, it will be obvious whether you are looking at the Association or the AssociationSet . It is not common, however, to work with the AssociationSet directly in code, and therefore this is not a change that I have ever made when customizing my own EDMs.
15.1.1. How Did the Entity Data Model Wizard Create the Association? The EDM Wizard created the FK_Address_Contact association shown in Figure 15-2 when it read the BreakAway database. If you are unfamiliar with how relationships are defined in a database, it will be helpful to read additional details about this. Figure 15-3 shows a portion of the database diagram for the BreakAway database. The diagram shows the Contact and Address tables as well as a visual representation of the 1:* (One to Many) relationship between Contact and Address . On the Contact side, the symbol for primary key is used because the primary key, ContactID
, is used in the definition of the relationship.
Figure 15-3. A primary key/foreign key relationship defined between the Contact and Address tables in the database
The ContactID field in the Address table is a foreign key. The relationship is known as a primary key/foreign key relationship , which is often described and represented as PK/FK . This relationship is defined in a foreign key relationship of the Address table named FK_Address_Contact , as shown in Figure 15-4.
Figure 15-4. The relationship defined by the table that contains the foreign key
The Contact table has no knowledge of this relationship; the relationship and all of its rules (known as constraints) are contained in the FK_Address_Contact key. Figure 15-5 shows the details of this foreign key.
Figure 15-5. SQL Server Management Studio's property editor for defining a relationship between tables
Although a cascading delete rule is not being used in this case, you could define such a rule so that anytime a contact is deleted all of its related Address records will be deleted automatically. You might expect this to be defined in the Contact table, but instead it is defined in the Address table's foreign key. To use a cascading delete rule in this case, you would change the Delete Rule in Figure 15-5 from No Action to Cascade. The EDM Wizard reads all of this information, creates the FK_Address_Contact association, and wires it up to the relevant items in the model.
15.1.2. Additional Items Created in the Model
In addition to the association, a number of other items are created in the model as a result of this relationship:
Navigation properties Navigation properties are the most visible properties that result from the relationship, and you have used them extensively in this book already. The navigation property itself doesn't contain very much information, but it does have a pointer back to the association, which enables the navigation property to return the related entity or collection of entities.
AssociationSets Like an EntitySet, an AssociationSet acts as a container for associations that have been instantiated at runtime. If you have three contacts in the ObjectContext along with one or more addresses for those contacts, three instances of the FK_Address_Contact association will be in the context as well. Although you will rarely interact with these directly, the ObjectContext manages association objects as carefully as it manages entity objects. Because developers don't see these association objects, it is sometimes confusing when the Entity Framework follows particular patterns to maintain the associations. You'll learn more about this later in the chapter.
AssociationSet mapping The EntitySetMapping element in the model contains no information about navigations or associations, a fact you can confirm by viewing it. Only the scalar properties are mapped. All the relationship information is contained in the AssociationSetMapping element for the association that is bound to the entity. You have also seen that you can create or view these mappings in the Designer.
15.1.3. Navigation Properties Are Not Required Although an association is always bidirectional, navigating with properties doesn't necessarily have to be. An interesting twist on relationships is that you are not required to have a navigation property in your model for every endpoint of a relationship. As an example, the business logic of your application may define that you will frequently need to navigate from a contact to its reservation, but that you will never have to navigate from a reservation back to the contact, meaning Reservation.Contact and Reservation.ContactReference.Load will never be required, but Contact.Reservations will be useful. In this case, you could simply delete the Contact navigation property from the Reservation entity. However, you must do this in the raw XML as the Designer does not support deleting Navigation Properties. This won't impact the association between the two entities, and in an edge case you can always dig into the ObjectStateManager to get from the Reservation to the Contact . The plus side is that when you're coding or debugging, you won't have the unnecessary Contact and ContactReference properties constantly in your face.
15.1.4. Understanding How Associations Impact the Native Query The native query logic you saw when calling Load also applies to how initial queries are constructed. As you have seen, even if you do not request related data to be loaded, the EntityReference information still needs to be returned. There is a particular query scenario that can result in a surprising native query; however, as you look at it, you'll see that it does make sense after all. Imagine two entities: Contact and Address. Each Contact can have zero or one related Addresses (see Figure 15-6).
Figure 15-6. Two entities with a 1:0..1 relationship
In the database, Address contains a foreign key, ContactID , to the Contact table.
When you're querying for addresses, you use the ContactID field to build the ContactReference.Entity . The native query is simply a query of the Address table. But when you're querying the Contact table, the Contact entity needs to build the AddressReference.EntityKey. The AddressID does not exist in the Contact table in the database, however, and therefore an additional query of the Address table will be required. Here is a LINQ to Entities query that just requests contacts:
From c In context.Contacts Select con
from c in context.Contacts select c
The native T-SQL query, shown in Example 15-1 , includes a query of the Address table involving all the Address table fields to be sure to get whatever fields are required for constructing the EntityKey.
Example 15-1. T-SQL when querying an entity with 1:0..1 relationship
SELECT 1 AS [C1], [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent2].[Id] AS [Id1], [Extent3].[AddressID] AS [AddressID] FROM [dbo].[Contact] AS [Extent1] LEFT OUTER JOIN [dbo].[Employee] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id] LEFT OUTER JOIN (SELECT [addresses].[AddressID] AS [AddressID], [addresses].[street] AS [street], [addresses].[contactID] AS [contactID] FROM [dbo].[addresses] AS [addresses]) AS [Extent3] ON [Extent1].[Id] = [Extent3].[contactID]
Although this may be considered an edge case, imagine what the query would look like in a large model that contains a number of these types of relationships. This means that what you may think of as a simple query could in fact become something much more complex when it becomes a native database query.
NOTE This scenario came to my attention in the MSDN forums where a user with an extreme example of a model with many 0..1 associations attached to a single entity was seeing somewhat unwieldy SQL queries being executed in the database. Even with a simple query of the central entity he reported that the query added "52 unwanted left outer joins and pull[ed] back some 2,800 unwanted columns."
15.2. Deconstructing Relationships Between Instantiated EntityObjects When instantiated objects are joined together in a relationship they are referred to as a graph, or an entity graph . There is no such thing as unrequited love when working with related entities—every relationship in the Entity Framework is bidirectional. When you load, add, or attach one entity to another entity, you can navigate in both directions. When an address is added to a contact's Addresses collection, the contact object becomes accessible through the address's Contact property. If these items are attached to an ObjectContext , when the relationship is instantiated the Address.ContactReference will also be populated. You need to do this on only one end or the other for the complete relationship to be created. It is not necessary to explicitly make the connection in both directions.
15.2.1. Relationships Are First-Class Citizens Learning to accept relationships as first-class citizens is a challenge for many developers. Associations and the EDM rules that exist because of them are a big point of confusion for many developers who are new to the Entity Framework. Many questions in the forums can be attributed to a lack of understanding of how associations function within the Entity Framework. The ObjectContext tracks relationships independently of entities. In fact, relationships are instantiated as objects. When you add an Address to the contact's Addresses EntityCollection , a new object is created to represent that relationship. This object has an EntityState just as an entity does. When you remove anAddress from the collection, that relationship object is deleted—it is not removed, but its EntityState is set to Deleted . The Entity Framework resolves these relationships whenSaveChanges is called to make the appropriate changes in the database.
Relationship Span The term relationship span is not an official term. You may not even find it in the EDM documentation. But it is often used to describe the rules by which the Entity Framework handles related entities under the covers. When you create a query that traverses relationships, the Entity Framework will know not only how to construct the query, but also how to materialize the related objects. Relationship span defines that the ObjectContext will automatically attach an entity when you have joined it to another attached entity.
15.2.2. The "Platinum Rule" About Related Entities That Are Attached or Detached from the ObjectContext Understanding that the ObjectContext manages objects for each relationship that exists between a pair of entities can help you to better understand how the context deals with entities that are detached from the context. Remember that when an entity is detached, the context stops tracking its changes and stops managing its relationships. This is because the context destroys the ObjectStateEntry for that entity and any relationships that are dependent on that ObjectStateEntry. The Entity Framework has a "golden rule" that it will never do something you didn't explicitly tell it to do. The Entity Framework's deferred loading, which you learned about in Chapter 4 , is a great example of this, whereby the API won't make a trip to the database without your explicit instruction. Because of this rule, there is one form of nonrequested behavior that may surprise you when detaching an entity from the context. The behavior is the result of how the context manages the relationships between entities. If you detach an entity from the context and that entity is part of a graph, the entity also gets detached from its graph because the context can no longer manage its relationship. The fact that the context manages the relationship objects mandates that an object graph must be completely in or completely out of the ObjectContext . For example, if you have a customer graph that consists of aCustomer with Orders and OrderDetails and you detach the Customer from the ObjectContext, the Customer will be disconnected from the rest of the entities in the graph. Because the relationship objects that involved that Customer were destroyed along with theObjectStateEntry for that object, this means you can no longer traverse from the customer, which is not in the context, to the orders, which are in the context. Conversely, if you have an entity that is not in theObjectContext and you join it to an entity that is in theObjectContext , to follow the rule that the entire graph must be either in or out of the context the detached entity will automatically be attached to the context in order to be part of the graph. You will see this behavior repeated throughout this chapter as you look at the features and functionality regarding relationships.
Where Does the Platinum Rule Come From? When I first noticed that an entity had been attached to theObjectContext even though I hadn't explicitly called for this in my code, I was a little taken aback because I had trusted the Entity Framework not to break what I thought of as "the golden rule": that is, it would not do anything I hadn't specifically told it to do. Once I understood why the entities were being automatically attached to the context when I was attaching them to a graph—and why that was necessary—I determined that this must be the Entity Framework's platinum rule, because it overruled the golden rule.
15.2.3. The Relationship Manager and the IRelatedEnd Interface Along with ObjectStateManager , Object Services provides a Relationship Manager to perform the tasks pertaining to relationships between entities being managed by the context. The Relationship Manager keeps track of how entities attached to the ObjectContext are related to each other. It's able to do this with the methods and properties that EntityCollection and EntityReference share through the IRelatedEnd interface, which they both implement.IRelatedEnd's methods include Add, Attach, and Load , among others. When these methods are called, or when one entity is simply set to another entity's navigation property (e.g., myAddress.Contact=myContact), the Relationship Manager kicks in. This may sound complex, but it is necessary so that Object Services has a dependable way to manage the many relationships that could exist at any given time. As you create and delete entities, attach and detach entities, and modify relationships, the Relationship Manager is able to keep track of all of this activity. When it comes time to call SaveChanges , the Relationship Manager plays a role that is just as important as that of ObjectStateManager . All of those updates you witnessed, in which related objects were taken care of automatically, were handled by the Relationship Manager. To have the flexibility that the Entity Framework provides at the coding level, it is necessary to have this complexity at lower levels. With an understanding of how things are working at the lower levels, interaction with related objects should become much easier to comprehend, anticipate, and implement.
15.2.3.1. The Relationship Manager provides related objects through late binding One of the jobs of the Relationship Manager is to "serve up" related entities when they are attached to theObjectContext . When you navigate to a related entity—for example, by requesting myAddress.Contact—the Relationship Manager will identify the existing relationship between the Address and Contact entities, find the correctContact entity in the ObjectContext, and return it. A related object that the ObjectContext is not managing is seen as any other property in .NET. A call tomyAddress.Contact when myAddress and its Contact are not attached to the context will merely return theContact as a regular property.
15.2.4. Experimenting with Relationship Span Here's a geeky test so that you can see how some of the plumbing works. Looking at this in detail will give you a better understanding of how the Entity Framework manages relationships and why some of the rules that might not otherwise make sense exist. Perform a query against the model that retrieves a single Reservation, as shown in Example 15-2.
Example 15-2. Retrieving a single entity
Dim res=context.Reservations.First
var res = context.Reservations.First();
Grab the new Unchanged ObjectStateEntries in the context, as shown inExample 15-3.
Example 15-3. Inspecting the ObjectStateEntries
Dim osm = context.ObjectStateManager Dim oses = osm.GetObjectStateEntries(EntityState.Unchanged)
var osm = context.ObjectStateManager; var oses = osm.GetObjectStateEntries(EntityState.Unchanged);
Now take a look at these ObjectStateEntries in the debugger, shown inFigure 15-7.
Figure 15-7. ObjectStateEntries in the C# debugger
You queried for a single entity, but three seem to be in the context. In fact, only the firstEntityEntry is an actual entity. It's the Reservation you queried for, as you can see inFigure 15-8.
Figure 15-8. The ObjectStateEntry for the queried Reservation
The last three EntityEntries are placeholders for theEntityKeys of the related data—for example, the relatedContact shown in Figure 15-9.
Figure 15-9. An ObjectStateEntry that is a placeholder for an entity even though the entity is not in the cache
Even if a related entity doesn't exist in memory, theObjectContext needs to be aware of any relationships that an existing entity (the Reservation ) might have. That's because of the rule (created to cover all scenarios) that states that when a reservation is deleted, the relationship to its contact must also be deleted. This makes sense when both entities have been pulled into the ObjectContext , but not when only the reservation is in there. While designing the Entity Framework, its creators decided it was safer to have an all-encompassing rule so that unexpected edge cases wouldn't result in errors. However, to satisfy the rule that the relationship must be deleted, the relationship must first exist. The pseudoentities were created during the query so that the relationships could be created without developers having to pull down additional data to satisfy the rule. This is why these extra entries exist—one for theContact , one for the relatedTrip , and one for theUnpaidReservation entity (which you created in Chapter 13 ). If the true entity is brought into the context at some point, theEntity value of the entry will be updated. A private property for these entries denotes them as EntityKeyEntries ; however, you won't see that name displayed in the debugger. As you read through this chapter, this knowledge will help you better understand some of the rules and behavior surrounding relationships.
15.2.5. Understanding Navigation Properties in Entity Objects On their own, navigation properties are almost meaningless. They are completely dependent on an association to provide access to related data. The navigation property does nothing more than define which association endpoint defines the start of the navigation and which defines the end.
A navigation property is not concerned with whether the property leads to an entity or anEntityCollection . The multiplicity in the association determines that, and the results are visible in the object instances where the navigation property is resolved. The property is resolved either as an entity plus an EntityReference , as with the Contact and ContactReference properties of the Address entity in Figure 15-10, or as an EntityCollection, as with the Addresses property of the Contact entity. You can also see this by looking in the generated classes for the model. The Entity Framework still needs to make this determination as it is materializing objects from query results, and then populate the objects correctly.
Figure 15-10. Resolving navigation properties as entities, EntityReferences and EntityCollections
15.2.5.1. EntityReference properties Navigation properties that return Entity and EntityReference properties need to be addressed together because they come as a pair, even though both may not be populated. When the navigation property points to the "one" or "zero or one" side of relationship of a 1:1 or 1:* relationship, that property is resolved as two public properties. One property contains the actual entity, and the other property
contains a reference to the entity. This reference contains the related entity's EntityKey , which is relative to a foreign key in a database. The EntityKey provides just enough information about that entity to be able to identify it when necessary. When you execute a query that returns addresses, the ContactID from the Addresses table is used to construct theEntityKey for ContactReference. Even if the contact does not exist in memory, the ContactReference property provides the minimal information about theContact that is related to the Address.
15.2.5.1.1. EntityReference.Value The value object of an EntityReference shows up in two places. The first is the navigation property Reservation.Customer) ( and the second is within the EntityReference property (Reservation.CustomerReference). You can see this in Figure 15-11, where the Customer is loaded into the ObjectContext and therefore is hooked up with theReservation entity.
Figure 15-11. The Customer property of the Reservation entity as the actual property and as the value of the EntityReference property, CustomerReference
15.2.5.1.2. What if there is no EntityReference In many scenarios, the "one" side of a relationship is required, such as the constraint in the BreakAway model that says a reservation can't exist without a related customer. However, in some relationships the target is 0..1, meaning the related end isn't required (e.g., if a customer's preferred activity does not need to be specified). In the case where there is nothing on that end, the navigation property (Customer.PrimaryActivity) will be null. The EntityReference will exist, but its EntityKey and Value will be null (Nothing in VB), as shown in Figure 15-12 and Figure 15-13.
Figure 15-12. An unpopulated EntityReference, which will have no EntityKey and Value
Figure 15-13. The VB debug view of the empty EntityReference
15.2.5.2. EntityCollection properties The other type of IRelatedEnd is an EntityCollection. EntityCollection is a generic class that is a container for entities of a particular type and is used by a navigation property to point to the "many" end of an association. Contact.Addresses is an EntityCollection of Address types.
NOTE EntityCollection is not based on other Collection classes in .NET. In fact, it implements an Entity Framework interface in the System.Data.Objects.DataClasses namespace, called IRelatedEnd. You have worked with EntityCollection s in many of the examples in this book already. Although you'll probably work withEntityCollection most frequently through a navigation property, it is also possible to instantiate anEntityCollection in memory and work with it directly. You cannot attach an EntityCollection to an entity through its navigation property—for example, with: MyContact.Addresses=myAddressEntityCollection
The EntityCollection is the navigation property (when the target is the "many" side of an association). You need to insert items into the collection itself, which you can do explicitly or by setting the EntityReference on the child object, which you'll see next. You can also remove items from the collection as needed.
15.2.6. Referential Integrity and Constraints It is possible to place constraints in both the database and the EDM to ensure the integrity of the data. The point at which these constraints are checked in the pipeline varies. Many developers approach the Entity Framework with the assumption that these constraints will be checked when their code is manipulating the entities. For example, if you delete an order, when you call ObjectContext.DeleteObject it would be nice to have the Entity Framework tell you "Hey, there are still line items for this order. You can't do that."
15.2.6.1. Constraints that are not checked until they hit the database Unfortunately, many constraint checks must be deferred to the database, because it is common for some dependent data not to be loaded into your application. Even if the Entity Framework did alert you to those details and you removed them as well, what if the database contains more order detail data that you hadn't loaded for some reason? This would make you feel uncomfortable deleting the order and sending that instruction to the database, which would throw an error anyway because of the other details that are still present. Other constraint checks that, for the same reason, can't be made on the client side are for uniqueness, primary keys, and foreign keys. An EntityReference might contain a key value for data that is not loaded into theObjectContext but that does exist in the database, so
only the database can provide that check. In Chapter 4 , you saw how database constraints were specified in the Store Schema Definition Layer (SSDL) portion of the model, which only declares the existence of the constraints in the database. It is also possible to apply additional constraints on the Conceptual Schema Definition Layer (CSDL) side, but these are generally meant to validate the model and won't have much impact on updates. This is why it's important to catch exceptions and deal with them when you callSaveChanges.
15.2.6.2. Constraints that the Entity Framework checks when SaveChanges is called The model itself can perform checks on the multiplicity of associations; however, this does not happen untilSaveChanges is called. For example, the 1:* relationship between Contact and Address means you can have one and only one contact related to an Address—not five and not zero. However, it is possible to create a new Address in code without assigning aContact or ContactReference, as shown in Example 15-4. When you add the Address to the context, the Entity Framework can't possibly know whether you plan to create a relationship.
Example 15-4. Code that will not incur a constraint check
Dim add = Address.CreateAddress(0, "Home", Now()) context.AddToAddresses(add)
var add = Address.CreateAddress(0, "Home", Now()); context.AddToAddresses(add);
But when it comes time to call SaveChanges, after any custom SavingChanges logic has been implemented, it's pretty obvious that if there is no relationship by now, there never will be. That's when an UpdateException is thrown before the command is even created to send to the database. The UpdateException provides the following explicit message, which is great for debugging and for logging: Entities in 'BAEntities.Addresses' participate in the 'FK_Address_ContactSet' relationship. 0 related 'Contact' were found. 1 'Contact' is expected.
You should be able to catch a scenario like this before your code goes into production. Otherwise, you'll either want to check for this constraint yourself in business rules (e.g., in SavingChanges ) or, as a last resort, catch the exception and either deal with the orphaned address or ask the user to do something about it.
NOTE
We will explore UpdateException along with other Entity Framework exceptions inChapter 18.
15.2.7. Deletes and Cascading Deletes Most databases support cascading deletes, which are used to automatically (and unquestionably) delete all of the children of a parent when that parent record is deleted. Cascading deletes are supported in the Entity Framework as well, but with caveats. The biggest caveat is that the Entity Framework can perform cascading deletes only on objects that are in memory. This could cause problems if a database constraint is enforcing referential integrity, and if the database does not also have a cascading delete set up on the same set of data. In this case, if the database still contains children, an error will be thrown.
15.2.7.1. Cascading deletes in the database Referring back to Figure 15-5, a cascading delete in the database is defined in theKey definitions of the "child" or dependent table. If data in the related table is deleted, all of the related rows of the dependent table will be automatically deleted.
15.2.7.2. Cascading deletes in the EDM You can also define a cascading delete in the EDM. The Designer does not support this, and therefore you need to do it manually in the XML Editor. This facet is added to the Association definition in the CSDL. The EDM Wizard can identify cascading deletes in a database. Microsoft's sample database, AdventureWorksLT, defines cascading deletes in many relationships, including the relationship between SalesOrderDetails and its "parent," SalesOrderHeaders. The wizard recognizes and includes that cascading delete in the Association definition, as shown inExample 15-5.
Example 15-5. An Association's OnDelete action set to Cascade as defined in the CSDL
The following code spins up a new ObjectContext and queries for the firstSalesOrderHeader along with its SalesOrderDetails. In this case, two SalesOrderDetails are returned along with theSalesOrderHeader. Then the SalesOrderHeader is deleted from the context, after which the ObjectStateManager is queried for all objects whose state isDeleted:
Using context As New AWLTEntities Dim ord = context.SalesOrderHeader.Include("SalesOrderDetail").First context.DeleteObject(ord)
Dim oses = context.ObjectStateManager.GetObjectStateEntries _ (EntityState.Deleted) context.SaveChanges End Using
using (AWLTEntities context = new AWLTEntities ()) { var ord = context.SalesOrderHeader .Include("SalesOrderDetail").First(); context.DeleteObject(ord); var oses = context.ObjectStateManager .GetObjectStateEntries(EntityState.Deleted); context.SaveChanges(); }
In the ObjectStateEntries that are returned, you can see that three entities are deleted: theOrder entity and the twoDetail entities. At the same time the context deleted the entities, it also needed to delete all of the association instances (RelationshipEntries ) that existed. Two of those seven RelationshipEntries are for relationships between the order and each detail record. The rest are for other associations that exist within those entities, such as Detail to Product, Order to Customer, Order to Billing, and Shipping Address. When SaveChanges is called, explicit commands are sent to the database to delete theSalesOrderHeader and the two SalesOrderDetail s, so although the database will perform a cascading delete, no more detail records will be available to delete. However, if another SalesOrderDetail for this order is added to the database in the meantime, the database's cascading delete will delete it.
15.2.7.3. Recommendation: Cascade in both the model and the database, or in neither Although you can add the OnDelete action to an association when the cascade is not defined in the database, this is not recommended because doing so will create incorrect expectations on the part of the developer, and unpredictable results. The recommendation is to use the OnDelete action in the database as a reflection of its definition in the database. If it exists in the database, it should exist in the model. If it does not exist in the database, it should not exist in the model. You can, of course, ignore the recommendations as long as your code is prepared for the possible repercussions.
15.3. Defining Relationships Between Entities Now that you have had a tour through the plumbing of how relationships work in the Entity Data Model and in instantiated objects, it's time to see how you can impact relationships in your code. Anytime you set one entity as the property of another (for example,Reservation.Customer=aCustomer ) or add an entity to an EntityCollection property of an entity (for example,Reservations.Payments.Add(aNewPayment) ) you are defining a relationship. In turn, the ObjectContext will create a new relationship object (technically, a
RelationshipEntry to represent this relationship. You can create relationships between entities in a number of ways. You saw several of them in the applications you built in previous chapters.
15.3.1. The CLR Way: Setting a Navigation Property The simplest way to create relationships between entities is to create the relationship the CLR way—by setting one entity as the property of another entity:
MyAddress.Contact = myContact
If you are starting with the child entity (e.g.,MyAddress ), this is pretty obvious. But if you are starting with the contact and you want to add the address to its collection, there's no reason not to switch your perspective and simply set the address's Contact property. It's simpler and has the same effect. This covers the most common scenarios, though you should be aware of the following subtleties: If both objects are detached, no relationship object will be created. You are simply setting a property the CLR way. If both objects are attached, a relationship object will be created. If only one of the objects is attached, the other will become attached (thanks to the "platinum rule") and a relationship object will be created. If that detached object is new, when it is attached to the context its EntityState will be Added.
15.3.2. Setting an EntityReference Using an EntityKey If you want to populate an EntityReference but you do not have an entity in hand, you can create theEntityReference using only an EntityKey . This requires that you know the value of theEntityReference 's key. And in fact, you did this when creating reservations in some of the example applications earlier in the book. This allows you to create a foreign key for an entity without having the related data in memory. For example, in the customization you made to SavingChanges , you used an EntityReference to set the defaultCustomerType of a new
Customer. Example 15-6 shows the relevant code from that method.
Example 15-6. Defining an EntityReference with an EntityKey
cust.CustomerTypeReference.EntityKey = New EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1)
cust.CustomerTypeReference.EntityKey = new EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1);
This is especially handy, because although you can set default values for scalar properties in the model, it's not possible to set a default value for a navigation property. By setting the CustomerTypeReference.EntityKey , you can define the CustomerType without having to load the actualCustomerType object into memory. When the Customer is saved, the CustomerTypeReference is resolved, and in the database theCustomer record has its CustomerTypeID foreign key value set to 1.
Synchronizing EntityReference.Value and Its EntityKey The ObjectContext automatically synchronizes the value of theEntityReference (when the entity exists in the ObjectContext) and the EntityKey of the EntityReference property. When both entities are attached, the EntityKey and Value will stay in sync. If you change one, the other will automatically change. However, you should be aware of the following scenarios regarding related entities that are not attached to the context: If both entities are detached, the EntityReference Value and EntityKey will not be synchronized because that is a function only the ObjectContext can perform automatically . It is possible to create an EntityReference whose EntityKey represents one entity and whose value represents another entity, however. When attaching an entity to the ObjectContext that has an EntityReference, the value (when it is not null) will take precedence, and if the Value and EntityKey are out of sync, the
EntityKey will be updated to match theValue.
15.3.3. Loading, Adding, and Attaching Navigation Properties There are a number of ways to populate navigation properties using methods provided by the IRelatedEnd interface, which EntityCollection and EntityReference implement. Depending on the type (collection or reference) from which
you call the methods, the Entity Framework performs slightly different methods in the background to achieve the goal.
15.3.3.1. Rules, rules, rules A lot of rules dictate when you canLoad, Add , or Attach navigation properties. When you are unfamiliar with how relationships function in the Entity Framework, you will be more likely to unknowingly break these rules and hit an exception. As you settle into a better understanding of relationships, all of these rules that you are about to learn on the following pages will become second nature and you will no longer think of them as rules that you need to remember, but as features of the Entity Framework. However, looking at them now, along with the reason behind each rule, will help you get to that better level of understanding.
15.3.4. EntityReference.Load and EntityCollection.Load Examples:
Contact.Addresses.Load Address.ContactReference.Load
NOTE Although we discussed theLoad method earlier in the book, here we will dig a little deeper. You can call Load from entities that are attached to theObjectContext. Calling Load will cause a query to be created and executed to retrieve the related data that Load requested. If you have a Contact and call
Contact.Addresses.Load , all of the addresses belonging to that contact will be retrieved from the database. One condition allows you to callLoad from detached entities: when the entity was created as a result of aNoTracking query. Example 15-7 calls Load on a Detached entity.
Example 15-7. Calling Load on a detached entity
Dim query = context.Reservations query.MergeOption = Objects.MergeOption.NoTracking Dim res = query.First res.CustomerReference.Load() ' a.City == "Seattle") select con;
NOTE The ConfigurationManager class can be tricky to find. You need to reference the System.Configuration namespace in your project; then you can get to System.Configuration.ConfigurationManager.
Unfortunately, the Metadata parameter is a string, so there's no strongly typed way to construct it. However, you can use one of the common DbConnectionStringBuilder classes, such as SqlConnectionStringBuilder , to programmatically construct the provider connection string (StoreConnection) of the EntityConnectionString.
16.1.3.1. Dynamic EntityConnections One of the overloads for the EntityConnection constructor allows you to pass in a model that is in memory along with a database connection. This allows you to work with models that may not be stored in a particular file. For example, if you were to define different models and store them in a database, at runtime the code would determine which model to work with. You could then load the model's XML from the database into memory—for example, into an XMLReader—and then create an EntityConnection with the XMLReader . Once this connection has been instantiated, you can use it with an ObjectContext to query that model.
NOTE The next version of Entity Framework (in Visual Studio 2010) will take advantage of the in-memory metadata with EF's new agile programming capabilities. This will be a scenario with EF that will not require developers to create a model in advance. More is involved in this scenario because you will also need to have code that can determine what is in the model at runtime. The Entity Framework's MetadataWorkspace allows you to determine this, and as such create a completelydynamic application. See Chapter 17 for more about MetadataWorkspace.
16.1.4. Opening and Closing Entity and Database Connections EntityConnection.Open loads the metadata files (to read the model) if they have not yet been loaded into application memory. This method calls the database provider's Connection.Open as well. EntityConnection.Close will, in turn, call the database connection's close method. When an ObjectContext executes a query internally it creates anEntityConnection, and an EntityCommand then executes the command. As soon as the data has been consumed, whether you call a method such as ToList to read all of the data at once or you iterate through the data and come to the end, the context will close the EntityConnection, which in turn closes the database connection. Opening and closing connections to the database is something that many developers fret about because we want to make the most efficient use of available resources. You may want to control when the open and close happen.
16.1.4.1. Manually opening and closing connections When working with EntityClient , you need to explicitly create and open anEntityConnection before you can have your query executed. When working with the ObjectContext directly or through LINQ to Entities, the default behavior is that theObjectContext opens and closes connections as needed and as efficiently as possible. It is possible, however, to override that behavior and explicitly control when EntityConnection is opened and closed. You have a few options here. You can manually open the connection and let it be closed implicitly when the context is disposed or you can manually open it and manually close it. One of the advantages of opening and closing the connection yourself is that you can prevent the connection from being opened and closed numerous times when you are making a bunch of rapid-fire queries or performing a query followed by deferred loading. You can see the difference in the following examples.
16.1.4.2. Default behavior 1: Many calls on a single connection Example 16-3 performs a single query, iterates through the results, and callsLoad and EntityCollection for some of the results. Each call to Load hits the database on the same connection because the context hasn't finished reading through the query results.
Example 16-3. The initial query and subsequent loads executed on the same connection
Using context As New BAEntities Dim cons = From con In context.Contacts Where con.FirstName = "Jose" For Each c In cons If c.AddDate < New Date(2007, 1, 1) Then c.Addresses.Load() End If Next End Using
using (BAEntities context = new BAEntities()) { var cons = from con in context.Contacts where con.FirstName == "Jose" select con; foreach (var c in cons) { if (c.AddDate < new System.DateTime(2007, 1, 1)) { c.Addresses.Load(); } } }
Only a single connection is used in this case because a connection is not closed until the results have been consumed. Therefore, because you are iterating through the resulting contacts, the connection remains open until you have reached the first contact. In the meantime, the additional calls to the database to load the addresses use that same connection. The MultipleActiveResultsSet setting in the connection string allows multiple streams to be read on the same connection. MultipleActiveResultsSet, also known as MARS, was introduced to ADO.NET in .NET 2.0.
16.1.4.2.1. Default behavior 2: Multiple connections The set of queries in Example 16-4 opens and closes a connection twice. It closes the first connection whencons.ToList is called, as this forces the entire set of results to be consumed at once. Recall that a connection is disposed when its results have been fully consumed. Therefore, a new connection needs to be created for the second query.
Example 16-4. Two queries, each getting their own connection
Using context As New BAEntities Dim cons = From con In context.Contacts Where con.FirstName = "Jose" Dim conList = cons.ToList Dim allCustomers = From con In context.Contacts.OfType(Of Customer)( Dim allcustList = allCustomers.ToList End Using
using (BAEntities context = new BAEntities()) { var cons = from con in context.Contacts where con.FirstName == "Jose" select con; var conList = cons.ToList(); var allCustomers = from con in context.Contacts.OfType() select con; var allcustList = allCustomers.ToList(); }
16.1.4.2.2. Forcing an explicit connection To change the default behavior that happens inExample 16-4 , you can force the connection to be reused by manually opening the connection as shown in Example 16-5 . Then you can either explicitly close it or let the context automatically close it when the context goes out of scope. or let the garbage collector dispose it when the time comes.
Example 16-5. Forcing queries to use the same connection
Using context As New BAEntities context.Connection.Open() Dim cons = From con In context.Contacts Where con.FirstName = "Jose" Dim conList = cons.ToList Dim allCustomers = From con In context.Contacts.OfType(Of Customer) Dim allcustList = allCustomers.ToList context.Connection.Close() End Using
using (BAEntities context = new BAEntities()) {
context.Connection.Open(); var cons = from con in context.Contacts where con.FirstName == "Jose" select con; var conList = cons.ToList(); var allCustomers = from con in context.Contacts.OfType() select con; var allcustList = allCustomers.ToList(); context.Connection.Close(); }
16.1.4.2.3. Connection versus Connection.StoreConnection Although ObjectContext.Connection returns the EntityConnection , you can drill deeper, as you saw inFigure 16-1, and get the actual database connection using EntityConnection's StoreConnection property. If for some reason you want to have very granular control over the database connection, for example, by specifying the ConnectionTimeout , you can do so by working directly with theStoreConnection.
16.1.5. Taking Control of How Store Connections Are Disposed As with any data access performed in .NET, it's important that you dispose the database connection, because it is not a managed resource and the garbage collector will not clean it up. Lingering database connections are a common cause of server resource issues. Again, when you rely on the Entity Framework's default behavior, the database connection will be properly disposed. Disposing the ObjectContext will automatically close the EntityConnection and will close and dispose the database connection. You can either explicitly dispose the ObjectContext or wait for the garbage collector to do the job. However, in the latter scenario, that means the database connection is still hanging around until that time. In common usage scenarios with the Entity Framework, the worst offense (holding a connection open) should not be an issue, because as you have seen, the connection will be closed automatically. But if one of the triggers for closing a connection has not been executed—completing the consumption of query results, calling EntityConnection.Close, or disposing the ObjectContext —you could unwittingly be consuming extra resources. ObjectContext's Dispose method calls EntityConnection.Dispose if ObjectContext created the connection. In turn, EntityConnection.Dispose will call the Dispose method on the StoreConnection. The code behind ObjectContext.Dispose is shown in Example 16-6 so that you can see just how it works.
Example 16-6. The ObjectContext.Dispose method
Protected Overridable Sub Dispose(ByVal disposing As System.Boolean) If disposing Then If (Me._createdConnection AndAlso _ (Not Me._connection Is Nothing)) Then Me._connection.Dispose End If Me._connection = Nothing Me._adapter = Nothing End If End Sub
protected virtual void Dispose(bool disposing) { if (disposing) { if (this._createdConnection && (this._connection != null)) { this._connection.Dispose(); } this._connection = null; this._adapter = null; } }
NOTE An age-old debate in ADO.NET concerns whether you should close or dispose database connections. In fact,DbConnection.Close calls Dispose and DbConnection.Dispose calls Close. Close takes care of the critical connection resources, but the connection object itself is still there. So, if you are using the defaults with LINQ to Entities orObjectContext , the connection will be disposed. If you want to be sure the connection is disposed right away, you need to either explicitly make that call or be sure the ObjectContext is explicitly disposed. If you have created the EntityConnection explicitly, you have to either dispose it explicitly or wait for the garbage collector to dispose it; again, this in turn will dispose the database connection.
16.1.6. What About Connection Pooling? Spinning up a database connection is expensive in terms of resources. When a connection is closed, it can be left in memory to be reused the next time a connection is required, eliminating the cost of creating a new connection. This is called connection pooling. Developers often ask whether the Entity Framework does connection pooling. Because connection pooling is controlled by the database provider, the Entity Framework does not explicitly impact or interact with how connection pooling works. Instead, it relies on the provider's connection pooling. For more information on connection pooling in ADO.NET, a good starting point is the "SQL Server Connection Pooling (ADO.NET)" topic in the MSDN documentation.
16.2. The Entity Framework and Transactions Another question that is frequently asked about the Entity Framework is whether it uses transactions. A transaction defines a unit of work that can contain a number of actions, such as database updates. When all of the actions have completed successfully, the transaction is committed. If any of the actions fail, the transaction is "rolled back," which causes all of the actions to roll back. Therefore, if you have actions that depend on each other and one action fails, you don't have to manually undo those that have already occurred. Resources that provide the capability to process transactions, such as databases, can have their transactions be enlisted in .NET. Whether you have a number of updates on a single database connection within a single transaction, or you have a few of them combined with interactions on another database and possibly combined with work in message queuing, you can coordinate all of those individual transaction resource managers in a single transaction. When performing a SaveChanges operation, the Entity Framework implicitly wraps all of the commands in a database transaction; however, you can take control of transactions as well.
16.2.1. Why Use Your Own Transaction? Although the default use of DbTransaction takes care of operations on a single instance of a database connection, the TransactionScope class System.Transaction can coordinate operations across a variety of processes that use resource managers. Therefore, within a single transaction you could make calls to a database, to the Message Queue (MSMQ), or even to another database using ADO.NET. If one of those fails, System.Transaction will allow all of them to be rolled back together. System.Transaction
leverages the Windows Distributed Transaction Coordinator (DTC) to make this happen, albeit with more overhead than a simple DbTransaction .
But what is great about System.Transaction is that it will decide whether your actions need only the individual transaction (such as SQLTransaction ), or whether they need to escalate to a DTC so that multiple transactions can be orchestrated. In that way, you don't needlessly waste resources with the DTC, but you also don't have to explicitly control it.
It's important to understand that the Entity Framework can only leverage transactions with database interaction. You cannot use transactions to control and roll back modifications to the ObjectContext itself—not even the creation of entities when performing a query.
16.2.2. Understanding the Entity Framework's Default: Implicit Transactions The database constraint between Contact and Address in the BreakAway database makes a good test case for demonstrating the implicit transactions in the Entity Framework. An address cannot exist without a contact, yet no cascading delete is defined in the database to delete related addresses when a contact is deleted. Therefore, an attempt to delete a Contact entity without deleting its related addresses in code will cause the database to throw an error when SaveChanges is called. Let's take advantage of that and write some code to see the transaction in action. The code in Example 16-7 queries for a particular contact, deletes it from the ObjectContext , and then calls SaveChanges . To add a twist, the code also creates a new payment for a reservation. Remember that when you attach the payment to the reservation in the context, SaveChanges automatically pulls the payment into the context and inserts it into the database.
Example 16-7. An implicit transaction that will roll back
Using context As New BAEntities Dim con = context.Contacts.Where _ (Function(c) c.ContactID = 5).FirstOrDefault context.DeleteObject(con) Dim res = context.Reservations.FirstOrDefault Dim newPayment = New Payment With newPayment .Amount = "500" .PaymentDate = Now .Reservation = res End With context.SaveChanges() End Using
using (BAEntities context = new BAEntities()) { var con = context.Contacts.Where(c => c.ContactID == 5) .FirstOrDefault(); context.DeleteObject(con); var res = context.Reservations.FirstOrDefault; var newPayment = new Payment(); newPayment.Amount = "500"; newPayment.PaymentDate = System.DateTime.Now; newPayment.Reservation = res; context.SaveChanges(); }
The attempt to delete the contact from the database will fail because of the referential constraint.Figure 16-2 , a screenshot from SQL Profiler, shows what happens when SaveChanges is called.
Figure 16-2. The Entity Framework automatically forcing a rollback if any of the commands to the database fail
A transaction was created, and because the delete failed, the transaction is rolled back and the insert for the payment is not even bothered with. On the client side, an exception is thrown containing the error from the database, which offers a very clear description of the problem: "The DELETE statement conflicted with the REFERENCE constraint "FK_Address_Contact". The conflict occurred in database "BreakAway", table "dbo.Address", column 'ContactID'. The statement has been terminated."
This highlights a good reason to be sure to include exception handling around SaveChanges in cases where any constraints in the database are not constrained in advance in the model or in the application. In this example, SaveChanges caused two commands to be executed. Even if SaveChanges created only one command, it would still be wrapped in a database
transaction.
16.2.2.1. Where did the transaction come from? A DbTransaction is created within the SaveChanges method. If no exceptions are thrown during the actual command execution, DbTransaction.Commit is called.
16.2.2.2. Controlling AcceptAllChanges in a transaction ObjectContext.AcceptAllChanges
updates the object state of all of the entities being change-tracked. This will set the OriginalValues to whatever the current values are and
it will change their EntityState to Unchanged. During the SaveChanges process and after the transaction has been committed, AcceptAllChanges is automatically called, causing the ObjectContext to be up-to-date and its entities to match the data in the database. It's possible to indicate in the SaveChanges call that SaveChanges should not call AcceptAllChanges when the save is complete. All you need to do is pass a Boolean of False
as a parameter. The default is True , and you do not need to explicitly call it if you want the default behavior.
ObjectContext.SaveChanges(false)
If you override the default, you can then control when and (if necessary) how many times AcceptAllChanges should occur. This is especially useful when you're using your own transaction, since you may want to retry the save or just call AcceptAllChanges even when the transaction did not complete.
16.2.3. Specifying Your Own Transaction Just as you can override the default behavior with connections, you can also control transaction functionality. If you explicitly create your own transaction, will not create a DbTransaction . You won't create a System.Common.DbTransaction, though. Instead, when creating your own transactions, you need to use a
SaveChanges
System.Transaction.TransactionScope object.
You can use a transaction for read and write activities in the database, which means that this will work with both ObjectContext and EntityClient.
NOTE Remember that if you are using LINQ to Entities and you want to take advantage of ObjectContext behavior, you can cast the LINQ to Entities query to an ObjectQuery,
as you learned in Chapter 9.
Example 16-8 uses an explicit transaction to save a new customer to a database and, if the call to SaveChanges is successful, to add the customer's name to a completely separate database. The application has references to two different projects with EDMs. If something goes wrong with either database update, the TransactionScope will not be completed and both updates will be rolled back.
NOTE You'll need to create a reference in your project to System.Transactions.
Example 16-8. Creating your own System.Transaction for SaveChanges
Using context As New BreakAwayEntities Dim cust As Customer = Customer.CreateCustomer _ ("George", "Jetson", "A real space cadet",New DateTime(1962,1,1)) context.AddToContacts(cust) Using tran As New TransactionScope Try context.SaveChanges(False) Using altcontext As New altDBEntities altcontext.AddToContact(Contact _ .CreateContact(cust.LastName.Trim & ", " & cust.FirstName)) altcontext.SaveChanges() End Using tran.Complete() context.AcceptAllChanges()
Catch ex As Exception 'throw or handle database or Entity Framework exceptions Throw End Try End Using End Using
using (var context = new BAEntities()) { Customer cust = Customer.CreateCustomer ("George", "Jetson", "A real space cadet",new DateTime(1962,1,1)); context.AddToContacts(cust); using (TransactionScope tran = new TransactionScope()) { try { context.SaveChanges(false); using (altDBEntities altcontext = new altDBEntities()) { altcontext.AddToContact(Contact .CreateContact(cust.LastName.Trim() + ", " + cust.FirstName)); altcontext.SaveChanges(); } tran.Complete(); context.AcceptAllChanges(); } catch { //throw or handle database of Entity Framework exceptions throw; } } }
You can watch the transaction being promoted in a few ways. For example, in SQL Profiler, you can see that System.Transaction starts out by using a simple database transaction, but as soon as it hits the call to SaveChanges to a different database, the transaction is promoted (see Figure 16-3).
Figure 16-3. SQL Profiler showing that a database transaction is used at first, but is then promoted when another database connection is made within the scope of a System.Transactions.TransactionScope
You can also add a variety of performance counters into the Windows Performance Monitor that tracks the DTC and you can see whether a transaction was
created, completed, or even rolled back.
NOTE If you are testing on a development machine, chances are you don't have the DTC services started. When the code reaches the second SaveChanges and .NET attempts to promote the transaction to use the DTC, if the DTC is not started you will receive an exception telling you that the DTC has not started on the system. You can start this service through the Computer Management console in Windows. The last way you can prove this is working is to force one of the updates to fail. You can see the rollback in the Profiler, or even just look in the database to verify that the changes have not been made.
16.2.4. Reading Queries Using System.Transaction or EntityTransaction It is also possible to use a transaction on a read-only query using System.Transaction or EntityClient.EntityTransaction . An EntityTransaction is merely a wrapper for the database provider's transaction, and call EntityConnection.BeginTransaction to create it, as shown in Example 16-9.
Example 16-9. Using a transaction on a read to control whether the read will read data that is in the process of being modified in the database
Using econ = New EntityConnection("name=BAEntities") Dim eTran As EntityTransaction = _ econ.BeginTransaction(IsolationLevel.ReadUncommitted) econ.Open() Dim eCmd = econ.CreateCommand eCmd.CommandText = _ "SELECT con.contactID FROM BreakAwayEntities.Contacts AS con" Dim dr = eCmd.ExecuteReader(CommandBehavior.SequentialAccess) While dr.Read 'do something with the data End While eTran.Commit() End Using
using (var econ = new EntityConnection("name=BAEntities")) { EntityTransaction eTran = econ.BeginTransaction(IsolationLevel.ReadUncommitted); econ.Open(); var eCmd = econ.CreateCommand(); eCmd.CommandText = "SELECT con.contactID FROM BreakAwayEntities.Contacts AS con"; var dr = eCmd.ExecuteReader(CommandBehavior.SequentialAccess); while (dr.Read()) { //do something with the data; } eTran.Commit(); }
At first glance, it may not make sense to have a transaction on a read, since you can't roll back a read. The purpose of performing a read within a transaction is to control how to read data in the database that may be involved in another transaction at the same time. Notice the IsolationLevel.ReadUncommitted parameter being passed in. IsolationLevel lets you determine how your query should read data that some other person or process is currently updating. The ReadUncommitted enum says that it is OK for this query to read data that is being modified, even if it has not yet been committed in the other transaction. The other possibilities are Serializable, RepeatableRead, ReadCommitted, Snapshot, Chaos, and Unspecified . You can check the docs to learn more about these IsolationLevel s, which are not specific to the Entity Framework. Although you can use EntityTransaction directly, it is recommended that you use System.Transaction.TransactionScope where you can also determine IsolationLevel . In that case, you would wrap the query (EntityClient, LINQ to Entities, or ObjectQuery) within a TransactionScope, just as in the previous example, which used TransactionScope for
SaveChanges.
NOTE Distributed transactions are more expensive to process, and often the events that cause a transaction to be promoted do not really require the extra cost of the DTC. Improvements were made in SqlClient so that transactions are escalated more wisely when using SQL Server 2008. Prior to SQL Server 2008, it helps to explicitly open the connection after creating the transaction. To read more about this, see the ADO.NET Team blog post "Extending Lightweight Transactions in SqlClient," at http://blogs.msdn.com/adonet/archive/2008/03/26/extending-lightweight-transactions-in-sqlclient.aspx/.
16.2.5. Can You Use Transactions Within ObjectContext? People often ask about the ability to roll back changes to entities in the context. Unfortunately, Object Services does not have a mechanism to achieve this. If you want to roll all the way back to the server values, you can use ObjectContext.Refresh to reset specific entities or a collection of entities, but you cannot do a thorough refresh of everything in the context. You'll learn more about refresh in Chapter 17 . Alternatively, you can dispose the context, create a new one, and requery the data. But still, this is not the same as rolling back to a previous state of the entities; all you're doing is getting fresh data from the store. If you want to persist the state of your entities at any given point in time and then restore them into the context, you'll need a better understanding of the which we will cover in detail in Chapter 17.
ObjectStateManager,
Unfortunately, at this time no pattern is available for pulling this off, but eventually someone from Microsoft or the development community will create and share a pattern to solve this. Hopefully, we'll see this feature added to version 2 of the Entity Framework.
16.3. The Entity Framework and Security Security is an important issue to be concerned with, and it is the subject of frequently asked questions regarding the Entity Framework, mostly due to database access. If you were to look at the security topic in the MSDN documentation (see the topic "Security Considerations [Entity Framework]"), you might find the lengthy list of items covered to be daunting. But on more careful inspection, you would see that most of the points are generic to programming and to data access, with only a few items pertaining specifically to the Entity Framework. The most frequently asked security topic in the Entity Framework concerns SQL injection. Another security issue of interest is the fact that developers can piggyback onto the Entity Framework's database connections. I will discuss these two scenarios in this chapter. Check the aforementioned MSDN topic for additional security topics.
16.3.1. SQL Injection SQL injection attacks are one of the most worrisome problems for data developers. An injection occurs when an end user is able to append actual query syntax in data entry form fields that can damage your data (e.g., delete table x ) or access information by piggybacking on the executed command.
NOTE Wikipedia has a handy tutorial on SQL injection if you want to learn more. See http://en.wikipedia.org/wiki/SQL_injection/. SQL injection can occur when you build queries dynamically in your code. For example:
QueryString="select * from users where username='" & TextBox.Text & "'"
Therefore, it is always recommended that programmers avoid building dynamic queries. Instead, we use parameterized queries or leverage stored procedures from our data access code. Because we have been trained to have an inherent fear of dynamic queries, on the surface the fact that the Entity Framework (and LINQ to SQL, for that matter) builds queries for us raises a big red flag.
16.3.1.1. You're safe with LINQ, but be careful with Entity SQL You do not have to worry when you are using LINQ to Entities (or LINQ to SQL). The queries that eventually land in your data store for execution are definitely parameterized queries, not dynamic ones. You've seen that throughout this book. And of course, you can always use stored procedures, which are the ultimate way to avoid SQL injection attacks. You'll need to be much more careful with Entity SQL. Entity SQL is broken down differently than LINQ to Entities, and the queries that result are composed differently. Let's look at the difference between a few queries in which it might be possible to inject some debilitating SQL by way of a text box in a data entry form. Here is a LINQ to Entities query:
From loc In context.Locations Where loc.LocationName = textBox.Text
When textbox.Text=Norway, the T-SQL that results is parameterized:
SELECT [Extent1].[LocationID] AS [LocationID], [Extent1].[LocationName] AS [LocationName] FROM [dbo].[Locations] AS [Extent1] WHERE [Extent1].[LocationName] = @p__linq__1 @p__linq__1='Norway'
Similarly, when textbox.Text= a' OR 't'='t (a classic injection attack), the native query still puts this "value" into a single parameter, and the injection is unsuccessful:
SELECT [Extent1].[LocationID] AS [LocationID], [Extent1].[LocationName] AS [LocationName] FROM [dbo].[Locations] AS [Extent1] WHERE [Extent1].[LocationName] = @p__linq__1 @p__linq__1='a'' OR ''t''=''t'
However, the same query in Entity SQL looks like this:
SELECT VALUE loc FROM BreakAwayEntities.Locations AS loc WHERE loc.LocationName='" & city & "'"
With Norway , the T-SQL is benign:
SELECT [Extent1].[LocationID] AS [LocationID], [Extent1].[LocationName] AS [LocationName] FROM [dbo].[Locations] AS [Extent1] WHERE [Extent1].[LocationName] = 'Norway'
but the injection succeeds. Here is the T-SQL:
SELECT [Extent1].[LocationID] AS [LocationID], [Extent1].[LocationName] AS [LocationName] FROM [dbo].[Locations] AS [Extent1] WHERE ([Extent1].[LocationName] = 'a') OR ('t' = 't')
Getting a list of all of the cities is still somewhat benign, but the point is that you have just lost control of your query. These types of attacks are not as easy to pull off with Entity SQL as they are when composing native queries in
ADO.NET, because the injection needs to be valid Entity SQL syntax and valid native SQL syntax at the same time. Therefore, an attack using this method:
"a' ; SELECT * FROM LOGINS"
or even this one:
"a' UNION ALL (SELECT value log from entities.logins as log)"
will fail because the Entity SQL command text will be invalid in both cases.
16.3.1.2. Entity SQL injection Injecting SQL that goes to the store is one problem. What about injecting Entity SQL into an Entity SQL string? Again, this is possible. Imagine appending a JOIN clause to your Entity SQL, followed by an Entity SQL expression that selects logins and passwords. The user only needs access to your EDM files to know the structure of the model and to figure out what your queries might look like to append the right string to get at the data she is looking for. It may not sound very easy to do, but some people spend a lot of time figuring out how to crack into our applications, and that is who you need to worry about. Therefore, as with any other data access that is dependent on user input, you need to validate all user input before inserting it into your queries; and you need to be very thoughtful regarding where and when you concatenate strings to build Entity SQL queries. Don't forget that you can use ObjectEntityParameters when building queries with
ObjectContext and EntityClient.
16.3.2. Protecting Data from Connection Piggybacks Although your model might limit what parts of your database a user has access to, it does make a connection to the database, providing an open door to users who might not otherwise have access to the database. As you saw in Section 16.1 , it is possible to get at the database connection through anEntityConnection ; therefore, a developer writing queries against the model could easily execute his own commands by using the existing connection. This would enable him to access data that is not even part of the model. Consider the code in Example 16-10 where the developer uses the connection from the context to return the employee data from the database.
Example 16-10. Using the EntityConnection to make an ADO.NET call to the database
Using context As New MyEntities Dim query = From con In context.Contacts Take 10 Dim conn As EntityConnection = context.Connection Dim dbconn As SqlConnection = conn.StoreConnection
conn.Open() Dim sqlcmd = New SqlCommand("Select * from HR.Employees", dbconn) Dim dr As SqlClient.SqlDataReader = sqlcmd.ExecuteReader While dr.Read Console.WriteLine(dr.Item("SocialSecurityNumber")) End While End Using
using (MyEntities context = new MyEntities()) { var query = (from con in context.Contacts select con).Take(10); EntityConnection conn = context.Connection; SqlConnection dbconn = conn.StoreConnection; conn.Open(); var sqlcmd = new SqlCommand("Select * from HR.Employees", dbconn); SqlClient.SqlDataReader dr = sqlcmd.ExecuteReader(); while (dr.Read()) { Console.WriteLine(dr["SocialSecurityNumber"]); } }
Even worse, with the connection string, any type of command against the database can be executed, not just queries. Although the developer may not necessarily have access to the connection string being used for the EDM queries—for example, the connection string may be encrypted—he can use this connection and any of the permissions associated with the login. This type of abuse is not particular to the Entity Framework, but it's important to be aware that the Entity Framework doesn't prevent it. As with any data access scenario, applying permissions carefully in your database can help you avoid this situation.
16.4. The Entity Framework and Performance "What about performance?" is another frequently asked question and a completely valid concern. There's no question that when you introduce layers into your application, performance will be impacted. Using ADO.NET to stream data directly from the database into your hands is certainly going to be faster than having a query interpreted and transformed and then having the returned data transformed again. You can do some things to help, and they can be hugely beneficial, but when comparing Entity Framework queries to "classic" ADO.NET queries or even LINQ to SQL, you are definitely paying a price for the benefits you gain.
16.4.1. A Few "Backyard Benchmarks" Following are some tests to give you a feel for the difference in performance (speed) between the Entity Framework, classic ADO.NET, and LINQ to SQL, because that's an important comparison as well.
NOTE Backyard benchmarks is my own term for identifying that these are simple tests that I conducted on my computer and that do not represent any official benchmarks from Microsoft or follow any type of official benchmarking guideline, if any even exists. The numbers are meant only to provide some relative comparisons between the Entity Framework, ADO.NET, and LINQ to SQL. Here are the specs of the computer used for these tests: Intel Core 2 Duo CPU, E4600 at 2.4 GHz 6 GB of RAM Windows Vista Ultimate SP 1 64-bit operating system Each test presents the average time it takes to process and return a query of the AdventureWorksLT Customer table 100 times. The tests are designed so that the processes will be comparable. For example, with the DataReader test, the code performs 100 individual queries, opening and closing a connection for each query. In the LINQ to Entities and ObjectContext tests, the sample instantiates a new context and performs 100 queries on that context. With the two Entity Framework queries, the default connection is being used; therefore, the Entity Framework will open a new connection for each query and then close the connection when the results have been iterated through. This is why the DataReader tests open and close the connection each time as well. The fourth test performs the same query using LINQ to SQL, which also opens and closes a connection for each query. In each test, the loop of 100 queries runs twice. The first time is to "prime the pump" so that any performance advantages provided by repeated queries are evened out between the various tests. The second set of 100 tests is used to gather the timings. In each test, the results are iterated through completely. The time quoted is not the time it took to perform a single query. It is the time it took to perform 100 queries, opening and closing the connection 100 times. It was a nice coincidence that the DataReader test resulted in an even 100 ms, making it easier to compare the other results.
NOTE For the Entity Framework queries, the metadata files were preloaded so that you do not see the cost of that in any of the results. Loading the metadata files happens only once during the lifetime of an application. Table 16-1 compares the relative times for the different methods of querying. In the following section, I interpret the results as well as list the code used to generate the results (see Examples Example 16-11 through Example 16-15).
Table 16-1. Comparison of relative times for different methods of querying Access type
Time for 100 queries
Difference from base
DataReader (base)
100 ms
--
LINQ to Entities
320 ms
+ 32%
Entity SQL with ObjectQuery
108 ms
+ 8%
Entity SQL with EntityClient
412 ms
+ 41%
LINQ to SQL
207 ms
+ 20%
16.4.1.1. Interpreting the tests It makes sense that the DataReader would be the fastest, as it has direct access to the database. It reads data directly from the database and streams it out to the client application. Therefore, this becomes the base for comparison. LINQ to Entities goes through a number of transformations prior to hitting the database, and the returned results need to be materialized, so this requires an extra hit. A query written in Entity SQL has one less transformation to go through before hitting the database, but whether you start with LINQ to Entities or an ObjectQuery , a number of expensive tasks need to be performed. The object materialization on the results incurs the same cost as using LINQ to Entities. If you look at the time required for the individual queries it's interesting to note the cost of the first query for each query method. The results shown in Table 16-2 display the times for the first and second queries compared to the average of all 100 queries.
Table 16-2. Differences in time between first and subsequent query calls Access type
First query
Second query
100-query average
DataReader (base)
1 ms
1 ms
1 ms
LINQ to Entities
9 ms
4 ms
3.2 ms
Entity SQL with ObjectQuery
6 ms
1 ms
1.1 ms
Entity SQL with EntityClient
13 ms
4 ms
4.1 ms
LINQ to SQL
7 ms
2 ms
2.1 ms
These results show the initial cost of the first query, whether you are using a DataReader , an EDM, or LINQ to SQL. The LINQ to Entities query has to do the additional work of creating the LINQ command tree, which is then sent to Object Services. Table 16-3 shows a comparison of just the three Entity Framework queries. Each is run in its own application; therefore, each must load the metadata on the first query.
Table 16-3. A new set of tests comparing only the EDM queries Access type
First EDM query in application
LINQ to Entities
703 ms
Entity SQL with ObjectQuery
638 ms
Entity SQL with EntityClient
597 ms
Why did these queries take so long? In all three queries, a lot of up-front expense occurs in query compilation—getting from the original query to the native query. The first is something that happens only once during the lifetime of an application—loading the EDM metadata into the ObjectContext . However, the metadata is alsoloaded into the application memory, so subsequent queries throughout the application do not have to load the metadata again. Because each of these tests is the first query in a newly running application instance, each of them incurs the cost of loading the metadata. Additionally, with LINQ to Entities andObjectQuery , other operations occur, such as the creation ofObjectStateEntries for entities and for relationships. On the way back, the results need to be either materialized as objects with LINQ to Entities and ObjectQuery, or transformed into an EntityDataReader with an EntityClient query. So, this is an expense you pay during the query, rather than later (in effort and processing time), as you would have to do when creating objects from a DataReader or dealing with relationships in DataTables. It's interesting to see that the EntityClient test, which does not materialize objects, is slower than theObjectQuery test. Even though the objects are not being created, EntityClient still needs to transform the data store results into the structure of the entities that it is returning.
NOTE Because these tests are somewhat lengthy, the C# version is provided here, and both the C# and Visual Basic versions will be available on the book's website.
Example 16-11. The DataReader performance test
private static void DataReaderTest(string connstring) { List testresults = new List(); string cmdText = "select CustomerID, NameStyle, Title, FirstName," + "MiddleName, LastName,Suffix,CompanyName, " + "SalesPerson, EmailAddress,Phone,PasswordHash, " + "PasswordSalt, rowguid, ModifiedDate " + "FROM SalesLT.Customer"; // start the timer System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); for (int i = 0; i < 2; i++)
{ testresults.Clear(); SqlConnection sqlCon = new SqlConnection(connstring); for (int j = 0; j < 100; j++) { sw.Reset(); sw.Start(); //timing the whole loop of 100 queries sqlCon.Open(); SqlCommand cmd = new SqlCommand(cmdText, sqlCon); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { object val = reader.GetValue(2); } reader.Close(); sqlCon.Close(); } sw.Stop(); testresults.Add((decimal)sw.ElapsedMilliseconds); } // return second set of results Console.WriteLine("DataReader: {0}ms", testsresults[1]) }
Example 16-12. The LINQ to Entities performance test
private static void LINQtoEntitiesTest() { List testresults = new List(); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); for (int i = 0; i < 2; i++) { testresults.Clear(); AWLTEntities edmAW = new AWLTEntities(); for (int j = 0; j < 100; j++) { sw.Reset(); sw.Start(); var customers = from c in edmAW.EFCustomers select c; foreach (EFCustomer cust in customers) {
object o = cust; } } sw.Stop(); testresults.Add((decimal)sw.ElapsedMilliseconds); } //end for loop //toss first result, calc average of rest Console.WriteLine("DataReader: {0}ms", testsresults[1]) }
Example 16-13. The Entity SQL ObjectQuery performance test
private static void ESQLQueryTest() { List testresults = new List(); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); for (int i = 0; i < 2; i++) { testresults.Clear(); AWLTEntities AWL2E = new AWLTEntities(); for (int j = 0; j < 100; j++) sw.Reset(); sw.Start(); { string esql = "SELECT VALUE c from AWLTEntities.EFCustomers AS c"; ObjectQuery customers = AWL2E.CreateQuery(esql); foreach (EFCustomer cust in customers) { object c = cust; } } sw.Stop(); testresults.Add((decimal)sw.ElapsedMilliseconds); } Console.WriteLine("DataReader: {0}ms", testsresults[1]) }
Example 16-14. The Entity SQL EntityClient performance test
private static decimal EntityClientTest() { List testresults = new List(); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); for (int i = 0; i < 2; i++) { testresults.Clear(); AWLTEntities AWL2E = new AWLTEntities(); EntityConnection eConn = new EntityConnection("name=AWLTEntities"); for (int j = 0; j < 100; j++) { sw.Reset(); sw.Start(); string esql = "SELECT VALUE c from AWLTEntities.EFCustomers AS c"; EntityCommand eCmd = new EntityCommand(esql, eConn); eConn.Open(); EntityDataReader eReader= eCmd.ExecuteReader(CommandBehavior.SequentialAccess); while (eReader.Read()) { object val = eReader.GetValue(2); } eReader.Close(); eConn.Close(); } sw.Stop(); testresults.Add((decimal)sw.ElapsedMilliseconds); } Console.WriteLine("DataReader: {0}ms", testsresults[1]) }
Example 16-15. The LINQ to SQL performance test
private static decimal LINQtoSQLTest() { List testresults = new List(); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); for (int i = 0; i < iOuterLoop; i++) { testresults.Clear(); AWLTDataContext AWL2SContext = new AWLTDataContext(); for (int j = 0; j < iInnerloop; j++) { sw.Reset(); sw.Start(); var query = from c in AWL2SContext.L2SCustomers select c; foreach (L2SCustomer cust in query) { object c = cust; } } sw.Stop(); testresults.Add((decimal)sw.ElapsedMilliseconds); } //end for loop Console.WriteLine("DataReader: {0}ms", testsresults[1]) }
16.4.2. Reducing the Cost of Query Compilation In an early 2008 blog post titled "Exploring the Performance of the ADO.NET Entity Framework—Part 1" (http://blogs.msdn.com/adonet/archive/2008/02/04/exploring-the-performance-of-the-ado-net-entity-framework-part-1.aspx/ ), Brian Dawson of the Entity Framework team breaks down query time by task. In his tests, 56 percent of the total time for processing a query is devoted to "view generation." View generation refers to the process of creating the native command from an Entity SQL ObjectQuery or a call to SaveChanges . Fifty-six percent! Here's a quick refresher on what's going on during this process. The Entity SQL is broken down into a command tree comprising Entity SQL operators and functions with entity names, properties, and relationships. This command tree is sent to the data provider, which translates the Entity SQL operators and functions to native operators and functions and uses the EDM to translate the entities and properties to tables and columns. Because the original query might be too complex for the native query, a series of simplifications is also performed on the tree. Finally, this newly created command tree is sent to the database. This is a lot of work. But it doesn't necessarily need to happen on the fly at runtime. Given the queries and the EDM, the native queries can be precompiled. You can take advantage of query precompilation in two ways: precompiled views and precompiled LINQ to Entities queries.
16.4.3. The EDM Generator for Precompiled Views (and More) The EDM Generator, a command-line tool (EDMGen.exe ), allows you to perform many of the same tasks that the EDM
Wizard performs, as well as some others. The EDM Generator has five command-line switches, which you can use to do the following:
/mode:FromSSDLGeneration Generates CSDL and MSL EDM files from an existing SSDL file
/mode:EntityClassGeneration Generates classes from a CSDL file
/mode:ValidateArtifacts Validates an EDM
/mode:ViewGeneration Precompiles queries from a specified project into a source code file
/mode:FullGeneration Creates CSDL, MSL, and SSDL files from a database, and generates the object classes and precompiled queries for each entity and relationship
NOTE Try out FullGeneration on a database so that you can see what the output looks like. It's quick and painless. All you need to pass in is a connection string and the project parameter to give it a name that will be used for all of the created files:
C:\Program Files\Microsoft Visual Studio 9.0\VC> edmgen /mode:FullGeneration /c:"Data Source=127.0.0.1; Initial Catalog=AdventureWorksLT; Integrated Security=True" /p:AWEDMGenTest
NOTE You can add other parameters, such as a Language parameter, to create Visual Basic files. Here are the files that result:
AWEDMGenTest.csdl AWEDMGenTest.ssdl AWEDMGenTest.msl AWEDMGenTest.ObjectLayer.cs AWEDMGenTest.Views.cs
16.4.3.1. What's in a precompiled view code file? Pregenerating views in the full generation will create views for eachEntitySet and association. For example, theViews class for the FullGeneration example in the preceding note will create a view fordbo.Customers that will be used anytime a query is made that involves customers. FK_SalesOrderHeader_Customer_CustomerID association also has a view that will be used anytime that association is required. It contains the necessary joins between the Customer table and the
SalesOrderHeader table.
16.4.3.2. Precompiling views against an existing project You can also target a project when precompiling views. However, be aware that EDMGen will not precompile any queries that are in the project. Only the model's EntitySet s get compiled. The purpose of targeting a project when precompiling views is so that the project's namespace gets used in the generated code. To see view generation working against an existing project, pick a project against which to test this—for example, the BreakAway WCF service you created in Chapter 15. The ViewGeneration option requires SSDL, MSL, and CSDL files that you don't actually have because you have been embedding them into the compiled assemblies. So, you'll need to go back to the BreakAwayModel project and generate these files: 1. Open the BreakAwayModel project if it's not already open. 2. Open the EDMX file in the Designer. 3. Click the background of the model to open the model's Properties window. 4. Change the Metadata Artifact Processing property to Copy to Output Directory. 5. Save the project. This will create the files. 6. Open the project's output directory in Windows Explorer. You can do this directly from the Solution Explorer by right-clicking the project and choosing Open Folder in Windows Explorer, then navigating to the output folder. 7.
Copy the CSDL, SSDL, and MSL files to another location on your drive (e.g., c:\EDMs). When you change the Metadata Artifact Processing property back to Embed in Output Assembly, the files will be removed from the output directory.
Now you can generate the view file. Note in Example 16-16 that the quotes around the project are there only because of a space in the file path.
Example 16-16. Using the EDM Generator command-line tool
C:\Program Files\Microsoft Visual Studio 9.0\VC> edmgen /mode:ViewGeneration /inssdl:c:\efmodels\BreakAwayModel.ssdl /incsdl:c:\efModels\BreakAwayModel.csdl /inmsl:c:\efmodels\BreakAwayModel.msl /p:"D:\VS2008\projects\_EFBOOK Samples\BreakAwayWCFService\ BreakAwayWCFService.vbproj" /language:VB
You'll find the newly generated file in the folder designated in the p (path) parameter. Be sure to include the file in the project in Solution Explorer. Again, it contains all the views that are represented in the model files. Now when you run this project, the runtime will be able to skip the bulk of the quey compilation tasks.
NOTE Because of the length of the generated code, I will not display it here. You can find sample Views files on the Downloads page of the book's website. The generated views are essentially strings containing native store commands.
16.4.4. Precompiled LINQ to Entities Queries Although the view generation feature lets you create the native SQL for all of the model's EntitySet s and associations, there's also a way to precompile the actual queries that you create in your application. This happens at runtime. For LINQ to Entities queries, you can explicitly precompile your queries using the CompiledQuery.Compile method. Entity SQL queries, whether called through EntityClient or through ObjectQuery , can be implicitly compiled and stored in a cache based on a setting that enables or disables query plan caching.
16.4.4.1. LINQ to Entities compiled queries
CompiledQuery.Compile allows you to compile a particular query, even one that takes parameters, at runtime. Then, anytime you need to use that query, you can point to the compiled version.
NOTE Query compilation is also available in LINQ to SQL, though the syntax is a bit different. Compiled queries can make a valuable performance improvement for queries that are used repeatedly in an application. You will still pay the compilation cost the first time the query is used, but subsequent uses of the query will avoid that part of the process. The code for creating compiled queries may seem a bit daunting at first because it takes advantage of functional programming . LINQ is based on functional programming, and more and more programmers are waking up to the benefits of it. Although this technique takes a bit of getting used to, it can be addictive once you get past the learning curve. The Entity Framework has a System.Data.Objects.CompiledQuery class, which lets you precompile a query into a
CompiledQuery object and then reuse that object.CompiledQuery.Compile takes two parameters and a query in the form of a delegate:
Compile(args, ReturnType) (Delegate Query)
The first parameter is args used to pass in any arguments. You'll want to pass in an instance of an ObjectContext and then any other variables that are used in the query. For example, your query may perform filtering on an integer, so you'll need to have an integer variable as one of the arguments. The second parameter is ReturnType, which might be an entity or it might be anObjectQuery of a particular type. The last,
Delegate , will be a lambda expression whose function is a LINQ to Entities query. Example 16-17 is an example of a query that might be used a number of times during an application's lifetime; it finds customers who have gone to a particular adventure location.
Example 16-17. A frequently used query that is a good candidate for precompilation
Dim custsToDestination = _ From cust In context.Contacts.OfType(Of Customer)() _ Where cust.Reservations.FirstOrDefault.Trip.Destination.DetinationName _ = "Patagonia"
var newCustomersSinceDate = from cust in context.Contacts.OfType() where cust.Reservations.FirstOrDefault().Trip.Destination.DetinationName == "Patagonia" select cust;
To turn this into a compiled query, you will need a variable to represent the object context, such as ctx . You will also need a variable for the location name. Construct a lambda expression that processes these two variables in a LINQ to Entities query, as shown in Example 16-18.
Example 16-18. A lambda expression of the query to be precompiled
Function(ctx As BreakAwayEntities, DestinationString As String) _ From cust In ctx.Contacts.OfType(Of Customer)() _ Where cust.Reservations.FirstOrDefault.Trip.Destination.DestinationName _ = DestinationString
(BreakAwayEntities ctx,String DestinationString) => from cust in ctx.Contacts.OfType() where cust.Reservations.FirstOrDefault().Trip.Destination.DestinationName == DestinationString select cust
This lambda expression is used as a parameter ofCompiledQuery.Compile . Example 16-19 shows the CompiledQuery , which will take aBreakAwayEntities object and a string when it's called, and will return an IQueryable(of Customer) . Those are passed into theCompile generic method. Then the lambda expression follows, inside parentheses. The query passes these parameters into the lambda expression. For brevity, I've used a placeholder where you need to insert the lambda expression from Example 16-18.
Example 16-19. The compiled LINQ to Entities query
Dim compQuery = CompiledQuery.Compile(Of BreakAwayEntities, String, _ IQueryable(Of Customer))(*insert lambda expression from Example 16-18*)
var compQuery = CompiledQuery.Compile (*insert lambda expression from Example 16-18*)
Once the CompiledQuery has been created, you can use it any time you want to use the query by implementing its Invoke method, as demonstrated in Example 16-20 . Because you have a parameter for this query, you can change the value of the parameter any time you use the query, which makes the compiled query pretty flexible.
Example 16-20. Using the compiled LINQ to Entities query
Dim context As New BreakAwayEntities Dim loc As String = "Malta" Dim custs As ObjectQuery(Of Customer) = compQuery.Invoke(context, loc) Dim custlist = custs.ToList
var context = new BreakAwayEntities(); var loc = "Malta"; IQueryable custs = compQuery.Invoke(context, loc); var custlist = custs.ToList();
Now you can use the code in Example 16-21 to test the performance of the compiled query. The first query loads the metadata files into the application memory so that the time for that task is not counted in the first run of the compiled query. You'll learn more about metadata files in Chapter 17 . Subsequent queries (the example lists only some of them) will not require query compilation and will be faster.
Example 16-21. A performance test of the compiled query
Using context = New BAEntities Dim cust = context.Contacts.FirstOrDefault End Using Using context As New BAEntities Dim destination As String = "Malta" Dim custs As ObjectQuery(Of Customer) = _
compQuery.Invoke(context, destination) Dim custlist = custs.ToList End Using Using context As New BAEntities Dim destination As String = "Bulgaria" Dim custs As ObjectQuery(Of Customer) = _ compQuery.Invoke(context, destination) Dim custlist = custs.ToList End Using
using (var context = new BAEntities ()) { var cust = context.Contacts.FirstOrDefault(); } using (BreakAwayEntities context = new BAEntities ()) { string destination = "Malta"; ObjectQuery custs = compQuery.Invoke(context, destination); var custlist = custs.ToList(); } using (BreakAwayEntities context = new BAEntities ()) { string destination = "Bulgaria"; ObjectQuery custs = compQuery.Invoke(context, destination); var custlist = custs.ToList(); }
Notice that for each timed test, a completely new context is created that also creates a new connection. The times shown in Table 16-4 are compared to performing the same test without using compiled queries.
Table 16-4. Performance comparisons between compiled and noncompiled LINQ to Entities queries Query 1
Query 2
Query 3
Using a compiled query
14 ms
1 ms
1 ms
Using a noncompiled query
8 ms
4 ms
3 ms
You can see that once the query has been compiled, query processing takes only a portion of the time it takes when repeating that particular task without the advantage of precompilation.
16.4.5. Query Plan Caching for Entity SQL By default, compiled Entity SQL queries are stored in an application domain cache for both EntityClient queries and ObjectQuery queries. As part of the query pipeline, the cache will be checked for a matching Entity SQL query (parameters are taken into account here, as with the precompiled LINQ queries), and if a precompiled version of that query is available, it will be used. Set the Boolean EntityCommand.EnablePlanCaching to true or false to enable or disable caching forEntityClient.
ObjectQuery.EnablePlanCaching is the property for enabling or disabling query plan caching forObjectQuery queries. Given the previous advice about avoiding SQL injection attacks with dynamic Entity SQL, Microsoft recommends that if you are building Entity SQL expressions dynamically, you disable query plan caching. The stored queries are case-sensitive, so if you have a query in which you type "select value con …" in one method and "SELECT VALUE con …" in another, they won't be considered matching queries, and not only will you lose the benefit of the cached query, but the size of the cache will increase as a result of extra queries being stored. Using tests similar to the previous performance tests, you can see the difference in query processing time when caching is enabled or disabled, as shown in the following code and in Table 16-5:
SELECT VALUE c from AWLTEntities.EFCustomers AS c
Table 16-5. Comparing average query times for materialized entities versus streamed data Query plan caching state
Enabled
Disabled
Entity SQL with Object Services
1.1 ms
3.23 ms
Entity SQL with EntityClient
4.1 ms
6.38 ms
Again, in this case the time for the cached query is significantly less than the non-cached query.
16.4.5.1. Entity SQL querying with EntityClient versus Object Services Although the difference between querying with and without the cache may not be surprising, the difference between querying with Object Services and EntityClient might be. When running the test with a query that returns data of a more complex shape, the difference shifts, as you can see in the following code and in Table 16-6:
SELECT cust.CompanyName,cust.SalesOrderHeader, (SELECT VALUE order.SalesOrderDetail FROM cust.SalesOrderHeader AS order) FROM AWLTEntities.EFCustomers AS cust
Table 16-6. Average query times for shaped results Query plan caching state
Enabled
Disabled
Entity SQL with Object Services
21.28 ms
42.51 ms
Entity SQL with EntityClient
17.05 ms
32.15 ms
Now the EntityClient and Object Services queries are more on par—with theEntityClient being about 15% faster. Because
EntityClient does not materialize the objects, you would expect it to have some advantages. But why is the query itself impacting the difference between the two methods of querying? Although object materialization takes some time, so does the task of shaping theEntityDataReader and then pushing in the results. In the case of the simple query, object materialization is very efficient in creating a Customer entity from data that maps exactly to the entity. In the second query, you are building data that is shaped like that shown in Figure 16-4.
Figure 16-4. The shaped data returned by the Entity SQL performance test
With the more complexly shaped data, once the EntityDataReader is created the cost of pushing the data into that
DataReader is a lot less than the cost of materializing a lot of complexly shaped objects.
16.4.6. What About Database Updates and Performance? Again, the Entity Framework will need to generate commands and transform the entity structure into the database structure; thus, compared to working with ADO.NET, where you would be working directly against the database, there will be a performance hit.
NOTE In talking with one of the folks who focuses on performance for the Data Programmability team, I learned that the performance for updating data in the Entity Framework is very impressive when compared to other technologies. Although that was proof enough for me, I still had to see the performance benefits for myself! For the following tests, I modified the previous tests to include updates and inserts, and because this is much more intensive and time-consuming than just querying data, there are only 10 iterations of the tests, not 100. Each test queries for the entire set of customers (approximately 450), iterates through those customers, and modifies a single field in each one. Once those modifications are made, 10 new customers are added. Finally, the appropriate update method is called (DataAdapter.Update, DataContext.SubmitChanges , or ObjectContext.SaveChanges).
To be fair, there are two tests forDataSet . The first uses the defaultUpdate , which sends one command at a time to the database. The second leverages UpdateBatch and sets the batch to 100 commands at a time. The final times represent the average of performing this entire operation 10 times.
NOTE Remember that these tests are meant only to be relative to one another. I conducted them on my computer, which might not be as tricked out as the average server. The tests are not meant to indicate the actual potential of any of the tested technologies' performance overall. The results are interesting. The Entity Framework is faster thanDataAdapter and LINQ to SQL, as you can see inTable 16-7.
Table 16-7. Comparing DataAdapter UpdateBatch to Entity Framework and LINQ to SQL Method
Average time
DataAdapter with UpdateBatch=1
353 ms
DataAdapter with UpdateBatch=100
288 ms
Entity Framework Object Services
143 ms
LINQ to SQL
1333 ms
You can perform updates with "classic ADO.NET" in a variety of ways, and you may achieve different results relative to the two newer technologies. But this at least gives you an idea that something very smart is happening under the covers of the Entity Framework when SaveChanges is called.
16.5. Entities in Multithreaded Applications Like much of .NET, the Entity Framework is not thread-safe. This means that to use the Entity Framework in multithreaded environments, you need to either explicitly keep individual ObjectContext s in separate threads, or be very conscientious about locking threads so that you don't get collisions.
NOTE Straight from the source (MSDN docs): "ObjectContext only supports Single-Threaded scenarios." Here are some examples of a few ways to useObjectContext in separate threads.
16.5.1. Forcing an ObjectContext to Use Its Own Thread Example 16-22 uses a separate class for managing theObjectContext and performing the database interaction. The main program then creates a separate thread when it needs the ObjectContext to do something. Delegates and callbacks are used so that it's possible for entities to be returned from the separate thread. Notice that every time theObjectContext is about to be impacted, a lock is placed on it. C# has a handy lock keyword, but with Visual Basic you'll need to use the Threading.Monitor class to do this.
NOTE If you are unfamiliar with threading and delegates, you are not alone. It's an advanced topic, and lots of resources are available to help you get up and running on threading if you need to use it explicitly. The one area where it is useful to understand, even if you have no plans to perform advanced threading work, is in keeping your UI responsive while performing tasks such as making a call to the database, which might take some time. Look for topics on the BackgroundWorker component that you can use in both Windows Forms and Windows Presentation Foundation (WPF), and the Asynchronous Page features in ASP.NET.
Example 16-22. Forcing an ObjectContext to use its own thread
Imports System Imports System.Threading Imports BAGA.BreakAwayModel Module Module2 ' Delegate that defines the signature for the callback method. Public Delegate Sub contextCallback _ (ByVal contactList As List(Of Contact)) Private contacts As List(Of Contact) Public Class MainModule
Public Shared Sub Main() Dim occ As New ObjectContextClass _ (New contextCallback(AddressOf ResultCallback)) Dim t As New Thread(AddressOf occ.GetCustomers) t.Start() t.Join() Console.WriteLine("Contacts Retrieved: " & contacts.Count.ToString) Console.WriteLine(contacts(0).LastName & contacts(0).ModifiedDate) contacts(0).ModifiedDate=DateTime.Now Console.WriteLine(contacts(0).LastName & contacts(0).ModifiedDate) t = New Thread(AddressOf occ.SaveChanges) t.Start() End Sub Public Shared Sub ResultCallback(ByVal contactList As List(Of Contact)) contacts = contactList End Sub End Class Public Class ObjectContextClass Private context As BreakAwayEntities ' Delegate used to execute the callback method when the task is done. Private callback As contextCallback ' The callback delegate is passed in to the constructor Public Sub New(ByVal callbackDelegate As contextCallback) callback = callbackDelegate End Sub Public Sub GetCustomers() If context Is Nothing Then context = New BreakAwayEntities End If 'put a lock on the context during this operation Threading.Monitor.Enter(context) Dim contactquery = From cust In context.Contacts _ Where cust.LastName.StartsWith("S") 'unlock the context Dim conList = contactquery.ToList Threading.Monitor.Exit(context) If Not callback Is Nothing Then callback(conList) End Sub Public Sub SaveChanges() Threading.Monitor.Enter(context) context.SaveChanges() Threading.Monitor.Exit(context) End Sub End Class
End Module
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using BAGA; internal static class Module2 { // Delegate that defines the signature for the callback method. // public delegate void contextCallback(List contactList); private static List contacts; public class MainModule { public static void Main() { ObjectContextClass occ = new ObjectContextClass(new contextCallback(ResultCallback)); Thread t = new Thread(occ.GetCustomers); t.Start(); t.Join(); Console.WriteLine("Retrieved: " + contacts.Count.ToString()); Console.WriteLine(contacts[0].LastName + contacts[0].ModifiedDate); contacts[0].ModifiedDate = DateTime.Now; Console.WriteLine(contacts[0].LastName + contacts[0].ModifiedDate); t = new Thread(occ.SaveChanges); t.Start(); } public static void ResultCallback(List contactList) { contacts = contactList; } } public class ObjectContextClass { private BAEntities context;
// Delegate used to execute the callback method when the task is done. private contextCallback callback; // The callback delegate is passed in to the constructor public ObjectContextClass(contextCallback callbackDelegate) { callback = callbackDelegate; } public void GetCustomers() { if (context == null) { context = new BAEntities(); } //put a lock on the context during @this operation; System.Threading.Monitor.Enter(context); var contactquery = from c in context.Contacts where c.LastName.StartsWith("S") select c; ////unlock the context; var conList = contactquery.ToList(); System.Threading.Monitor.Exit(context); if (callback != null) callback(conList); } public void SaveChanges() { System.Threading.Monitor.Enter(context); context.SaveChanges(); System.Threading.Monitor.Exit(context); } } }
It's important to call out the locking of the context. Because of the way the ObjectContext manages state and relationships, and because of the merge possibilities when new data is brought in, you need to be very careful so that two separate threads do not affect the context at the same time. You should consider this use as an edge case, and you should be sure that you really understand threading before you start spinning your own threads and working with classes that are not thread-safe. It's much safer (though less practical in many cases) to keep individual ObjectContext s on completely separate threads so that you don't have to worry about this as much. The BackgroundWorker component, introduced in .NET 2.0, does alleviate some of the complexities of working with multiple threads, but still, the Entity Framework does not have any inherent features that make it easy to use in
multithreaded applications. Hopefully, future versions of the Entity Framework will make threading and asynchronous programming simpler to work with.
16.5.2. Another Spin on Threading: Concurrent Processing Example 16-22 used a separate thread to host theObjectContext. Example 16-23 shows another way to use worker threads to perform some concurrent processing on entities. Because this example only performs reads on the entities, the concerns of Example 16-22 are not present. This example sends entities off to a variety of methods that will merely read information from the entities and possibly send a form letter or email. In this case, the code is writing some text out to the console only to demonstrate the concept. The query pulls back customers along with their reservation and trip information. Then, based on the reservation status, the Customer entity is sent to a different method to create the email. Because the process is being performed in different threads, the emails can be written concurrently and there is no need in this case to wait for any type of result. When the text is written out to the console, the example also displays the ID of the thread so that you can verify that different threads are being used.
Example 16-23. Managing threads to get concurrent processing
Imports System.Threading Imports BAGA Private Sub MultiThreadTest() Dim emailThread = New EmailThreadClass Using context As New BreakAwayEntities Dim custs = From cust In context.Contacts.OfType(Of Customer) _ .Include("Reservations.Trip.Location") For Each cust In custs If cust.Reservations.Any _ (Function(r) r.Trip.StartDate > Today.AddDays(6)) Then 'new thread for upcoming trip emails Dim workerThread As Threading.Thread = _ New Threading.Thread(AddressOf emailThread.UpcomingTripEmails) workerThread.Start(cust) ElseIf cust.Reservations.Any _ (Function(r) r.Trip.StartDate > Today _ And r.Trip.StartDate c.Addresses .Any(a => a.CountryRegion == "UK")) .ToList(); //Get all entries in the context, even relationships var allOses=context.ObjectStateManager.GetObjectStateEntries(); //Get all Customer entries var custOses=context.ObjectStateManager.GetObjectStateEntries(); //Get only Modified Customer entries var modifiedCustOses=context.ObjectStateManager _ .GetObjectStateEntries(EntityState.Modified);
17.2.2. Getting a Single Entry with GetObjectStateEntry and TryGetObjectStateEntry You can also retrieve a single entry from the ObjectStateManager using either GetObjectStateEntry or its counterpart,
TryGetObjectStateEntry.
These methods will look in the context to return an entry. They each have two overloads that let
you use either an entity or an EntityKey as a parameter. If you pass in the entire entity, the method will extract itsEntityKey and use that to find the entry. Example 17-7 uses an entity to find its relatedObjectStateEntry, whereas Example 17-8 uses an EntityKey to find an ObjectStateEntry.
Example 17-7. Using an entity to find its related ObjectStateEntry
GetObjectStateEntry(myReservation)
GetObjectStateEntry(myReservation)
Example 17-8. Using an EntityKey to find an ObjectStateEntry
GetObjectStateEntry(New EntityKey("BAEntities.Reservations", "ReservationID",10)
GetObjectStateEntry(new EntityKey("BAEntities.Reservations", "ReservationID",10)
If the entry cannot be found (meaning that the object doesn't exist in the context), anInvalidOperationException will be thrown.
TryGetObjectStateEntry is safer than GetObjectStateEntry . TryGetObjectStateEntry emulates the TryParse and TryCast methods in the .NET Framework. Rather than throwing an exception, it will return a Boolean if the entry is not found. You need to create a variable in advance for the entry and pass that into the method to be populated. Again, you can pass in either the entity or the EntityKey. You can then use the Boolean to determine whether the operation succeeded or failed, and have your code smoothly handle a failure, as shown in Example 17-9.
Example 17-9. Using TryGetObjectStateEntry to avoid an exception
Dim osm=context.ObjectStateManager Dim ose As Objects.ObjectStateEntry If osm.TryGetObjectStateEntry(myReservation, ose) Then 'is the method returned True, then ose is populated 'continue working with the entry Else 'logic here to gracefully deal with entry not being found End If
Var osm=context.ObjectStateManager; ObjectStateEntry ose; if (osm.TryGetObjectStateEntry(res,out ose)) // success logic else //failure logic
GetObjectStateEntry Versus GetObjectByKey You may recall the ObjectContext.GetObjectByKey and TryGetObjectByKey methods from Chapter 4 . If you were to dig into these methods in Reflector, you would discover that they actually use the GetObjectStateEntry method to find the object. However, there are two big differences between the
GetObjectByKey methods and theGetObjectStateEntry methods. First, GetObjectByKey returns an entity object, whereas GetObjectStateEntry returns an ObjectStateEntry. More importantly, GetObjectByKey queries the database if it is unable to find the object in the context, whereas GetObjectStateEntry only looks in the context for existing entries. It will not make a trip to the database. Many times you only want to work with what is already in memory and you do not want to bring additional data back from the database.
17.2.3. Digging Through ObjectStateEntry Once you have an ObjectStateEntry for an entity in hand, you can view some of its details in the debugger watch window.
However, the debug view doesn't show much more than what you can already get from the entity itself (see Figure 17-2).
Figure 17-2. An ObjectStateEntry in debug view
The real information comes through the methods and properties that are not exposed in the debugger. The C# debug view shows some of these private properties, but you can't expand them to see the actual data. Once you know what the methods and properties are, you can type them directly into the debugger to see their results.
17.3. CurrentValues and OriginalValues The CurrentValues property returns a CurrentValueRecord (an enhanced version of a DbDataRecord), which is an ObjectStateEntryDbUpdatableDataRecord , and it contains three things: An array of the property values for the entity A FieldCount property A DataRecordInfo object containing the metadata about the entity, such as the name and type of each property The OriginalValues property returns a DbDataRecord that contains the array of original property values and aFieldCount property. It does not include a DataRecordInfo object. Under the covers, this is anObjectStateEntryDbDataRecord. The word Updatable is missing from this type. You can modify the current values but not the original values.
Entities in the Added state do not have any original values. In fact, callingOriginalValues will throw an exception.
The value array contains scalar property values of the entity. If the property is a complex type, the value is a nested DbDataRecord.
NOTE Remember that the ObjectContext has a different definition of original than you may have. Although the original values are typically the database values, they are reset using the current values anytime you attach the entity to the ObjectContext . So, if you have detached and reattached an entity, there's no longer a guarantee that the values are what originally came from the database. The way to access the values is through the Item property or one of the many casting methods such asGetString or GetByte. You can't expand the array in the debugger, and no property returns the entire array. If you are familiar with working with DbDataReader s, the properties are exposed in the same way. The code in Example 17-10 grabs an entry for a Customer that is in the context and displays its property values.
Example 17-10. Reading the CurrentValues of an ObjectStateEntry
Dim objectStateEntry = osm.GetObjectStateEntry(customer.EntityKey) Dim currentValues = objectStateEntry.CurrentValues For i = 0 To currentValues.FieldCount - 1 Console.WriteLine("Field {0}: {1}", i, currentValues.Item(i)) Next
var objectStateEntry = osm.GetObjectStateEntry(customer.EntityKey); var currentValues = objectStateEntry.CurrentValues; for (var i = 0; i < currentValues.FieldCount; i++)
{ Console.WriteLine("Field {0}: {1}", i, currentValues [i]); }
The example code returns the following: Field 0: 16 Field 1: Christopher Field 2: Beck Field 3: Mr. Field 4: 9/4/2004 2:58:25 AM Field 5: 8/7/2008 8:27:07 AM Field 6: System.Byte[]
Even if the reservations or other related data for the customer was in the context, it won't be listed here. No navigation properties are retained in an ObjectStateEntry for an entity. However, it is possible to identify theRelationshipEntries for this entity, and from those you can locate the related entities. In this way, you can identify or interact with the graph, if you need to do so from this direction.
Why Read ObjectStateEntry Information? Digging around in the ObjectStateManager , reading entity information, and tracking down the related entities is a lot of work. Many developers won't have a reason to go to this trouble. But the fact that all of this information is exposed means you can create very dynamic features in your application, or even create dynamic applications, whereby you can pass any Entity Data Model (EDM) and create objects and graphs on the fly. Even if you are not building third-party tools, you can use the power of this functionality to encapsulate a lot of reusable and generic functionality within and across your applications. .NET developers have been doing such things using reflection since .NET 1.0. Although you can also use reflection to work with types, using the ObjectStateManager results in much better performance. When you add in the MetadataWorkspace and reading the model, you can go even further with these capabilities.
The ObjectStateEntry Visualizer extension method you looked at briefly inChapter 9 takes advantage of inspecting an entity in a generic way using information from the ObjectStateEntry . Although it doesn't inspect relationships, it does use a few more features of ObjectStateEntry , so let's look at those before looking at the methodextension.
17.3.1. CurrentValueRecord.DataRecordInfo The DataRecordInfo that is returned by CurrentValues provides two important functions. The first is that it enables you to access the metadata about the entity: property names, EDM types, and more. Additionally, it allows you "back-door" access to edit the entity objects. This is especially useful in scenarios where you don't have specific references to entities that are being managed by the context. You can grab an ObjectStateEntry from the context and then get the entity from there. This allows you to work directly with the entity after all. OriginalValues does not expose a DataRecordInfo property. You can see OriginalValues.DataRecordInfo in the debugger, but you can't access it in code. If you need the metadata information, use CurrentValues to get the DataRecordInfo . Also, it's not possible to update the original values. The only time you would explicitly impact the original values is if you call AcceptAllChanges on the ObjectContext , forcing the original values to be updated with the current values. Figure 17-3 displays the debug window for the CurrentValueRecord of a Reservation entity. In Example 17-10, the values were retrieved using the Item property combined with the FieldCount. The FieldMetadata lists details for each field. The first is expanded a bit and highlighted. Notice that you can see the property name, ReservationID , here. Now you have a way to align the value of the first item with the property name of the first field, and you can conclude that ReservationID=1.
Figure 17-3. The FieldMetadata value of CurrentValues, which lets you discover plenty of information about each property
The properties and methods of ObjectStateEntry give you direct access to some of the metadata without having to use the MetadataWorkspace . This is the tip of the iceberg in terms of what you can achieve when coding directly with the MetadataWorkspace.
17.4. Building the ObjectStateEntry Visualizer Now let's look at the tool for visualizing an ObjectStateEntry , which you saw briefly in Chapter 9 . This tool reads information from the ObjectStateEntry and displays it on a Windows form. I have found it to be a handy tool to use when debugging Entity Framework applications. The visualizer is an extension method for an EntityKey because EntityKeys are serializable. In addition, it takes an ObjectContext as a parameter so that it can search the context to find the ObjectStateEntry.
NOTE For those who are familiar with debugger visualizers, introduced in Visual Studio 2005, the ObjectStateEntry visualizer is not a debugger visualizer. Debugger visualizers require the target object to be serializable so that it can be moved to the debugger process. However, like ObjectStateManager, ObjectStateEntry classes are not serializable. In fact, if you do want to serialize them, you will need to deconstruct them and reconstruct them using the tools you are learning about in this chapter. Instead, this visualizer will be wrapped into an extension method with an attribute that makes it available only during debugging. Although the tool is handy to have, the lessons you will learn by writing this code will be valuable. The code provides a practical demonstration of inspecting and extracting details of an ObjectStateEntry using its properties and methods.
17.4.1. Setting Up the Project and Code File You can download the code for the visualizer from the book's website. If you want to build it while walking through the explanation in this chapter, you'll need to create a new class library project with a reference to System.Data.Entity . In the primary code file, add Imports or Using statements for the following namespaces: System.Runtime.CompilerServices System.Data.Objects System.Data System.Data.Common System.Windows.Forms
Add a Windows form to the project you'll work on after you have created the extension method. Name the form debuggerForm. In VB, extension methods are housed in modules. The shell for your code should look like Example 17-11 . If you haven't written an extension method before, the first parameter defines the class that the method will extend.
Example 17-11. Base module and method for the Visual Basic version of the visualizer
_ Public Module Visualizers
_ _ Public Sub VisualizeObjectStateEntry + (ByVal eKey As EntityKey, ByVal context As ObjectContext)
'code will go here
End Sub
In C#, you can create the visualizer extension method within a class:
namespace EFExtensionMethods { public static class Visualizer {
public static void VisualizeObjectStateEntry
(this EntityKey eKey, ObjectContext context) { //code will go here } } }
17.4.2. Retrieving an ObjectStateEntry Using an EntityKey The visualizer's first task is to use the EntityKey to retrieve the ObjectStateEntry from the context (see Example 17-12 ). If the entity is detached, there will be no entry in the context, so you can use TryGetObjectStateEntry to be safe.
NOTE The visualizer displays its results in a Windows form; therefore, you should already be in the correct environment for displaying a MessageBox.
Example 17-12. Getting the ObjectStateEntry
Dim ose As ObjectStateEntry = Nothing 'If object is Detached, there will be no Entry in the ObjectStateManager If Not context.ObjectStateManager.TryGetObjectStateEntry(eKey, ose) Then MessageBox.Show( _ "Object is not currently being change tracked and no " & _ "ObjectStateEntry exists.", "ObjectStateEntryVisualizer", _ MessageBoxButtons.OK, MessageBoxIcon.Warning) Else
ObjectStateEntry ose = null; /If object is Detached, there will be no Entry in the ObjectStateManager
if (!(context.ObjectStateManager.TryGetObjectStateEntry(eKey, out ose))) MessageBox.Show ("Object is not currently being change tracked " + "and no ObjectStateEntry exists.", "ObjectStateEntry Visualizer", MessageBoxButtons.OK, MessageBoxIcon.Warning); else {
17.4.3. Reading the OriginalValues and CurrentValues of an ObjectStateEntry If the entry exists, the next step is to retrieve the current and original values from the entry. However, there's a potential problem with OriginalValues . As noted earlier, entities in the "Added" state do not have original values and the property will throw an exception. Therefore, you'll declare a variable to contain the OriginalValues and populate it only if the state is not Added (see Example 17-13).
NOTE Because of the length of code for the visualizer, this walkthrough will highlight critical pieces of code but will not display the complete code listing. You can download the C# and VB code for the visualizer from the book's website.
Example 17-13. Getting the CurrentValues and OriginalValues
Dim currentValues = ose.CurrentValues Dim originalValues As DbDataRecord = Nothing If ose.State EntityState.Added Then originalValues = ose.OriginalValues End If
var currentValues = ose.CurrentValues; DbDataRecord originalValues = null; if (ose.State != EntityState.Added) originalValues = ose.OriginalValues;
Next, create an array to store the data you'll be collecting for each property. The visualizer will need to not only display the current and original values, but also retrieve the property name by drilling into the metadata. Iterate through the items in CurrentValues , picking up the value and the property as well as its related item value in the OriginalValues array. The values are captured in a number of variables and at the end will be pushed into the new array. Example 17-14 shows how DataRecordInfo is used to drill into the metadata to get the field names. For added records, you'll use a default of "n/a" in place of the nonexistent original value.
Example 17-14. Reading through the value arrays
'create an array to store the data for the form Dim valueArray As New ArrayList 'walk through value arrays to get the values For i = 0 To currentValues.FieldCount - 1 'FieldMetadata provides field names Dim sName = currentValues.DataRecordInfo.FieldMetadata(i) _ .FieldType.Name Dim sCurrVal = currentValues.Item(i) Dim sOrigVal As Object If originalValues Is Nothing Then sOrigVal = "n/a" 'this will be for Added entities Else sOrigVal = originalValues.Item(i) End If
//walk through arrays to get the values var valueArray = new System.Collections.ArrayList(); for (var i = 0; i < currentValues.FieldCount; i++) { //metadata provides field names var sName = currentValues.DataRecordInfo.FieldMetadata[i] .FieldType.Name; var sCurrVal = currentValues[i]; object sOrigVal = null; if (originalValues == null) sOrigVal = "n/a"; //this will be for Added entities
else sOrigVal = originalValues[i];
17.4.4. Determining Whether a Property Has Been Modified Although you could just compare original to current values to determine whether the property has been modified, ObjectStateEntry has a method called that returns an array of strings listing the names of any properties that have changed. Example 17-15 uses a LINQ to Objects query to check whether the current property is in that list. GetModifiedProperties
Example 17-15. Determining whether the value has changed
Dim changedProp = (From prop In ose.GetModifiedProperties _ Where prop = sName).FirstOrDefault Dim propModified As String propModified = If(changedProp = Nothing, "", "X")
string changedProp = (from prop in ose.GetModifiedProperties() where prop == sName select prop).FirstOrDefault(); string propModified; if(changedProp == null) propModified= ""; else propModified="X";
Finally, gather all of the information you just collected regarding that item and place it into the array you created at the start (see Example 17-16).
Example 17-16. Pushing the property information into the array
valueArray.Add(New With {.Index = i.ToString, .Property = sName, _ .Current = sCurrVal, .Original = sOrigVal, _ .ValueModified = propModified}) Next 'this closes the For Each loop
valueArray.Add(new { _Index = i.ToString(), _Property = sName, Current = sCurrVal, Original = sOrigVal, ValueModified = propModified }); } //this closes the foreach loop
17.4.5. Displaying the ObjectStateEntry's State and Entity Type When this is complete, the array is passed into a Windows form and is displayed in a grid. Two more pieces of data are sent along as well: the ObjectStateEntry.State and ObjectStateEntry.Entity.ToString properties. ObjectStateEntry.Entity.ToString returns the fully qualified name of the entity's type (see Example 17-17). You can see the results in Figure 17-4.
Figure 17-4. The visualizer populated with ObjectStateEntry information
NOTE The code in Example 17-17 assumes you have added the appropriate labels and a DataGridView to the form. To access the controls from the class, you will need to set their Modifiers property to Friend in Visual Basic and to Internal in C#.
Example 17-17. Pushing the values into the form
Dim frm As New debuggerForm With frm .DataGridView1().DataSource = valueArray .lblState.Text = ose.State.ToString .lblType.Text = ose.Entity.ToString .ShowDialog() End With
debuggerForm frm = new debuggerForm(); frm.dataGridView1.DataSource = valueArray; frm.lblState.Text = ose.State.ToString(); frm.lblType.Text = ose.Entity.ToString(); frm.ShowDialog();
17.4.6. Getting ComplexType Properties Out of ObjectStateEntry There's one more twist that the preceding code doesn't take into account: the possibility of a complex type in your properties. If the entity contains a complex type, the value of the item will be a DbDataRecord , not a normal scalar value. Using the preceding solution, this will display in the grid as System.Data.Objects.ObjectStateEntryDbUpdatableDataRecord. Instead, you'll need to read the array values of the complex type. Your first step is to determine whether the property is a complex type. The simple way to do this is to look for a DbDataRecord type using a type comparison, as
shown in Example 17-18.
Example 17-18. Testing to see whether a property is a complex type
If TypeOf (currentValues(i)) Is DbDataRecord Then
if (currentValues[i] is DbDataRecord)
No other property types will render a DbDataRecord, so this will do the trick. Although it is not practical for this example, it is possible, as shown in Example 17-19 , to get much more granular by drilling much deeper into the entry where you can use the metadata to identify the complex type, or any other entity type, for that matter.
NOTE You can compare the BuiltInTypeKind property to the BuiltInTypeKind enumerator. You can use BuiltInTypeKind to identify any one of 40 schema types in an EDM, beginning alphabetically with AssociationEndMember.
Example 17-19. An alternative way to check for a complex type
If currentValues.DataRecordInfo.FieldMetadata(i).FieldType _ .TypeUsage.EdmType.BuiltInTypeKind = _ System.Data.Metadata.Edm.BuiltInTypeKind.ComplexType Then
if (currentValues.DataRecordInfo.FieldMetadata[i].FieldType .TypeUsage.EdmType.BuiltInTypeKind == System.Data.Metadata.Edm.BuiltInTypeKind.ComplexType)
Your code can then return the scalar item or, if it is a complex type, further process the item to extract its values. The visualizer uses a separate function, for that task.
ComplexTypeString ,
ComplexTypeString
takes the DbDataRecord and returns a string with the internal values of the complex value, as shown in the following code:
Private Function ComplexTypeString(ByVal item As DbDataRecord) As String Dim dbRecString = New StringBuilder For i = 0 To item.FieldCount - 1
If item(i) Is DBNull.Value Then dbRecString.AppendLine("") Else dbRecString.AppendLine(CType(item(i), String)) End If Next Return dbRecString.ToString End Function
private string ComplexTypeString(DbDataRecord item) { var dbRecString = new StringBuilder(); for (var i = 0; i < item.FieldCount; i++) { if (item[i] == DBNull.Value) { dbRecString.AppendLine(""); } else { dbRecString.AppendLine((String)(item[i])); } } return dbRecString.ToString(); }
You could take this a step further and find the property names of the complex type. You probably don't want to attempt to find these from within the DataRecordInfo . It would be much simpler to use the MetadataWorkspace API directly to read the Conceptual Schema Definition Layer (CSDL) and determine the property name of the complex type—in this case, AddressDetail. You can discover that name through the same TypeUsage property you used earlier to identify that this was a ComplexType:
currentValues.DataRecordInfo.FieldMetadata(i) .FieldType.TypeUsage.EdmType.Name
currentValues.DataRecordInfo.FieldMetadata[i] .FieldType.TypeUsage.EdmType.Name
Shortly, you'll see how to perform the next steps with the MetaDataWorkspace API. Figure 17-5 displays the results (without the additional property names of the complex type).
NOTE You can download the visualizer's code from the book's website.
Figure 17-5. An Address entity with a ComplexType property displayed in the visualizer by reading the ObjectStateEntry
17.4.7. Modifying Values with ObjectStateManager Because the CurrentValues property returns an updatable DbDataRecord , it is possible to modify the values directly through the ObjectStateManager. Like the various accessors for a DbDataReader and a DbDataRecord, CurrentValues allows you to change a value using SetValue or one of the type-specific setters such as SetString, SetInt32,
or even SetDBNull.
The signature for these methods is to pass in the value to be used for updating and the index of the item in the array. Again, remember that you can do this directly only with the scalar values. If you need to change relationships, more work is involved. Example 17-20 shows the signature for CurrentValueRecord.SetBoolean .
Example 17-20. Changing a Boolean property with SetBoolean
ObjectStateEntry.CurrentValues.SetBoolean(3,False)
ObjectStateEntry.CurrentValues.SetBoolean(3,false)
The plural SetValues lets you pass in an array to update all of the values, as shown in Example 17-21. SetValues requires that you know the order and types of the properties. There are two fields that you don't want to change, however: the ContactID and TimeStamp values. Those fields will just have their current values passed back in.
Example 17-21. Changing all of the values with SetValues
currentValues.SetValues(currentValues(0), "Pablo", "Castro", "Sr.", _ DateTime.Now, DateTime.Now, currentValues(6))
currentValues.SetValues(currentValues[0],"Pablo","Castro","Sr.", DateTime.Now,DateTime.Now, currentValues[6]);
17.4.8. Working with Relationships in ObjectStateManager You've seen the RelationshipEntry in ObjectStateManager. As with EntityEntry types, the debugger doesn't provide a lot of information, especially critical information, to help you identify which entities the relationship is for. You can access this information using CurrentValues, which returns EntityKey s in the first and second index positions. Because a relationship has two ends, each set has only two fields containing the EntityKey of the entity on each end of the relationship.
NOTE Although you will also find the EntityKeys in the OriginalValues (unless the relationship is Added), the OriginalValues are not truly viable. The property exists because it is there for all EntityStateObject s, but you should not rely on it for RelationshipEntries. Stick with the CurrentValues. Because the RelationshipEntry describes a relationship between two entities, the EntityKey s found within the CurrentValues will match up with EntityKeys of ObjectStateEntries in the context. Figure 17-6 shows a RelationshipEntry that defines the relationship between a Customer and a Reservation.
Figure 17-6. A RelationshipEntry defining a relationship between two entities using EntityKeys
Object Services uses the values in the RelationshipEntry to determine how to build graphs with the entities in the context. It also uses this information to build commands that involve foreign keys when SaveChanges is called. If you need to work with code generically, you can take advantage of the RelationshipEntries as well.
17.4.8.1. RelationshipEntry EntityState When a new relationship is created between entities, a RelationshipEntry is created and its EntityState is Added. The EntityState of a RelationshipEntry that is created of a graph being added to the context is Unchanged . This is also true for related entities that were returned from a query. When a relationship is removed (e.g., you remove a Reservation from a Customer's Reservations collection), the existing RelationshipEntry 's EntityState becomes Deleted. If you change a relationship (e.g., move a payment from one reservation to another), the existing relationship is marked Deleted and a new relationship is created with its EntityState set to Added. A RelationshipEntry will never have a Modified EntityState.
17.4.8.2. Inspecting the RelationshipEntries You can filter the RelationshipEntries using the IsRelationship property. Then you can start digging into the values for each end of the current state of the relationship and the original state. Example 17-22 uses the GetObjectStateEntries overload to return entries from an already populated context, regardless of their EntityState . Then it filters for only those that are relationships.
Example 17-22. Inspecting the RelationshipEntry objects
For Each relEntry In _ (From ose In context.ObjectStateManager.GetObjectStateEntries() _ Where ose.IsRelationship) Dim currRelEndA As EntityKey = relEntry.CurrentValues(0) Dim currRelEndB As EntityKey = relEntry.CurrentValues(1) Next
foreach (var relEntry in (from ose in context.ObjectStateManager.GetObjectStateEntries() where ose.IsRelationship select ose)) { EntityKey currRelEndA = relEntry.CurrentValues[0]; EntityKey currRelEndB = relEntry.CurrentValues[1]; }
Figure 17-7 shows the last value, the second end of the original values of the relationship, which, as promised, is an EntityKey . You can see that the EntityKey is for a CustomerType.
The second CurrentValues item contains an EntityKey for the Customer entity, which is attached to this CustomerType in this particular relationship.
Figure 17-7. An EntityKey that is the result of the RelationshipEntry's CurrentValues property requested in Example 17-22
The EntityKey s provide the common thread throughout the graph. Now, given an entity, you can take its key, query the RelationshipEntries to find the relationships that it is part of, and from those relationships, find the other ends.
17.4.8.3. Locating relationships for an entity You can search relationship entries for an EntityKey to see which relationships a particular entity is involved in. Example 17-23 is a handy extension method for ObjectStateEntry that
searches a relationship for a given EntityKey. If the EntityKey does not exist, the result will be null. If it does exist, rather than just returning a
Boolean of True , the result will be the ordinal representing the position (0 or 1) of the EntityKey in CurrentValues . This way, not only do you know that the entity is involved in that relationship, but also you can use the ordinal to retrieve the EntityKey of the related item. Notice in the example that it's necessary to cast the item to an EntityKey.
Example 17-23. Finding relationships with a particular entity
_ Public Function KeyIsinRelationship _ (ByVal relatedEnd As ObjectStateEntry, _ ByVal eKey As EntityKey) As Nullable(Of Integer) 'check currentvalues 0 and 1 for this entity key If CType(relatedEnd.CurrentValues(0), EntityKey) = eKey Then Return 0 ElseIf CType(relatedEnd.CurrentValues(1), EntityKey) = eKey Then Return 1 Else Return Nothing End If End Function
public static Nullable KeyIsinRelationship (this ObjectStateEntry relatedEnd, EntityKey eKey) { //check currentvalues 0 and 1 for this entity key if (((EntityKey)(relatedEnd.CurrentValues[0])) == eKey) return 0; else if (((EntityKey)(relatedEnd.CurrentValues[1])) == eKey) return 1; else return null; }
If you had a Customer entity in the context, you could use the extension method to help you find all of the entities that are related to the Customer. Recall that the method that you learned about in Chapter 9 only affects scalar values. If you were writing a method to apply changes throughout a graph and
ApplyPropertyChanges
you needed the method to be generic, you could use this technique to discover additional entities in the graph that should have changes applied as well.
Why an Extension Method? Why am I creating extension methods rather than regular methods? In the cases discussed in this section, it's for discoverability. By creating an extension method specifically for an ObjectStateEntry , you can find that method very easily. Even if you didn't know it was there, IntelliSense would show it to you and make you aware. But there is a tendency to overuse extension methods because they are handy and fun to write. So, be prepared to justify using it, even if you need to make that justification only to yourself.
Example 17-24 iterates through the RelationshipEntries looking for a relationship that contains a particular entity. This example excludes Deleted entries. It then grabs the EntityKey of the other related end and gets the related entity from the context. Remember that the purpose of this code is to be generic, which is why you see EntityObject being used rather than a particular entity type.
Example 17-24. Using the KeyIsInRelationship method to find the other end of a relationship
Dim osm = context.ObjectStateManager For Each relEntry In _ (From entry In osm.GetObjectStateEntries _ (EntityState.Unchanged Or EntityState.Added) _ Where entry.IsRelationship)
Dim otherKey As EntityKey Dim otherEntity As EntityObject
Dim pmtOrdinal = relEntry.KeyIsinRelationship(entityKeyToFind) If pmtOrdinal.HasValue Then If pmtOrdinal = 0 Then 'get entitykey of other end otherKey = CType(relEntry.CurrentValues(1), EntityKey) Else otherKey = CType(relEntry.CurrentValues(0), EntityKey) End If 'get the entity on other end otherEntity = CType(context.GetObjectByKey(otherKey), EntityObject) End If Next
var osm = context.ObjectStateManager; foreach (var relEntry in ( from entry in osm.GetObjectStateEntries (EntityState.Unchanged | EntityState.Added) where entry.IsRelationship select entry)) { EntityKey otherKey = null; EntityObject otherEntity = null; var pmtOrdinal = relEntry.KeyIsinRelationship(PaymentKey); if (pmtOrdinal.HasValue) { if (pmtOrdinal == 0) //get entitykey of other end otherKey = (EntityKey)(relEntry.CurrentValues[1]); else otherKey = (EntityKey)(relEntry.CurrentValues[0]); otherEntity = (EntityObject)(context.GetObjectByKey(otherKey)); } }
17.4.8.4. Building graphs directly with the RelationshipManager It is possible to get your hands on an instance of the RelationshipManager to build graphs on the fly, creating RelationshipEntr ies directly in your code. The RelationshipManager 's entry point is through the IEntitywithRelationships interface. Every EntityObject implements this interface, and any custom objects that you build will need to implement it as well if you want to have relationships managed by Object Services. The entity does not need to be attached to anObjectContext to get the RelationshipManager. To get the IEntityRelationship view of an existing entity, cast the entity to IEntityRelationship. From there, you can get a RelationshipManager associated specifically with your entity. Example 17-25 gets a RelationshipManager for an existing instance of a Payment object, represented by the variable pmt.
Example 17-25. Getting the RelationshipManager for an instance of a Payment
Dim pmtRelMgr = CType(pmt, IEntityWithRelationships).RelationshipManager
var pmtRelMgr = (IEntityWithRelationships)pmt.RelationshipManager;
Once you have the RelationshipManager , the next step is to get a reference to the other end of the relationship that you want to add. To do this, you need to identify which association and which end of the association you want to work with. Unfortunately, you won't be able to do this in a strongly typed way. You'll have to use a string to specify the association's name. In Example 17-26 , the goal is to add a Reservation to the Payment used in Example 17-25 , so you'll need to work with the FK_Payments_Reservations association and add it to the "Reservations" end.
NOTE Some of the tricks that RelationshipManager performs do not require the ObjectContext . This is handy to know if you are building generic code without the aid of the ObjectContext
. Check out the MSDN Entity Framework forum post titled "Remove Associations from Entity," which shows how to use IRelatedEnd with reflection
to strip related data from an entity. (When reading this forum thread, which I started, you'll also see that I learned this lesson the hard way, too.) RelatedEnd
has an Add method, which is the final call you'll need to make. Example 17-26 shows how you can add the existing Reservation entity to the RelatedEnd. This
will create a new relationship between the Payment entity and the Reservation entity.
Example 17-26. Creating a relationship on the fly using the RelationshipManager created in Example 17-25
Dim resRelEnd As IRelatedEnd = _ pmtRelMgr.GetRelatedEnd(FK_Payments_Reservations,Reservations) resRelEnd.Add(myReservation)
IRelatedEnd resRelEnd = pmtRelMgr.GetRelatedEnd(FK_Payments_Reservations,Reservations); resRelEnd.Add(myReservation);
This method of building graphs works exactly the same as if you had called pmt.Reservation=myReservation . If neither object is attached to the context, you will still get a graph; however, no RelationshipEntry will be created in the context. If only one of the entities is attached to the context, the other one will be pulled in and given the appropriate EntityState (Attached or Added).
NOTE RelatedEnd
also has a Remove method, so you can deconstruct graphs as well.
17.5. ObjectStateManager and SavingChanges One of the most useful places to take advantage of theObjectStateManager is in the ObjectContext.SavingChanges event handler. You saw some examples of using the SavingChanges event in Chapter 10, where you used
GetObjectStateEntries to find Modified and Added entries, and then to do some last-minute work on particular types.
NOTE The other events, PropertyChanged/Changing and AssociationChanged , do not have access to the
ObjectContext or its ObjectStateManager , so you won't include this type of functionality in those event handlers. Now that you have some additional tools at your disposal, you can create validators that will generically work with entities, without knowing their type. Example 17-27 locates any Added or Modified entries that have a ModifiedDate property and then updates that property with the current date and time. This example handles two gotchas that you need to watch out for. The first is that if the entry isRelationshipEntry a , an exception will be thrown when you try to read the metadata. Although you could use IsRelationship to test this, another method will kill two birds with one stone: by testing to see whether the ObjectStateEntry has an Entity value, you not only filter out relationships, but also filter out the "stub" entries that exist only to provide an end for EntityReference s when the entity is not in the context. This filter is used in the first query that returns the entries variable. The second gotcha is that it's possible that a field namedModifiedDate is not a DateTime field. Never assume! The LINQ query in the example drills into theCurrentValues of each entry. Then, using theAny method, it looks at the names of each FieldMetaData item for that entry, picking up only those whose name isModifiedDate . You saw code similar to this when building the visualizer earlier in this chapter. Next, the If statement verifies that theModifiedDate property is a DateTime field; then it updates the field usingCurrentValues.SetDateTime .
Example 17-27. Updating ModifiedDate fields during SavingChanges
Dim entries = _ From e In osm.GetObjectStateEntries _ (EntityState.Added Or EntityState.Modified) _ Where Not e.Entity Is Nothing For Each entry In entries.Where _ (Function(entry) entry.CurrentValues.DataRecordInfo.FieldMetadata _ .Any(Function(meta) meta.FieldType.Name = "ModifiedDate")) Dim ordinal = _ ent.CurrentValues.DataRecordInfo.FieldMetadata _ .Where(Function(meta) meta.FieldType.Name = "ModifiedDate") _ .Select(Function(meta) meta.Ordinal).FirstOrDefault
If ent.CurrentValues.DataRecordInfo.FieldMetadata(ordinal) _ .FieldType.TypeUsage.EdmType.Name = _ PrimitiveTypeKind.DateTime.ToString Then ent.CurrentValues.SetDateTime(ordinal, Now) End If Next
var entries = from ose in osm.GetObjectStateEntries (EntityState.Added | EntityState.Modified) where ose.Entity != null select ose; foreach (var entry in entries.Where((entry) => entry.CurrentValues.DataRecordInfo.FieldMetadata .Any((meta) => meta.FieldType.Name == "ModifiedDate"))) { var ordinal = entry.CurrentValues.DataRecordInfo.FieldMetadata .Where((meta) => meta.FieldType.Name == "ModifiedDate") .Select((meta) => meta.Ordinal) .FirstOrDefault(); if (entry.CurrentValues.DataRecordInfo.FieldMetadata[ordinal] .FieldType.TypeUsage.EdmType.Name == PrimitiveTypeKind.DateTime.ToString()) entry.CurrentValues.SetDateTime(ordinal, System.DateTime.Now); }
NOTE Importing the System.Data.Metadata.Edm namespace gives you access to thePrimitiveTypeKind class. This code takes advantage of a lot of the details exposed in the metadata. The For Each has filtered down to any entity that has a ModifiedDate property, but you still need to know which property that is for the SetValue/SetDateTime method. This is why you see the line of code that finds the exact property and returns the ordinal that can be found in the metadata.
17.5.1. The FieldMetadata Hierarchy The type name in the previous example is buried deep in the class and is not very discoverable. But as you have seen, it can definitely be a worthwhile effort to uncover that data. Everything that's described in the EDM is accessible through metadata. But knowing where the information is and how to access it is definitely a challenge. In the MSDN documentation, a topic called "Metadata Type Hierarchy Overview" contains a diagram displaying the hierarchy of the
EDM metadata. To help you get started, here are some of the critical parts of the hierarchy:
CurrentValues.DataRecordInfo.FieldMetadata This is an array ofFieldMetadata objects for each scalar property (this includes complex types) in the entity. Each item in the Metadata array is a Metadata.Edm.MetadataProperty.
CurrentValues.DataRecordInfo.RecordType.EdmType This contains the property settings of the entity; for example,Name, Abstract, and NamespaceName .
CurrentValues.DataRecordInfo.RecordType.EdmType.EntityType In addition to the same properties that are exposed directly from EdmType , in here you can find the full metadata for each of the entity's "members," which means not only the scalar properties, but also the navigation properties. Each member is detailed either as anEdmProperty or as a navigation property. Opening these will display the details of each property—the property's name, its facets, and its TypeUsage , which contains information regarding its type (String, DateTime, etc.). The KeyMembers property shows only those members that comprise theEntityKey. The Members property lists all of the members. As you begin to investigate theEntityType , it starts to become clear that everything you did to define the entity, its properties, and its relationships is available here. Additionally, the DataRecordInfo provides a variety of views. For example,FieldMetaData is a subset of
RecordType.EdmType.EntityType.Members. So, you really can get at the metadata you are seeking in a variety of ways.
NOTE Now that you have read this, look again atExample 17-27 . Hopefully the code will make a lot more sense than it did the first time you looked at it. Don't hesitate to poke around in the debugger to see how all of these puzzle pieces fit together now that it isn't quite as mysterious.
17.6. The MetadataWorkspace API At this point, you have seen a lot of interaction with the metadata through the ObjectStateManager . You can also work directly with the metadata through the MetadataWorkspace , which is in the System.Data.Metadata.Edm namespace. The MetadataWorkspace API is used internally throughout the Entity Framework. It is the mechanism that reads the EDM. It can also read the generated classes, as you've seen in this chapter's examples. In addition to being able to get metadata about the entity types and other model objects, the EntityClient uses the metadata during query processing. After the LINQ to Entities or Entity SQL queries are turned into command trees using the conceptual model, these command trees are then transformed into a command tree using the store schema. The conceptual, store, and mapping layers of the model are read in order to perform this task. You can use the MetadataWorkspace API to read and dissect each of the three layers, as well as the entity classes that are based on the model. The power of the MetadataWorkspace lies in its ability to let you not only write generic code, but also write code that can create objects on the fly. You could write utilities or entire applications that have no knowledge in advance of the conceptual model.
17.6.1. Loading the MetadataWorkspace In Chapter 16, you learned that an EntityConnection loads the metadata when it is opened. Typically, it does this by loading the actual files (CSDL, Mapping Schema Layer [MSL], and Store Schema Definition Layer [SSDL]) that the metadata attribute of the connection string points to. It is also possible to load these files into a memory stream and pass that memory stream in when you are instantiating an EntityConnection. The MetadataWorkspace can work only with metadata that has already been loaded, which happens when anEntityConnection is created directly or as a result of an ObjectContext being instantiated. Example 17-28 demonstrates loading the MetadataWorkspace from an EntityConnection and then from an ObjectContext.
Example 17-28. Accessing the MetadataWorkspace from an EntityConnection and an ObjectContext
Dim econn As New EntityConnection("name=BAEntities") Dim mdw = econn.GetMetadataWorkspace() Dim context As New BAEntities Dim mdw = context.MetadataWorkspace
var econn = new EntityConnection("name=BAEntities"); var emdw = econn.GetMetadataWorkspace(); BAEntities context = new BAEntities(); var mdw = context.MetadataWorkspace;
17.6.1.1. Creating a MetadataWorkspace without an EntityConnection
You can also instantiate a MetadataWorkspace if you don't need to make a connection, by using the overload of the MetadataWorkspace constructor, which takes file paths and assemblies. You can point directly to the files or instantiate a System.Reflection.Assembly to use this constructor. One enumerable that contains file paths and another enumerable that contains assemblies are required; however, you can leave the enumerables empty. Example 17-29 loads the conceptual and store metadata directly from files using syntax to create an array on the fly. Because no assembly is needed in this example, an empty array is created for the second parameter.
Example 17-29. Creating a MetdataWorkspace using EDM files
Dim mdw = New MetadataWorkspace _ (New String(){"C:\EFModels\BAModel.csdl", _ "C:\EFModels\BAModel.ssdl"}, _ New Assembly(){})
var mdw = new MetadataWorkspace (new string[] { "C:\\EFModels\\BAModel.csdl", "C:\\EFModels\\BAModel.ssdl" }, new Assembly[]{});
If the model is embedded in an assembly, you can use syntax similar to the metadata property of anEntityConnection string to point to the files and then provide an assembly that is a type loaded in through System.Reflection . This enables the Entity Framework to inspect the assembly and find the embedded files. Example 17-30 shows one of many ways to load an assembly.
Example 17-30. Creating a MetadataWorkspace from EDM files embedded in an assembly file
Dim assem As Assembly = Assembly.LoadFile("C:\myapp\BreakAwayModel.dll") Dim mdw = New MetadataWorkspace _ (New String() {"res://*/BAModel.csdl", "res://*/BAModel.ssdl"}, _ New Assembly() {assem})
Assembly assem = Assembly.LoadFile("C:\\myapp\\BreakAwayModel.dll"); var mdw = new MetadataWorkspace (new string[] { "res://*/ BAModel.csdl", "res://*/ BAModel.ssdl" }, new Assembly[] { assem });
If you need to use the MetadataWorkspace only to read the models, this is a nice option to leverage.
17.6.2. Clearing the MetadataWorkspace from Memory Remember that loading the metadata into memory is expensive, so you should leave it in an application cache or in the lifetime of the application. It is possible, however, to clear it out and force it to be reloaded if you require it again, by calling MetadataWorkspace.ClearCache.
17.6.3. The MetadataWorkspace ItemCollections The metadata has a number of sources: the model's compiled assembly, the CSDL, the MSL, and the SSDL. TheMetadataWorkspace contains five separate item collections, one for each of these different resources. Once you have the MetadataWorkspace , you can start to drill into the metadata, but you always need to specify which item collection to access by using a DataSpace enum: CSpace for the conceptual model, SSpace for the storage model, OSpace for the object model, CSSpace for the mapping layer, and finally, OCSpace for a mapping between the conceptual layer and the object model.
NOTE When you're reading about the Entity Framework, you will find that developers who have been working with the Entity Framework for a while sometimes use the words C-Space and O-Space , among other similar terms, to refer to theDataSpace . This is how they differentiate between the classes and the various parts of the model, since so many of the terms cross over into all of these areas. Saying "the contact in the O-Space" makes it clear that you are talking about the class. "The contact in theC-Space " means the contact specified in the conceptual model, as opposed to "the contact in the S-Space," which refers to the Contact table from the database as it is described in the model's store layer. These terms also appear in messages when the model can't be validated because of an error somewhere in the schema.
Although the compiler will allow you to combine theDataSpace enums using operators such as And and Or , the enums are integers, not expressions, and they are not meant to be combined in this way. You won't get an exception, but the returned values will be incorrect. Instead, perform the methods on one DataSpace at a time and then combine the results. You can use LINQ'sUnion operator for this purpose.
17.6.4. ItemCollections Are Loaded as Needed with EntityCollection When the MetadataWorkspace is created as the result of an EntityCollection being instantiated, not all of the item collections are loaded right away. For example, the metadata from the store layer isn't loaded until the first time something is done that requires the store layer—a query execution, for instance, or a call to the ToTraceString method. If you attempt to extract metadata from an item collection that has not yet been loaded, an exception will be thrown. Therefore, most of the methods for extracting metadata (e.g., GetItem, GetFunctions) come paired with a method using the Try pattern TryGetItem, ( TryGetFunctions ), which returns Booleans rather than risking an exception being thrown if no data is returned. When you use the MetadataWorkspace constructor with the file paths overload as shown earlier, all of the designated models are
loaded immediately.
17.6.4.1. GetItemCollection/TryGetItemCollection You can also test to see whether an ItemCollection has been loaded prior to attempting to get information from it, by using GetItemCollection and TryGetItemCollection . It makes more sense to use the latter so that you don't get an exception. The code in Example 17-31 tests to see whether the SSpace is loaded.
Example 17-31. Testing to see whether a DataSpace, specifically the SSpace, is loaded
Dim SSpaceColl As ItemCollection Dim SSpaceLoaded as Boolean= _ mdw.TryGetItemCollection(DataSpace.SSpace, SSpaceColl) Then
ItemCollection SSpaceColl = null; if (mdw.TryGetItemCollection(DataSpace.CSpace, out SSpaceColl))
Other than triggering the model to load through query generation, as explained earlier, there's no other way to force a model to load to an existing MetadataWorkspace.
17.6.5. Reading Metadata from the MetadataWorkspace You can pull information from these collections using a variety of methods.
17.6.5.1. GetItems/TryGetItems You can use GetItems or TryGetItems to find all items or items of a specific type in a model.Example 17-32 will return an array of every item defined in the model.
Example 17-32. Requesting an array of every item defined in the CSDL
mdw.GetItems(Metadata.Edm.DataSpace.CSpace)
mdw.GetItems(Metadata.Edm.DataSpace.CSpace)
You'll find in here not only the EntityType and AssociationType items that are defined in your model, but also all of thePrimitiveType s and FunctionType s that the model needs to be aware of. PrimitiveType s are .NET, EDM, and store types.FunctionType s are built-in functions from the provider as well as the functions that are created from stored procedures in the database. Most likely you will not need access to all of these items; therefore, the GetItems overload shown in Example 17-33 , which lets you specify which types to return, might be more useful and efficient.
Example 17-33. Requesting an array of all EntityTypes in the CSDL
mdw.GetItems(Of EntityType)(DataSpace.CSpace)
mdw.GetItems(DataSpace.CSpace)
The array that is shown in Figure 17-8 should look familiar to you. CurrentValues.DataRecordInfo.RecordType.EdmType.EntityType returns the same Edm.EntityTypes. In there, you can find out anything you might want or need to know about the structure of an entity. As with the other Try alternatives you have seen already,TryGetItems follows the .NET Try pattern to avoid an exception if no matching items are returned.
Figure 17-8. The results of mdw.GetItems(Of EntityType) (DataSpace.CSpace)
Notice again how the entity's properties are grouped in a few different ways to make it easier to access what you are looking for: KeyMembers returns only the properties that are used to build theEntityKey. Members returns all of the properties. NavigationProperties returns a subset of members. Properties returns only the scalar properties (includingComplexTypes). The biggest benefit of being able to get at this information is the ability to write dynamic functionality in your application. Not only can you instantiate objects, as you'll see in a bit, but also this metadata is an optimal source for report design tools, just as other schemas, such as the DataSet XSD files, are used for report design.
17.6.5.2. GetItem/TryGetItem GetItem and TryGetItem allow you to pass in a string to specify the name of the item you would like to get from the model, rather than returning an array. The name must be fully qualified, not just the string used for the entity's name property. Example 17-34 shows how to call GetItem.
Example 17-34. Getting EntityTypes that are Contacts from the CSDL
mdw.GetItem(Of EntityType)("BAModel.Contact",DataSpace.CSpace)
mdw.GetItem("BAModel.Contact", DataSpace.CSpace)
If you pass multiple DataSpace enums into this method and one or more of them do not contain this particular item, an exception will be thrown. Use TryGetItem as a precaution. InExample 17-34 , you can assume that the model to search is obvious, but if you are building generic methods where you always have a number of models in the parameter, it is possible to run into this problem.
17.6.5.3. GetFunctions/TryGetFunctions GetFunctions and TryGetFunctions will return only functions, but they are different from just callingGetItems . Instead, you need to specify the name and the namespace of the function separately (as opposed to the fully qualified name requirement in GetItem ), as well as the DataSpace. The code in Example 17-35 returns the EdmFunction displayed in Figure 17-9.
Example 17-35. Getting a list of functions from the SSDL
mdw.GetFunctions("UpdateContact", "BAModel.Store", DataSpace.SSpace)
mdw.GetFunctions("UpdateContact", "BAModel.Store", DataSpace.SSpace);
Figure 17-9. Debugging a function's metadata
Compare this to the function's description in the SSDL, shown inExample 17-36.
Example 17-36. The UpdateContact function listing in the SSDL
Everything you see in the schema is available through theMetadataWorkspace.
17.6.6. Querying the Items It's also possible to perform LINQ queries against item collections. As an example, the method query inExample 17-37 searches the store model's ItemCollection to find any item that has Contact in the name.
Example 17-37. Querying the items of the model with LINQ
mdw.GetItems(Of EdmType)(DataSpace.SSpace) _ .Where(Function(i) i.Name.Contains("Contact"))
mdw.GetItems(DataSpace.SSpace) .Where(i => i.Name.Contains("Contact"));
From the BreakAway model, this query returns 10 items that represent the database schema: Contact and ContactPersonalInfo EntityTypes (tables) FK_Address_Contact, FK_Lodging_Contact, and FK_Customers_Contact AssociationTypes Five different functions (stored procedures) with Contact in the title If you want to search across models, you can use LINQ'sUnion query method, which follows the pattern query1.Union(query2).otherMethods, as shown in Example 17-38.
Example 17-38. Combining items from the CSDL and SSDL in one request
mdw.GetItems(Of EdmType)(DataSpace.CSpace) _ .Union(mdw.GetItems(Of EdmType)(DataSpace.SSpace)) _ .Where(Function(i) i.Name.Contains("Contact"))
mdw.GetItems(DataSpace.CSpace) .Union(mdw.GetItems(DataSpace.SSpace)) .Where(i => i.Name.Contains("Contact"));
This returns all of the items with the word Contact in both the conceptual and store models.
17.6.7. Building Entity SQL Queries Dynamically Using Metadata You can also use the metadata to build Entity SQL queries thanks to the fact that an Entity SQL expression is simply a string. Imagine that you have a query that returns data from every navigation propertyEntityCollections ( or EntityReferences): Select con, con.Addresses,con.Orders from MyEntities.Contacts ....
When the application is first written, Addresses and Orders comprise the only child data for the contact. But perhaps another navigation property is introduced with additional child data, such as PhoneNumbers . By writing the query dynamically using the metadata, you'll never have to modify the query manually. The application will always be able to follow the rule of returning a contact with all of its child data without having to know what the definition of "all of its child data" is. Building the query dynamically will also mean you won't know exactly what is contained in the results, and therefore you may also need to use the metadata to handle the results. Using the BreakAway model, here's how to do that.
First, you need to make some presumptions. The method will need to know what name is used for an entity when it is a navigation property. It needs both the EntityReference version and the EntityCollection version. For example, if the entity isReservation, in the Payment entity the navigation property isPayment.Reservation, whereas in Customer it's Customer.Reservations . For this sample, assume that the model was designed so that Entity.Name and EntityReference are always the same and thatEntityCollection is the same in every entity so that you don't have to worry about "what if" scenarios. Therefore, the Reservation and Reservations strings need to be provided to the method.
NOTE It's a good idea to establish patterns in your model naming for many reasons. Being able to construct generic code in this way is one of those reasons. You'll also have the query perform a filter to find the reservation whose ID is 100. The signature of the method shown in Example 17-39 takes the EntityReference, the EntityCollection , and the entity's key value as well as a reference to the MetadataWorkspace to read.
Example 17-39. The BuildSingle signature
Private Function BuildSingleEntQuery _ (ByVal EntityRef As String, ByVal EntityColl As String, _ ByVal EntityID As Int32, ByVal mdw As MetadataWorkspace) As String
private static string BuildSingleEntQuery (string EntityRef, string EntityColl, Int32 EntityID, MetadataWorkspace mdw)
Next, you're going to need the EntityContainer name for the query and theNamespaceName so that you can use theGetItem method. You can find the NamespaceName within various types, but not directly from theMetadataWorkspace, so the code inExample 17-40 grabs a random EntityType from the CSpace and gets the NamespaceName from there.
Example 17-40. Finding the Container and Namespace names using the MetadataWorkspace
Dim mdw = context.MetadataWorkspace Dim containername = mdw.GetItems(Of EntityContainer) _
(DataSpace.CSpace).First.Name Dim namespacename = mdw.GetItems(Of EntityType) _ (DataSpace.CSpace).First.NamespaceName
Var mdw=context.MetadataWorkspace; var containername = mdw.GetItems (DataSpace.CSpace).First().Name; var namespacename = mdw.GetItems (DataSpace.CSpace).First().NamespaceName;
Next, get the Reservation EntityType to find its navigation properties. Then iterate through the navigation properties, adding each name to a List (see Example 17-41).
Example 17-41. Creating a list of navigation property names
'instantiate a List to contain the navigation properties Dim listOfNavs As New List(Of String) 'get the Reservation type Dim EntityInfo = mdw.GetItem(Of EntityType) _ (namespacename & "." & EntityRef, DataSpace.CSpace) 'get its nav properties Dim navs = EntityInfo.NavigationProperties For Each nav In navs listOfNavs.Add(nav.Name) Next
//instantiate a List to contain the navigation properties List listOfNavs = new List(); var EntityInfo = mdw.GetItem (namespacename + "." + EntityRef, DataSpace.CSpace); //get nav properties var navs = EntityInfo.NavigationProperties; foreach (var nav in navs) listOfNavs.Add(nav.Name);
You can use the KeyMembers property we discussed inSection 17.5.1 to get the name of the property or properties that contain the key field. The code in Example 17-42 assumes that only one property is used for theEntityKey and only one key value has been passed in (EntityID).
Example 17-42. Getting the name of an entity's key property
Dim singlekeyName = EntityInfo.KeyMembers(0).Name
var singlekeyName = EntityInfo.KeyMembers[0].Name;
For the Reservation EntityType, this will return the stringReservationID. Now you can finally build the string using the names inlistofNavs and the various other variables you have collected along the way (see Example 17-43).
Example 17-43. Building the Entity SQL expression and closing the method
Dim esqlSB As New Text.StringBuilder esqlSB.Append("SELECT ent") 'add each navigation property to the projection For Each name In listOfNavs esqlSB.Append(",ent." & name) Next 'add the FROM...AS clause esqlSB.Append _ (" FROM " & containername.Trim & "." & EntityColl & " AS ent") esqlSB.Append(" WHERE ent." & singlekeyName & " = " & IDtoFind) Return esqlSB.ToString End Function 'end of the function
StringBuilder esqlSB = new StringBuilder(); esqlSB.Append("SELECT ent"); //add each navigation property to the projection foreach (var name in listOfNavs) esqlSB.Append(",ent." + name); //add the FROM...AS clause esqlSB.Append (" FROM " + containername.Trim() + "." + EntityColl + " AS ent"); //Add the filter esqlSB.Append (" WHERE ent." + singlekeyName + " = " + EntityID); return esqlSB.ToString(); } //end of the method
Now you can create a routine to call the function (seeExample 17-44 ). Note that the query is explicitly executed so that you can work with the results, not the actual query, and that you don't have to worry about inadvertently executing the query again.
Example 17-44. Testing the method
Private Sub DynamicESQLTest() Using context As New BAEntities Dim esql = BuildSingleEntQuery _ ("Reservation", "Reservations", 90, context.MetadataWorkspace) Dim query = context.CreateQuery(Of DbDataRecord)(esql) Dim queryResults = query.Execute(MergeOption.AppendOnly) For Each entity In queryResults 'PLACEHOLDER: iterate through the data Next End Using End Sub
private static void DynamicESQLTest() { using (BAEntities context = new BAEntities()) { var esql = BuildSingleEntQuery ("Reservation", "Reservations", 90, context.MetadataWorkspace); var query = context.CreateQuery(esql); var queryResults = query.Execute(MergeOption.AppendOnly); foreach (var entity in queryResults) //PLACEHOLDER: iterate through the data;
} }
Example 17-45 displays the Entity SQL expression that results.
Example 17-45. The Entity SQL expression that results
SELECT ent,ent.Customer,ent.Trip,ent.Payments,ent.UnpaidReservation FROM BAEntities.Reservations AS ent WHERE ent.ReservationID = 90
17.6.8. Reading the Results of a Dynamically Created Query Iterating through the data without knowing what is in there might be a little tricky. However, since this was an ObjectQuery, all of the data is in the ObjectContext. So, as you iterate through the data, you can accessObjectStateEntries and use the tricks you learned earlier in this chapter to find out about the entities using generic code. You can replace the empty iteration used earlier with a new bit of code that will do the following for each DbDataRecord returned by the query: 1.
Cast the row to an IExtendedDataRecord, which will turn the DbDataRecord into a record containing aDataRecordInfo object, a FieldCount property, and a list of items representing the fields of the row. Based on the preceding query, you should expect the row to contain the following: Field(0): A Reservation entity Field(1): A Customer entity Field(2): A Trip entity Field(3): EntityCollection Field(4): An UnpaidReservation entity
2.
Iterate through each field in the row.
3.
Identify whether the field is an EntityType.
4. Pass the EntityKey of the entity to another method that will find theObjectStateEntry in the ObjectStateManager and list the name and value of each field. 5. Identify whether the field is an EntityCollectionType. 6. Cast the field to an IEnumerable , and then iterate through the entities in that collection and perform the same method on the EntityKey of each entity.
NOTE Casting to the IEnumerable is not a random act of coding. You need to cast the field to something in order to access it, but the EntityCollection surfaces as a genericList , which causes a problem. Because this code is dynamic, you can't specify the type. It would be nice to cast it to a List, but you cannot cast a genericList to another generic List. Therefore, casting to a standard list type, such as ICollection or IEnumerable, does the trick. Internally, there are advantages to
using IEnumerable, so this was the winning target of the cast. In Example 17-44 , a line of code indicated a placeholder for iterating through the data. Replace that placeholder with the code Example in 17-46 , which drills deep into theFieldMetadata hierarchy. The routine calls out to another method,DisplayFields, shown in Example 17-46.
Example 17-46. Iterating through the results and determining whether the navigation properties are entities or EntityCollections
For Each ent As Data.IExtendedDataRecord In query Dim fieldCount = ent.FieldCount For i As Integer = 0 To fieldCount - 1 'If the navigation property is an Entity, list its fields. If ent.DataRecordInfo.FieldMetadata(i).FieldType.TypeUsage. _ EdmType.BuiltInTypeKind = BuiltInTypeKind.EntityType Then DisplayFields CType(ent(i), EntityObject).EntityKey, context) 'otherwise, if it's a collection, iterate through the collection 'and list the fields for each entity in the collection ElseIf ent.DataRecordInfo.FieldMetadata(i).FieldType.TypeUsage. _ EdmType.BuiltInTypeKind = BuiltInTypeKind.CollectionType Then Dim listofEntities = CType(ent(i), Collections.IEnumerable) For Each collEnt As EntityObject In listofEntities DisplayFields(collEnt.EntityKey, context) Next End If Next Next
foreach (IExtendedDataRecord ent in queryresults) { var fields = ent.FieldCount; for (int i = 0; i < fields; i++) { //If the navigation property is an Entity, list its fields. if (ent.DataRecordInfo.FieldMetadata[i].FieldType.TypeUsage. EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType) DisplayFields(((EntityObject)(ent[i])).EntityKey, context); //otherwise, if it's a collection, iterate through the collection //and list the fields for each entity in the collection
else if (ent.DataRecordInfo.FieldMetadata[i].FieldType.TypeUsage. EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType) { var listofEntities = (System.Collections.ICollection)(ent[i]); foreach (EntityObject collEnt in listofEntities) DisplayFields(collEnt.EntityKey, context); } } } }
The DisplayFields method takes the EntityKey and the context, and then digs into theObjectStateManager to get the information it needs regarding the entity. Like the visualizer, this method takes into account the possibility of complex types, as shown in Example 17-47.
Example 17-47. The DisplayFields method getting the field names and values from metadata
Private Sub DisplayFields(ByVal ekey As EntityKey, _ ByVal context As ObjectContext) Dim entEntry = context.ObjectStateManager.GetObjectStateEntry(ekey) Dim fieldcount = entEntry.CurrentValues.FieldCount Dim metadata = entEntry.CurrentValues.DataRecordInfo.FieldMetadata() Console.WriteLine(entEntry.CurrentValues.DataRecordInfo _ .RecordType.EdmType.Name) For i = 0 To fieldcount - 1 Select Case metadata(i).FieldType.TypeUsage.EdmType.BuiltInTypeKind Case BuiltInTypeKind.PrimitiveType Console.WriteLine(" " & metadata(i).FieldType.Name & ": " & _ entEntry.CurrentValues(i).ToString) Case BuiltInTypeKind.ComplexType Dim ct = entEntry.CurrentValues.GetDataRecord(i) For j = 0 To ct.FieldCount Console.WriteLine(" " & ct.GetName(i) & ": " & ct(j).ToString) Next End Select Next Console.WriteLine() End Sub
private static void DisplayFields(EntityKey ekey, ObjectContext context) { var entEntry = context.ObjectStateManager.GetObjectStateEntry(ekey); var fieldcount = entEntry.CurrentValues.FieldCount;
var metadata = entEntry.CurrentValues.DataRecordInfo.FieldMetadata; Console.WriteLine(entEntry.CurrentValues.DataRecordInfo .RecordType.EdmType.Name); for (var i = 0; i < fieldcount; i++) { switch (metadata[i].FieldType.TypeUsage.EdmType.BuiltInTypeKind) { case BuiltInTypeKind.PrimitiveType: Console.WriteLine(" " + metadata[i].FieldType.Name + ": " + entEntry.CurrentValues[i].ToString()); break; case BuiltInTypeKind.ComplexType: var ct = entEntry.CurrentValues.GetDataRecord(i); for (var cti = 0; cti from c in _commonContext.Contacts.OfType() .Include("Addresses") .Include("Reservations.Trip.Destination") .Include("Reservations.UnpaidReservations") .Include("Reservations.Payments") where c.ContactID == custID select c); } public Customer GetCustomerwithReservationsAndAddresses(Int32 ContactID) { if (_compiledCustGraphQuery == null) PreCompileCustGraph(); var query = _compiledCustGraphQuery.Invoke(_commonContext,ContactID); return _dal.ExecuteFirstorDefault(query); }
The GetCustomer method, shown in Example 22-10, gets the Customer graph using the new provider method. The returned data is then transformed into a Customer DTO class by a helper method, which in turn converts the Reservations and Addresses children to DTOs.
Example 22-10. The GetCustomer service operation
Public Function GetCustomerDTO(ByVal contactID As Integer) As BAGA.DTO.Customer Using custProvider = New BAGA.Providers.CustomerProvider Dim cust = custProvider.GetCustomerwithRelatedData(contactID) Dim custDTO = CustEF_to_CustDTO(cust) custDTO.EntityState_Local = BAGA.DTO.EntityStateLocal.Unchanged InitializeChildren(custDTO.Reservations) InitializeChildren(custDTO.Addresses) Return custDTO
End Using End Function
public BAGA.DTO.Customer GetCustomerDTO(int contactID) { using (var custProvider=new BAGA.Providers.CustomerProvider()) { var cust = custProvider.GetCustomerwithRelatedData(contactID); var custDTO = CustEF_to_CustDTO(cust); custDTO.EntityState_Local = BAGA.DTO.EntityStateLocal.Unchanged; InitializeChildren(custDTO.Reservations); InitializeChildren(custDTO.Addresses); return custDTO; } }
The GetCustomer method calls two other methods in theService class: CustEF_to_CustDTO and InitializeChildren. Following are the descriptions and code listings for both methods.
22.5.4. Building Methods to Create DTOs from Entity Objects The set of helper methods shown in Example 22-11 handles transferring the entity objects into the DTOs that the service will return. The methods are abbreviated and do not show all of the properties being set.
Example 22-11. Methods to push entity objects into DTOs
Private Function CustEF_to_CustDTO(ByVal cust As Customer) As BAGA.DTO.Customer 'select customer scalars into new cust Dim custDTO = New BAGA.DTO.Customer With { _ .CustomerID = cust.ContactID, _ .TimeStamp=cust.TimeStamp, _ .FirstName = cust.FirstName, _ .LastName = cust.LastName, _
.Notes = cust.Notes} 'careful with possible null reference values If Not cust.CustomerType Is Nothing Then custDTO.CustomerTypeID = cust.CustomerType.CustomerTypeID custDTO.CustomerTypeName = cust.CustomerType.CustomerTypeName End If If Not cust.PrimaryActivity Is Nothing Then custDTO.PrimaryActivityID = cust.PrimaryActivity.ActivityID custDTO.PrimaryActivityName = cust.PrimaryActivity.ActivityName End If 'child collections custDTO.Reservations = New List(Of BAGA.DTO.Reservation) For Each res In cust.Reservations custDTO.Reservations.Add(ReservationEF_toReservationDTO(res)) Next custDTO.Addresses = New List(Of BAGA.DTO.Address) For Each add In cust.Addresses custDTO.Addresses.Add(AddressEF_toAddressDTO(add)) Next Return custDTO End Function Private Function ReservationEF_toReservationDTO(ByVal res As Reservation) _ As BAGA.DTO.Reservation Dim resDTO = New BAGA.DTO.Reservation With { _ .ReservationID = res.ReservationID, _ .CustomerID = res.Customer.ContactID, _ .ReservationDate = res.ReservationDate, _ .TripDetails = res.TripDetails} resDTO.Payments = New List(Of BAGA.DTO.Payment) For Each Payment In res.Payments resDTO.Payments.Add(PaymentEF_toPaymentDTO(Payment)) Next Return resDTO End Function Private Function AddressEF_toAddressDTO(ByVal add As Address) As BAGA.DTO.Address Dim addDTO = New BAGA.DTO.Address With { _ .addressID = add.addressID, _ .AddressType = add.AddressType, _ .Street1 = add.Street1, _ .City = add.City} Return adddto End Function Private Function PaymentEF_toPaymentDTO(ByVal pmt As Payment) As BAGA.DTO.Payment Dim pmtDTO = New BAGA.DTO.Payment With { _
.Amount = pmt.Amount, _ .PaymentDate = pmt.PaymentDate} Return pmtDTO End Function
private BAGA.DTO.Customer CustEF_to_CustDTO(Customer cust) { //select customer scalars into new cust var custDTO = new BAGA.DTO.Customer { FirstName = cust.FirstName, LastName = cust.LastName, Notes = cust.Notes}; //careful with possible null reference values if (cust.CustomerType != null) { custDTO.CustomerTypeID = cust.CustomerType.CustomerTypeID; custDTO.CustomerTypeName = cust.CustomerType.CustomerTypeName; } if (cust.PrimaryActivity != null) { custDTO.PrimaryActivityID = cust.PrimaryActivity.ActivityID; custDTO.PrimaryActivityName = cust.PrimaryActivity.ActivityName; } //transfer child collections custDTO.Reservations = new List(); foreach (var res in cust.Reservations) custDTO.Reservations.Add(ReservationEF_toReservationDTO(res)); custDTO.Addresses = new List(); foreach (var add in cust.Addresses) custDTO.Addresses.Add(AddressEF_toAddressDTO(add)); return custDTO; } private BAGA.DTO.Reservation ReservationEF_toReservationDTO(Reservation res) { var resDTO = new BAGA.DTO.Reservation { ReservationID = res.ReservationID, CustomerID = res.Customer.ContactID, ReservationDate = res.ReservationDate,
TripDetails = res.TripDetails }; resDTO.Payments = new List(); foreach (var Payment in res.Payments) resDTO.Payments.Add(PaymentEF_toPaymentDTO(Payment)); return resDTO; } private BAGA.DTO.Address AddressEF_toAddressDTO(Address add) { var addDTO = new BAGA.DTO.Address { addressID = add.addressID, AddressType = add.AddressType, Street1 = add.Street1, City = add.City }; return addDTO; } private BAGA.DTO.Payment PaymentEF_toPaymentDTO(Payment pmt) { var pmtDTO = new BAGA.DTO.Payment { Amount = pmt.Amount.Value, PaymentDate = pmt.PaymentDate.Value}; return pmtDTO; }
22.5.5. Initializing the DTO Children The GetCustomer method in Example 22-10 calls InitializeChildren , a separate method. This method is used to help with the child data to ensure that their EntityState is set toUnchanged before they are passed back to the client as part of the Customer graph. The DTOs don't have business logic, and therefore cannot define a default. This generic method, shown in Example 22-12 , uses a constraint to ensure that the type being passed in implements IEntityStateLocal . This way, you don't have to worry about classes being passed in that don't have the
EntityState_Local property.
Example 22-12. Initializing the EntityState of the child data
Sub InitializeChildren(Of T As BAGA.DTO.IEntityStateLocal) _ (ByVal children As List(Of T)) For Each item In children item.EntityState_Local = EntityStateLocal.Unchanged
Next End Sub
public void InitializeChildren(List children) where T: BAGA.DTO.IEntityStateLocal { foreach (var item in children) item.EntityState_Local = BAGA.DTO.EntityStateLocal.Unchanged; }
NOTE If you haven't seen constraints used with generics before, you'll notice that there is a big difference in the VB and C# syntax. VB adds the constraint as the generic is being defined, whereas C# accomplishes it with a where clause at the end of the declaration.
22.5.6. Saving Edits Sent Back from the Client with SaveCustomer SaveCustomer is where the most interesting part of the service happens, although some of its functionality is farmed out to helper methods.
SaveCustomer uses the EntityState_Local values to decide how to treat each entity in the graph, and follows this logical path. The first task, however, is to deal with the incoming DTOs because the code requires entities. You'll need to transform them back into their comparable entity objects. You can create a set of reusable helper methods similar to the methods that you called in GetCustomer that transferred the entity data into DTOs. This time, however, you will be transferring the values of the incoming DTOs back to EntityObjects. When looking at these methods, listed in Example 22-12 , there is an important difference to highlight. When porting from entity to DTO, you grabbed the IDs of EntityReference properties and used those in the DTO. For example, the
Reservation class has a TripID property rather than a TripReference . Coming back, however, you'll need to create an EntityKey for the TripReference to ensure that the foreign key is set correctly in the database. Pay attention to this in the code listing in Example 22-13. Also note that the code is recursively handling the child collections as well.
Example 22-13. Transferring incoming DTOs to entities
Private Function CustDTO_to_EF(ByVal custDTO As BAGA.DTO.Customer) As Customer Dim cust As New Customer With _ {.ContactID = custDTO.CustomerID, _ .TimeStamp = custDTO.TimeStamp, _ .EntityState_Local = custDTO.EntityState_Local, _ .FirstName = custDTO.FirstName, _ .LastName = custDTO.LastName, _ *populate all property*} For Each resDTO In custDTO.Reservations cust.Reservations.Add(ResDTO_to_EF(resDTO)) Next For Each addDTO In custDTO.Addresses cust.Addresses.Add(AddressDTO_toAddressEF(addDTO)) Next Return cust End Function Private Function ResDTO_to_EF(ByVal resDTO As BAGA.DTO.Reservation) As Reservation Dim res As New Reservation With _ {.ReservationID = resDTO.ReservationID, _ .TimeStamp = custDTO.TimeStamp, _ .EntityState_Local = custDTO.EntityState_Local, _ .ReservationDate = resDTO.ReservationDate _ *populate all properties* } 're-create the TripReference from the TripID Dim tripKey = New EntityKey("BAEntities.Trips", "TripID", resDTO.TripID) res.TripReference.EntityKey = tripKey Return res End Function
private Customer CustDTO_to_EF(BAGA.DTO.Customer custDTO) { Customer cust = new Customer {ContactID = custDTO.CustomerID, TimeStamp = custDTO.TimeStamp, FirstName = custDTO.FirstName,
LastName = custDTO.LastName, EntityState_Local = (EntityStateLocal)custDTO.EntityState_Local}; foreach (var resDTO in custDTO.Reservations) cust.Reservations.Add(ResDTO_to_EF(resDTO)); foreach (var addDTO in custDTO.Addresses) cust.Addresses.Add(AddressDTO_toAddressEF(addDTO)); return cust; } private Reservation ResDTO_to_EF(BAGA.DTO.Reservation resDTO) { Reservation res = new Reservation { ReservationID = resDTO.ReservationID, ReservationDate = resDTO.ReservationDate, TimeStamp = resDTO.TimeStamp, EntityState_Local = (EntityStateLocal)resDTO.EntityState_Local }; //re-create the TripReference from the TripID if (!(resDTO.TripID == null)) { if (resDTO.TripID > 0) { var tripKey = new EntityKey("BAEntities.Trips", "TripID", resDTO.TripID); res.TripReference.EntityKey = tripKey; } } return res; }
The beauty of using the DTO in the case of theReservation.Trip property is that you no longer have to worry about an attached trip being treated as a new entity to be added to the database. You may recall the extra code required to deal with that problem in Chapter 14.
Be sure not to neglect any of the entities' properties when you build the methods to transfer the DTOs back to entities. If you leave them empty, SaveChanges will update those fields to null values! In Example 22-13 , some of the properties have been left out only for the sake of brevity in the code listing. If the properties of the DTO class matched those of the entity exactly, you could write a generic method that performs this transformation. However, the fact that the DTO has special properties makes the possibility of creating a generic method much more difficult.
22.5.7. Building the Main Method for Updating the Graph: SaveCustomer
Now it's time to take these incoming entities and prepare for the call to SavingChanges. The UpdateCustomer method follows a series of steps in the pattern. The step numbers listed here are identified in the code listing in Example 22-15:
Step 1 Convert incoming DTOs to Entities.
Step 2 Check Customer's EntityState_Local property. If the Customer is new, just add the entire graph. Even if the
Customer is unchanged, there still may be new, modified, or deleted children to deal with. Therefore, you'll have to process unchanged and modified customers together.
Step 3 Query for a current server version of the graph using a helper method. This is a very important step and it has to do with the timestamp field. When the fresh customer data is retrieved from the database, its TimeStamp may be newer than that of the customer that the consuming application used. If they are different, the concurrency conflict will not be detected on SaveChanges because the original value of the fresh data will be used for the concurrency check. To get around the concurrency checking issue, you'll have to perform a little sleight of hand. You'll notice that in step 3 the graph is queried with MergeOptions set to NoTracking . This will give you an opportunity to override the
TimeStamp s of all of the entities in the graph before the entities are being change-tracked. After the TimeStamp fields are updated, you can attach the whole graph to the context and the TimeStamp in the OriginalValues will be the one that was retrieved in the GetCustomer operation.
NOTE This seems a bit unwieldy, but these are the types of patterns you'll need to use with version 1 of the Entity Framework to succeed at implementing services. The GetDetachedCustomerfromServer method in Example 22-14 shows how to code this step. This will be called from the SaveCustomer method, which will take the returned graph and attach it to the current context. The context is passed into this method so that a query can be executed. This method uses a new query that does not eager load the EntityReferences and calls an overload ofExecuteFirstOrDefault that takes a MergeOption , rather than calling the method used to retrieve a more involved Customer graph.
Example 22-14. Forcing the TimeStamp into the original values
Private Function GetDetachedCustomerfromServer _ (ByVal custEF As Customer, ByVal context As BreakAwayEntities) As Customer
'EntityRefs are not needed for the update Dim custs = context.Contacts.OfType(Of Customer) _ .Include("Reservations").Include("Addresses") _ .Where("it.ContactID=" & custEF.contactID) 'do not change-track at first Dim dal = new DAL.CommandExecutor Dim cust = dal.ExecuteFirstOrDefault(custs, MergeOption.NoTracking)
'update cust timestamp value cust.TimeStamp = custEF.TimeStamp 'update addresses and then reservations timestamp values For Each add In cust.Addresses Dim addressID = add.addressID Dim originalTS = (From a In custEF.Addresses _ Where a.addressID = addressID _ Select a.TimeStamp) _ .FirstOrDefault If Not originalTS Is Nothing Then 'unchanged entities did not come back add.TimeStamp = originalTS End If add.TimeStamp = originalTS Next For Each res In cust.Reservations Dim resID = res.ReservationID Dim originalTS = (From r In custEF.Reservations _ Where r.ReservationID = resID _ Select r.TimeStamp) _ .FirstOrDefault If Not originalTS Is Nothing Then 'unchanged entities did not come back res.TimeStamp = originalTS End If Next Return cust End Function
private Customer GetDetachedCustomerfromServer(Customer custEF, BAEntities context) { //'EntityRefs are not needed for the update
var custs = context.Contacts.OfType() .Include("Reservations") .Include("Addresses") .Where("it.ContactID=" + custEF.ContactID); //do not change track at first var dal = new BAGA.DAL.CommandExecutor(); var cust = dal.ExecuteFirstorDefault(custs, MergeOption.NoTracking); //update cust timestamp value cust.TimeStamp = custEF.TimeStamp; //update reservations timestamp values foreach (var add in cust.Addresses) { var addressID = add.addressID; var originalTS = ( from a in custEF.Addresses where a.addressID == addressID select a.TimeStamp).FirstOrDefault(); if (originalTS != null) //unchanged entities did not come back add.TimeStamp = originalTS; } foreach (var res in cust.Reservations) { var resID = res.ReservationID; var originalTS = ( from r in custEF.Reservations where r.ReservationID == resID select r.TimeStamp).FirstOrDefault(); if (originalTS != null) //unchanged entities did not come back res.TimeStamp = originalTS; } return cust; }
With the GetDetachedCustomerfromServer method in place, it's time to continue on with steps ofSaveCustomer in Example 22-15:
Step 4 Update the Customer, if necessary, using ApplyPropertyChanges.
Step 5 Iterate through both sets of children Addresses ( and Reservations), checking for Added, Modified, or
Deleted entities and performing the necessary actions on those entities. A generic helper method is used so
that it can perform the same tasks for any type of EntityCollection.
Step 6 Save the changes. This last call, in step 6, is actually for theSaveAllChanges wrapper method that you saw in previous chapters. The SaveAllChanges method has been moved to the partial class forBAEntities in the model's assembly.SaveAllChanges calls SaveChanges and handles OptimisticConcurrency and other entity exceptions. Example 22-15 shows the code listing for the entireSaveCustomer method as described earlier.
Example 22-15. The SaveCustomer operation method
Public Function SaveCustomer(ByVal custDTO As BAGA.DTO.Customer) As Boolean _ Implements ICustomerService.SaveCustomer 'STEP 1: Convert DTO to Entities Dim custEF = CustDTO_to_EF(custDTO) Try Using context = New BreakAwayEntities 'STEP 2: If customer is added, just add to the context If cust.EntityState_Local = EntityStateLocal.Added Then context.AddToContacts(custEF) ElseIf custEF.EntityState_Local = EntityState.Modified Or _ custEF.EntityState_Local = EntityState.Unchanged Then 'STEP 3: Get fresh data from server Dim custFromServer = GetDetachedCustomerfromServer(custEF, context) 'attach and begin change tracking context.Attach(custFromServer) If cust.EntityState_Local Then 'STEP 4: Apply property changes for customer If custEF.EntityState_Local = EntityStateLocal.Modified Then context.ApplyPropertyChanges("Contacts", custEF) End If 'STEP 5: Deal with addresses and reservations UpdateChildren(Of Address) (custEF.Addresses.ToList, _ custFromServer.Addresses, _ context, "Addresses")
UpdateChildren(Of Reservation)(custEF.Reservations.ToList, _ custFromServer.Reservations, _ context, "Reservations") ElseIf EntityState.Deleted Then 'we're not deleting customers End If End If 'STEP 6: Save Changes context.SaveAllChanges() Return True End Using Catch ex As Exception 'handle exceptions properly here Return False End Try End Function
public bool SaveCustomer(BAGA.DTO.Customer custDTO) { //STEP 1: Convert DTO to Entities var custEF = CustDTO_to_EF(custDTO); try { using (var context = new BAEntities()) { //STEP 2: If customer is added, just add to the context if (custEF.EntityState_Local == EntityStateLocal.Added) context.AddToContacts(custEF); else if (custEF.EntityState_Local == EntityStateLocal.Modified || custEF.EntityState_Local == EntityStateLocal.Unchanged) { //STEP 3: Get fresh data from server var custFromServer = GetDetachedCustomerfromServer(custEF, context); //attach and begin change tracking context.Attach(custFromServer); if (custEF.EntityState_Local == EntityStateLocal.Modified)
{ //STEP 4: apply property changes for customer if (custEF.EntityState_Local == EntityStateLocal.Modified) context.ApplyPropertyChanges("Contacts", custEF); //STEP 5: Deal with addresses and reservations UpdateChildren(custEF.Addresses.ToList(), custFromServer.Addresses, context, "Addresses"); UpdateChildren(custEF.Reservations.ToList(), custFromServer.Reservations, context, "Reservations"); } else if (custEF.EntityState_Local == EntityStateLocal.Deleted) //we're not deleting customers
//STEP 6: Save Changes; context.SaveAllChanges(); return true; } } } catch (Exception ex) { //handle exceptions properly here return false; } return false; }
22.5.8. UpdateChildren The UpdateChildren method called by SaveCustomer and listed in Example 22-16 is a generic method that you can reuse with any EntityCollection as long as the entities in the collection implement theIEntityStateLocal interface. Because it needs to affect the attached entities with those entities that came from the client, you need to pass in both sets of entities. The generic type (TEntity) is constrained to ensure that onlyEntityObjects that implement IEntityStateLocal are passed in. This way, you are assured that you have access to the necessary properties. The method emulates what you've done with theCustomer. First it finds the children whose state isAdded and adds them to the Customer entity, which is attached to the context. Because the Entity Framework will implicitly remove that item from the one collection before adding it to the other, it is necessary to use the pattern you used earlier in this book that makes it possible to remove items from a collection without impacting the enumeration. Next, the code looks forModified children and uses theApplyPropertyChanges method to update the matching entity that is already in the context. Finally, the method looks forDeleted children. Any children marked for deletion are located in the context using the
ObjectStateManager and are then deleted using theDeleteObject method of ObjectContext. Example 22-16 lists the complete UpdateChildren method.
Example 22-16. The UpdateChildren method
Sub UpdateChildren (Of TEntity As {IEntityStateLocal, EntityObject}) _ (ByVal childCollectionfromClient As List(Of TEntity), _ ByVal childCollectionAttached As EntityCollection(Of TEntity), _ ByVal context As BAEntities, ByVal EntitySetName As String) 'Move Added Addresses to attached customer Dim AddedChildren = childCollectionfromClient _ .Where(Function(r) r.EntityState_Local = EntityStateLocal.Added) For i = AddedChildren.Count - 1 To 0 Step -1 childCollectionAttached.Add(AddedChildren(i)) Next 'ApplyChanges of Modified child to its match in the context For Each child In childCollectionfromClient _ .Where(Function(a) a.EntityState_Local = EntityStateLocal.Modified) context.ApplyPropertyChanges(EntitySetName, child) Next 'Delete children from context if necessary For Each child In childCollectionfromClient. _ Where(Function(a) a.EntityState_Local = EntityStateLocal.Deleted) 'don't assume incoming deleted object is in the cache, use TryGet... Dim ose As Objects.ObjectStateEntry If context.ObjectStateManager. _ TryGetObjectStateEntry(child.EntityKey, ose) Then context.DeleteObject(ose.Entity) End If Next End Sub
public void UpdateChildren (List childCollectionfromClient, EntityCollection childCollectionAttached, BAEntities context, string EntitySetName) where TEntity : EntityObject, IEntityStateLocal
{ try { //Apply Addresses to attached customer var AddedChildren = childCollectionfromClient .Where(r => r.EntityState_Local == EntityStateLocal.Added); for (var i = AddedChildren.Count() - 1; i >= 0; i--) childCollectionAttached.Add(AddedChildren.ToList()[i]); foreach (var child in childCollectionfromClient .Where(a => a.EntityState_Local == EntityStateLocal.Modified)) context.ApplyPropertyChanges(EntitySetName, child); foreach (var child in childCollectionfromClient .Where((a) => a.EntityState_Local == EntityStateLocal.Deleted)) { //don't assume incoming deleted object is in the cache ObjectStateEntry ose = null; if (context.ObjectStateManager .TryGetObjectStateEntry(child.EntityKey, out ose)) context.DeleteObject(ose.Entity); } } catch (Exception ex) { throw; } }
That wraps up the service implementation. Now it's time to see what happens on the client side.
22.6. Implementing the Client That Will Use the WCF Service Although the service has been written with the intent to minimize client effort, clients that consume this service will still need to agree to the contract and rules of this service. The client is not required to reference any of the .NET APIs to use these services. In fact, the provided classes can be converted to perform the same tasks using a different technology if necessary. This is the reason that only the classes are provided, rather than precompiled assemblies. The sample client is written using a console application for the UI and a business layer for the interaction with the service.
22.6.1. Rules for the Client to Follow The client must follow certain rules if the service is to function as expected: The first rule is that entities must have theirEntityState_Local properties set appropriately. We'll write some classes that take onus off the developer and handle that task implicitly. Conceptually, you could provide these classes to consumers of your WCF service. Entities that need to be deleted must be marked for deletion by setting the EntityState_Local property to
Deleted . Otherwise, if they are truly deleted, they will not be returned to the service and the service won't know they need to be deleted. IDs and timestamps must not be modified. If the ID is modified on an entity that needs to be updated or deleted, it won't be possible for the matching data to be found in the data store. Because the TimeStamp fields are used for concurrency checks, they are also part of the filter used when attempting to update or delete records. Every editable object must be initialized when it is first retrieved from the web service. The client-side initialization provided by the supplemental classes enables the implicit updates to the EntityState_Local
fields.
22.6.2. Building a Business Layer Between the UI and the Services In this client application, you'll create a layer that interacts with the service. It is a separate project that has the reference to the service and contains the code to interact with the service. In Visual Studio, this is the project to which you would add the service reference.
NOTE When creating the service reference to the new WCF service, remember to configure the reference to use System.Collections.Generic.List as the default collection type, as you did in the earlier WCF example. The classes in this project will have access to the DTO classes that the services return. Additionally, the business layer will contain some business logic that will ensure that the consuming application follows the rules the service has laid out. The logic is embedded in classes that extend the DTO classes to provide some behavior. These business layer methods can be called by the UI layer, which won't have to know anything about the services and won't have as many rules to be concerned with.
NOTE
When you add the service reference to the business layer project, Visual Studio will apply the necessary service client configuration to the app.config file. When you reference this from the UI layer, the UI will need that configuration information. You can locate the section of the business layer's.config file and copy it into the .config file of the UI layer.
22.6.3. What's in These Business Classes? The business classes provide a small amount of business logic for the entities and help to implement some of the client-side rules. Providing these types of classes to consumers of your service will be a benefit to them and will help to ensure that they can easily follow the service contract. Because the enums added into the model assembly were marked with theDataContract and EnumMembers attributes, they are available to the developer who is consuming the services. .NET clients will have the same IntelliSense help with the enums as you would if you were referencing the model's assembly. Each editable class (Customer, Address, and Reservation ) has a partial class. And each partial class provides business logic for the classes using three methods, as discussed in the following three subsections.
22.6.3.1. The class constructor The first method is the class constructor, which will be hit when the client explicitly instantiates a new object—for example, if the client application creates a new Address. The constructor sets theEntityState_Local property to Added and it inserts a default TimeStamp field.
NOTE The binary TimeStamp field is being created here to comply with a limitation of serialization. The TimeStamp is non-nullable and is a reference type. This combination of attributes means it won't get a default value the way in which, for example, an integer would automatically be 0. If the new object's TimeStamp property is null, it will not be possible to serialize the object when it's time to send it back to the service.
22.6.3.2. The property changed event Anytime the property of an entity changes, this event will change theEntityState_Local property to Modified . It will first check to make sure the entity is not already marked as Added or Deleted.
22.6.3.3. The Initialize method Objects coming down from the service will not automatically be wired up to the classes' event handlers. The Initialize method manually ties the object to the PropertyChanged event so that when changes are made to preexisting objects, their EntityState_Local property will be changed. As an example of implementing this business logic, Example 22-17 lists the Reservation class for the client-side business layer.
Example 22-17. The client-side Reservation class
Imports System.ComponentModel Namespace BreakAwayServices 'TODO: modify this namespace to match the namespace ' you have given the service Public Class Reservation Public Sub Initialize() AddHandler Me.PropertyChanged, AddressOf entPropertyChanged End Sub Public Sub New() Me.EntityState_Local = EntityStateLocal.Added 'default timestamp value because it cannot be null Me.TimeStamp = System.Text.Encoding.Default.GetBytes("0x123") End Sub Private Sub entPropertyChanged(ByVal sender As Object, _ ByVal e As PropertyChangedEventArgs) Handles Me.PropertyChanged If EntityState_Local = 0 Or _ EntityState_Local = EntityStateLocal.Unchanged Then Me.EntityState_Local = EntityStateLocal.Modified End If End Sub End Class End Namespace
using System.ComponentModel; namespace BreakAwayServices { //TODO: modify this namespace to match the namespace // you have given the service public class Reservation { public void initialize() { this.PropertyChanged += entPropertyChanged; }
public Reservation() { this.EntityState_Local = EntityStateLocal.Added; //default timestamp value because it cannot be null this.TimeStamp = System.Text.Encoding.Default.GetBytes("0x123"); }
private void entPropertyChanged(object sender, PropertyChangedEventArgs e) { if (EntityState_Local == 0 || EntityState_Local == EntityStateLocal.Unchanged) this.EntityState_Local = EntityStateLocal.Modified; } } }
The Address partial class can be implemented using the same code as theReservation class. As the parent node of the graph, the Customer class has some additional responsibilities. When aCustomer is initialized, its reservations and addresses also need to be initialized. Rather than forcing the client-side developer to manually code all of the initialization, the Customer class' Initialize method initializes the children for you, thereby initializing the customer graph, as shown in Example 22-18.
Example 22-18. The Initialize method of the Customer class
Public Sub Initialize() AddHandler Me.PropertyChanged, AddressOf entPropertyChanged For Each add In Me.Addresses add.initialize() Next For Each res In Me.Reservations res.initialize() Next End Sub
public void Initialize() { this.PropertyChanged += entPropertyChanged; foreach (var add in this.Addresses) add.Initialize(); foreach (var res in this.Reservations) res.Initialize(); }
Additionally, the Customer class can help to ensure that children are marked for deletion rather than removed from the collection. You can use the Customer.RemoveChild method instead of the standardCollection.Remove method, and you can use Customer.RemoveAllAddresses and Customer.RemoveAllReservations instead of
Collection.RemoveAll , as shown in Example 22-19.
Example 22-19. The Customer.RemoveChild method
Public Sub RemoveChild(ByRef childEntity As Object) If TypeOf childEntity Is Address Or _ TypeOf childEntity Is Reservation Then childEntity.EntityState_Local = EntityStateLocal.Deleted
End If End Sub Public Sub RemoveAllAddresses() For Each add In Me.Addresses add.EntityState_Local = EntityStateLocal.Deleted Next End Sub Public Sub RemoveAllReservations() For Each res In Me.Reservations res.EntityState_Local = EntityStateLocal.Deleted Next End Sub
public void RemoveChild(ref object childEntity) { if (childEntity is Address | childEntity is Reservation) childEntity.EntityState_Local = EntityStateLocal.Deleted; } public void RemoveAllAddresses() { foreach (var add in this.Addresses) add.EntityState_Local = EntityStateLocal.Deleted; } public void RemoveAllReservations() { foreach (var res in this.Reservations) res.EntityState_Local = EntityStateLocal.Deleted; }
22.6.4. Calling the Service Operations from the Business Layer This business layer contains another class with four methods to call the four service operations: GetCustomerList, GetTripList , GetCustomer, and SaveCustomer . Each method calls the related operation from the service.
GetCustomerList , shown in the following code, is straightforward. It instantiates a reference to the service and calls the operation; then the reference to the service is disposed at the end of the using clause:
Public Function GetCustomerList() As List(Of ShortCustomer)
Using svc = New CustomerServiceClient Return svc.GetCustomerList() End Using End Function
public List GetCustomerList() { using (var svc = new CustomerServiceClient()) { return svc.GetCustomerList(); } }
GetTripList is equally straightforward, as shown here:
Public Function GetTripList() As List(Of Trip) Using svc = New CustomerServiceClient Return svc.GetTrips() End Using End Function
public List GetTripList() { using (var svc = new CustomerServiceClient()) { return svc.GetTrips(); } }
The GetCustomer method, shown next, callsCustomer.Initialize after the data has been retrieved for the service. This, in turn, ensures that the customer, addresses, and reservations are all initialized:
Public Function GetCustomer(ByVal ContactID As Integer) As Customer Using svc = New CustomerServiceClient Dim cust = svc.GetCustomer(ContactID) cust.Initialize() Return cust End Using End Function
public Customer GetCustomer(int ContactID) { using (var svc = new CustomerServiceClient()) { var cust = svc.GetCustomer(ContactID); cust.Initialize(); return cust; } }
For the sake of efficiency, SaveCustomer removes Address and Reservation objects that do not need updating prior to sending the graph to the service, as shown in Example 22-20 . This will reduce the size of the message sent back to the service.
Example 22-20. The SaveCustomer method in the client-side business layer
Public Sub SaveCustomer(ByVal cust As Customer) 'completely remove unchanged children 'removing items from a collection requires a particular pattern 'don't remove reference objects (e.g., the Trip attached to Reservation) For i = cust.Addresses.Count - 1 To 0 Step -1 Dim add = cust.Addresses(i) If add.EntityState_Local = EntityStateLocal.Unchanged Then
cust.Addresses.Remove(add) End If Next For i = cust.Reservations.Count - 1 To 0 Step -1 Dim res = cust.Reservations(i) If res.EntityState_Local = EntityStateLocal.Unchanged Then cust.Reservations.Remove(res) End If Next Using svc = New CustomerServiceClient svc.SaveCustomer(cust) End Using End Sub
public void SaveCustomer(Customer cust) { //completely remove unchanged children //removing items from a collection requires a particular pattern //don't remove reference objects (e.g., the Trip attached to Reservation) for (var i = cust.Addresses.Count - 1; i >= 0; i--) { var add = cust.Addresses(i); if (add.EntityState_Local == EntityStateLocal.Unchanged) cust.Addresses.Remove(add); } for (var i = cust.Reservations.Count - 1; i >= 0; i--) { var res = cust.Reservations(i); if (res.EntityState_Local == EntityStateLocal.Unchanged) cust.Reservations.Remove(res); } using (var svc = new CustomerServiceClient()) { svc.SaveCustomer(cust); } }
NOTE
You may recall in Chapter 14 modifying the client configuration's MaxReceivedMessageSize property. This is an important step, as the graphs can quickly grow and can exceed the default step. Check Figure 14-12 for a reminder of how to change this setting using the tool. Or just modify it manually in the app.config file.
22.7. Testing It All with a Simple Console Application Now you need some code to make this run and to see it in action. Rather than create a full interface, you can just write a little bit of code in a console application to test the basic functionality. The code in Example 22-21 tests the GetCustomerList function to retrieve a list of customer names and IDs. It also calls
GetTripList so that it will be possible to add a new reservation in this example. Next it selects a random customer from the list of customers and uses the ID to call GetCustomer. Once the customer has been retrieved, you can make a change to aCustomer property, change a property in one of the addresses, delete a reservation, and create a new reservation.
Example 22-21. A console application to test the service
Sub Main() Dim bal As New BusinessClass Dim custlist = bal.GetCustomerList Dim triplist = bal.GetTripList 'select a random customer from the list Dim rnd = New Random Dim randomPositioninList = rnd.Next(1, custlist.Count) Dim ID = custlist(randomPositioninList).ContactID Dim cust = bal.GetCustomer(ID) 'modify the customer cust.Notes = cust.Notes.Trim & " Updated on " & Now.ToShortDateString If cust.Reservations.Count > 0 Then 'Remove the last reservation from customer if there are no payments 'RULE: Call removechild, not remove Dim reslast = cust.Reservations(cust.Reservations.Count - 1) If reslast.Payments.Count = 0 Then cust.RemoveChild(cust.Reservations(cust.Reservations.Count - 1)) End If End If 'Create a new reservation Dim res = New WCFClientBusinessLayer.BreakAwayServices.Reservation res.ReservationDate = Now 'pick a random trip and assign it to the reservation randomPositioninList = rnd.Next(1, triplist.Count) res.Trip = triplist(randomPositioninList) 'add the new reservation to the customer cust.Reservations.Add(res)
'modify the customer's first address If cust.Addresses.Count > 0 Then Dim add = cust.Addresses(0) add.Street2 = "PO Box 75" End If 'call the SaveCustomer method in the business layer bal.SaveCustomer(cust) End Sub
public void Main() { BusinessClass bal = new BusinessClass(); var custlist = bal.GetCustomerList; var triplist = bal.GetTripList; //select a random customer from the list var rnd = new Random(); var randomPositioninList = rnd.Next(1, custlist.Count); var ID = custlist(randomPositioninList).ContactID; var cust = bal.GetCustomer(ID); //modify the customer cust.Notes = cust.Notes.Trim() + " Updated on " + Now.ToShortDateString(); if (cust.Reservations.Count()>0) { //Remove the last reservation from customer if there are no payments //RULE: Call removechild, not remove var reslast = cust.Reservations(cust.Reservations.Count - 1); if (reslast.Payments.Count == 0) cust.RemoveChild(cust.Reservations(cust.Reservations.Count - 1)); } //Create a new reservation var res = new WCFClientBusinessLayer.BreakAwayServices.Reservation(); res.ReservationDate = System.DateTime.Now; //pick a random trip and assign it to the reservation randomPositioninList = rnd.Next(1, triplist.Count); res.Trip = triplist(randomPositioninList);
//add the new reservation to the customer cust.Reservations.Add(res); if (cust.Addresses.Count()>0) { //modify the customer's first address var add = cust.Addresses(0); add.Street2 = "PO Box 75"; } //call the SaveCustomer method in the business layer bal.SaveCustomer(cust); }
NOTE Even though the code checks to make sure there are no dependent payments before deleting a reservation, the database will still throw an error if more payments have been added in the meantime. By running this module, you'll be able to see all of the features of the SaveCustomer WCF operation in action: updating, adding, and deleting entities. Set breakpoints in the service to see the various methods doing their jobs, and watch SQL Profiler to see the updates, inserts, and deletes hit the database.
22.8. Summary In this chapter, you leveraged many of the things you learned throughout the book to build a more sophisticated Entity Framework WCF service that doesn't rely on assumptions to determine the state of data. Of all of the possible ways to approach this problem, the most effective solution requires the consuming application to follow some requirements, yet it still does not need to have references to the Entity Framework APIs, or even to your model's assembly. Working in distributed applications with version 1 of the Entity Framework is challenging, but it is possible once you have a grasp of the many tools at your disposal and you understand what behavior to expect from the entities. Don't forget to consider ADO.NET Data Services if you don't need as much granular control over your services. It provides a simple and standardized way for consumers to access your data as a resource through HTTP rather than specific operations. It's also very easy to set up ADO.NET Data Services to expose your data through your model. If you are building enterprise applications that need to leverage services, this chapter provided you with a pattern that you can use and expand upon.
Chapter 23. The Entity Framework, Today and Tomorrow Microsoft's data strategy is solidly focused on conceptual models. The Entity Framework is the tool for .NET developers to build applications that leverage these models. Yet version 1 is only the first step toward Microsoft's larger vision for a very robust set of capabilities. In this book, you learned how to use the features in version 1 to build applications, and you gained a good understanding of the plumbing of the Entity Framework so that you can overcome some of the limitations of this version. Microsoft has listened very closely to the developer community and has started to show publicly the features it is implementing in the next version, which will be part of the next Visual Studio release. Microsoft continues to work with the community and encourages our feedback in helping to shape this next version. In the meantime, the company has been putting together samples of how to achieve some of the capabilities that are targeted for the next version, as well as code you can use to extend the Entity Framework's current features. In this chapter, we'll take a brief look at a few lingering questions regarding the Entity Framework, the version 1 toolkits that Microsoft has been sharing, and some of what's coming in the next version, which will be part of Visual Studio 2010 and .NET 4.0.
23.1. What About Building Reports with the Entity Framework? We did not discuss reporting in this book because there is not much to share. In Windows Forms, the embedded Microsoft Report Creator and the Crystal Reports Report Builder can build reports from DataSource objects, including those that you create from entities. However, the bridge between the entities and the reporting tools has a hole that prevents you from building reports from entity graphs, thereby removing most of the usefulness of employing the report builders. As of this writing, none of the third-party vendors had created a reporting tool that will work with the Entity Data Model (EDM) or EntityObject s and graphs. However, for the next version of SQL Server, Reporting Services will be embracing the EDM concept and you will be able to build reports against the conceptual model. Until then, you'll have to create reports directly from the database as you would do for most other Object Relational Mapping tools. And, of course, keep an eye on the third-party vendors that could very well release something in the meantime.
23.1.1. Major Differences Between the Entity Framework and LINQ to SQL LINQ to SQL was released with Visual Studio 2008, allowing more developers to adopt and implement it into production systems quickly. The Entity Framework was not released until 10 months later as part of Visual Studio 2008 Service Pack 1. LINQ to SQL is a much lighter-weight API that is perfect for quickly implementing a data layer. It does not approach the level of granularity that the Entity Framework allows you in order to control how your objects are handled from query to materialization and back to the database. LINQ to SQL also does not provide a way to stream data, as you can with EntityClient. With the Entity Framework, you have the option of querying with LINQ to Entities or Entity SQL. Entity SQL is a great choice when you need to build queries dynamically . Although LINQ to SQL doesn't have another mechanism for building queries, there is a helper library called the LINQ Dynamic Query Library that can be used with any LINQ provider, including LINQ to SQL and even LINQ to Entities. It's not as flexible as Entity SQL, but it certainly provides a lot of capability when using LINQ methods. You can read all about this library on Scott Guthrie's blog at http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx . Otherwise, you are bound to the strongly typed query language and cannot build dynamic queries. LINQ to SQL works only with SQL Server, whereas the Entity Framework works against any database for which there is a provider. However, many LINQ providers are available and some of those work against databases. LINQ to SQL's model is a set of classes that tie directly to the database tables and columns with class attributes, which is very different from the conceptual model that the Entity Framework uses along with the schema files that map back to the database. LINQ to SQL has the same issues regarding working with data across tiers as the Entity Framework does, in that change-tracking data disappears. Developers trying to solve this problem for LINQ to SQL have come up with solutions similar to those you have seen in this book for the Entity Framework.
23.1.1.1. The future of LINQ to SQL Both the Entity Framework and LINQ to SQL are part of ADO.NET and are controlled by the same development team at Microsoft. As I mentioned at the beginning of this book, Microsoft has recently made it clear that because it will be investing in the Entity Framework as part of its data platform strategy, LINQ to SQL will be maintained and tweaked but will not benefit from the same level of evolution and innovation as the Entity Framework. You can read more on the ADO.NET team blog, at http://blogs.msdn.com/adonet/ , for posts made from late October 2008 onward.
23.1.1.2. Migrating LINQ to SQL to the Entity Framework Beginning in mid-October 2008, the ADO.NET team began providing guidance on migrating LINQ to SQL projects to the Entity Framework in various posts on their blog (http://blogs.msdn.com/adonet/ ). Microsoft has said that it will provide this guidance, but to date it is not yet available. Formal guidance as well as tools will be provided to help you migrate if you wish to.
23.2. Extensions, Samples, and Solutions from Microsoft Although the Entity Framework team is doggedly working on the next version, in the meantime they have shared a lot of additional tools and sample code on the MSDN Code Gallery website. The URL for that jumping-off page for all Entity Framework team projects is http://code.msdn.microsoft.com/adonetefx/.
23.2.1. Extensions and APIs As you have seen throughout this book, extra coding is required in a lot of places to achieve functionality that would be nice to have built-in to the framework. A number of Entity Framework team members have worked out some of these problems for us, and have posted their solutions on Code Gallery. Some of these examples are ideas that the team is exploring for planned enhancements for version 2. In the next several subsections, I'll highlight some of the tools that are available as of this writing.
23.2.1.1. EFExtensions The EFExtensions provide lots of tricks, including the ability to call stored procedures that return multiple resultsets (as long as those results match up with existing entities). They also provide the ability to create types from DataReader , and they include a sample application that demonstrates these and other features built into the extensions.
23.2.1.2. POCO Adapter for version 1 POCO (Plain Old CLR Objects) is coming in the Entity Framework version 2 to allow for programming and unit testing in a way that doesn't require tight coupling to the ObjectContext. The POCO Adapter allows you to achieve this with version 1.
23.2.1.3. Entity Framework Lazy Loading If you do not like the fact that the Entity Framework forces you to callLoad to explicitly load related data for an entity, you might be interested in implementing lazy loading. Entity Framework Lazy Loading will provide implicit lazy loading so that if you already have a customer and you want the customer's orders, referencing Customer.Orders will cause the Entity Framework to load the orders behind the scenes. Entity Framework Lazy Loading also provides a number of additional utilities that can help you get over a hump or can show you the code for solving particular types of problems.
23.2.1.4. eSqlBlast eSqlBlast can help you test Entity SQL expressions outside Visual Studio. It was a valuable tool for helping me with some of the trickier Entity SQL expressions in this book.
23.2.2. Samples The same Code Gallery project also provides a number of samples.
23.2.2.1. Entity Framework Query Samples Entity Framework Query Samples is a Visual Studio project that contains a slew of sample queries for LINQ to Entities and Entity SQL.
23.2.2.2. Sample EDMX Code Generator If you are interested in customizing how the classes are generated from your model, the sample EDMX Code Generator will teach you how to write your own code generator.
23.2.2.3. Entity Framework Sample Provider For those who are interested in learning how to write their own providers, there is an example here as well as an Oracle sample.
23.2.3. Learning Tools There is also a group of learning tools that includes the Entity Mapping Helper, which is a great way to visualize the different mapping capabilities of the EDM and which shows the XML implementation of the related schema files for the selected mapping.
23.3. Entity Framework v.Next The Entity Framework version 1 is a very robust tool, but as you have seen throughout this book, many features require that you dig deeper than you may have wanted to in order to accomplish certain tasks. The Designer has a lot of features, but it does not support many of the mappings that you can achieve in the EDM, which requires you to handcode directly in the XML and, in certain cases (such as when using complex types), completely abandon the Designer. Version 1 was "wrapped up" months before it shipped, and the Entity Framework team was already hard at work improving the Entity Framework for its next release. At Microsoft PDC2008, Program Manager Tim Mallalieu presented "Entity Framework," where he demonstrated a number of the new features that are being implemented in the next version. You can view all of the videos from the conference at http://www.microsoftpdc.com/. Among these features are the following:
A more robust Designer Two features that Mallalieu demonstrated are Designer support for complex types and "model first" design capabilities. You can begin by designing a model and then create a database from it. Combined with the Update Model from the Database tool, you can then do iterative development of both the model and the database.
Support for POCO to enable Agile development and unit testing that is not dependent on ObjectContext What's most interesting about this demo is that it solidifies the idea that the EDM is a concept. With the POCO classes, there is no need for the XML files. The model will be dynamically created in memory based on the classes as they are instantiated.
Support for model-driven development If you would rather design your model and create your database from there, you'll be able to do this in the next version. The team established an Entity Framework Advisory Council, which includes Domain Development gurus such as Eric Evans and Jimmy Nillsson to help them with this area of expansion.
Support for computed properties in the model so that you don't have to rely on partial classes and you can include those properties in queries This new feature is similar toDefiningQueries , which we discussed inChapter 13 . It is called a
DefiningExpression , and is defined in the model and can be linked to a class property using attributes. Other important topics that were discussed but not demonstrated include better support for stored procedures and better support for change tracking across tiers. Much more is happening with the next version of the Entity Framework, and these changes will become more visible as the team solidifies some of the code and includes it in upcoming betas of Visual Studio 2010. By the time this book is in print a lot more information about the next version of the Entity Framework will likely be publicly available.
EDM, Oslo, and the Importance of Models at Microsoft In October 2008 at Microsoft's Professional Developer Conference (PDC), Microsoft's new technology—codenamed "Oslo"—had its "coming out" party with a number of sessions. "Oslo" is a platform that solidifies Microsoft's commitment to model-driven development. In the keynote session, Sr. VP Bob Muglia introduced "Oslo" as a platform that "is going to have some very important ramifications on the way code is written in the long term." Along with ASP.NET MVC, Entity Framework's EDM is one of the early examples of model-driven development from Microsoft. MSDN's Oslo Frequently Asked Questions document (http://msdn.microsoft.com/en-us/library/dd129873.aspx) states that: the Entity Data Model (EDM) and Entity Framework (EF) are important technologies for Microsoft. "Oslo" fully embraces EF/EDM as a primary mechanism for "Oslo"-based runtimes to access the Repository. We are working closely with the Entity Framework team to enable this scenario. In addition, as we continue the development of "Oslo," we expect deeper alignment with EF/EDM. In particular, we expect that to have "Oslo" textual and visual DSLs for EDM and for these to play an important role in how application developers build model-driven applications.
23.3.1. The Transparent Design Process for the Next Version The Entity Framework team wants to get more input regarding the design of the .NET 4.0 version of the Entity Framework. Toward that end, they have created the Entity Framework Design blog, where they blog the ideas they are working on and request feedback from the community. You can find this blog at http://blogs.msdn.com/efdesign/.
23.4. Blogs, Forums, and Other Resources If you are looking for additional information and discussion on the Entity Framework, the Resources page of the book's website has many links to resources such as the MSDN Forum for the Entity Framework, screencasts and blogs about the Entity Framework, and a growing list of ADO.NET providers that support the Entity Framework.
Appendix A. Entity Framework Assemblies and Namespaces This appendix will provide you with a high-level overview of the assemblies and namespaces of the Entity Framework. You will learn about the files that are used for the Entity Framework and the namespaces of the Entity Framework and their purpose.
A.1. Unpacking the Entity Framework Files You'll find the physical DLL files that contain the Entity Framework APIs in the following two directories: :\Program Files\Reference Assemblies\Microsoft\Framework\v3.5, which contains: System.Data.Entity.Design.dll This file contains functionality related to the design tools, such as the Designer, the mapping details, and the model viewer. System.Data.Entity.dll This file is the root of the Entity Framework. It contains all of the namespaces and classes for programming against the Entity Data Model (EDM). :\Windows\Microsoft.NET\Framework\v3.5, which contains: Microsoft.Data.Entity.Build.Tasks.dll This file contains the functions for compilation tasks, including building the EDM schema files.
A.2. Exploring the Namespaces The Entity Framework lives within theSystem.Data namespace of the .NET Framework. New functionality (classes, properties, and methods) has been added to existing namespaces in the System.Data hierarchy, along with a number of new namespaces that begin with the term System.Data.Entity. The System.Data.Entity.dll assembly provides all of the namespaces, as shown in Figure A-1.
Figure A-1. Namespaces provided in System.Data.Entity.dll
A.2.1. Existing Namespaces That Have New Classes and Functionality A number of existing namespaces have new classes and functionality added to them to support the Entity Framework:
System.Data
System.Data is the namespace in the .NET Framework that provides all of .NET's data access functionality. Some functionality is contained directly in System.Data , and much more exists in its subnamespaces. The Entity Framework adds Exception classes directly to this namespace, as well asEntityKey, which provides a durable reference to an entity.
System.Data.Common
System.Data.Common provides base classes that are common to all of the data providers written for .NET. For example, DbDataReader is the base ofSqlDataReader, OleDbDataReader, OracleDataReader , and more. The Entity Framework adds a few high-level DbProvider members into this namespace along with DataRecordInfo to expose query results in the form of aDbDataRecord and EntityRecordInfo , which provides access to the metadata of an entity.
System.Data.SqlClient The provider information that allows the ADO.NET Entity Framework to communicate with Microsoft SQL Server is added into the System.Data.SqlClient class through additional classes added into the
System.Data.Entity assembly.
System.Linq.Expressions
System.Linq.Expressions adds LINQ to Entities query functionality to theSystem.Linq.Expressions namespace.
A.2.2. New Namespaces All of the functionality that you will use directly or indirectly when working in the Entity Framework lives in the following namespaces:
System.Data.Common.CommandTrees
System.Data.Common.CommandTrees adds logic for building Entity Framework command trees from LINQ to Entities and Entity SQL expressions. Each provider that is written to work with the Entity Framework will have the ability to turn these command trees into store queries.
System.Data.Entity
System.Data.Entity does not contain any classes or methods; it is the base for a hierarchy of other namespaces—namely, System.Data.Entity.Design and System.Data.Entity.Design.ASP.NET (discussed shortly).
System.Data.EntityClient
System.Data.EntityClient is a standard ADO.NET managed provider supporting access to the data described in the EDM. This namespace is comparable to System.Data.SqlClient or System.Data.OracleClient and provides classes such asEntityConnection, EntityCommand, and EntityDataReader .
System.Data.Mapping
System.Data.Mapping provides logic for performing view generation from query expressions.
System.Data.Metadata.Edm
System.Data.Metadata.Edm contains the types that are represented in the conceptual, mapping, and store schemas that define and support the EDM. Using these types directly, it is possible to programmatically work with the metadata of the model.
System.Data.Objects
System.Data.Objects is the most important namespace in the Entity Framework. It provides the classes for querying, change tracking, relationship management, and updating the data store through the EDM. The functionality provided by System.Data.Object and its child namespace,DataClasses, is referred to as "Object Services."
System.Data.Objects.DataClasses
System.Data.Objects.DataClasses contains the classes and interfaces that allow types described in the EDM to be instantiated as objects. With these classes you can programmatically interact with the data that is provided as a result of querying the EDM.
System.Linq.Expressions
System.Linq.Expressions adds the necessary expressions for performing LINQ to Entities queries.
System.Data.Entity.Design
System.Data.Entity.Design provides functionality for generating an EDM as well as performing the code
generation to create classes from the EDM.
System.Data.Entity.Design.ASP.NET
System.Data.Entity.Design.ASP.NET provides the build providers used in the build environment for ASP.NET.
System.Data.Query.InternalTrees
System.Data.Query.InternalTrees provides the tools for converting query expressions to be converted to command trees that are executed against the Entity Framework. This is very low-level and all of the members in this namespace are sealed.
System.Data.Query.PlanCompiler
System.Data.Query.PlanCompiler is another namespace filled with low-level functionality for processing queries.
System.Data.QueryResultAssembly
System.Data.QueryResultAssembly , the third subnamespace in System.Data.Query , is also low-level and sealed. When data is returned from the data store, it needs to be transformed into objects. This namespace contains the tools that are used internally to perform this transformation.
Appendix. Colophon The animal on the cover ofProgramming Entity Framework is a Seychelles blue pigeon (Alectroenas pulcherrima ). Also known as a Seychelles blue fruit dove, this medium-size pigeon is approximately 10 inches long and inhabits the woodlands of the Seychelles archipelago. Its wings, underbody, and tail are dark blue, while its head and breast are a silvery-gray or a pale blue. It has a characteristic patch of crimson skin that runs from its forehead to its crown. Its diet consists mostly of fruit. The cover image is fromRiverside Natural History vol. IV. The cover font is Adobe ITC Garamond. The text font is Linotype Birka; the heading font is Adobe Myriad Condensed; and the code font is LucasFont's TheSansMonoCondensed.
Index [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]
[A] abstract types creating abstract entity types TPT inheritance with AcceptAllChanges method, ObjectContext controlling in transactions Access database Action property, CollectionChangeEventArgs Add method EntityCollection class ObjectContext or IRelatedEnd, Attach method versus AddChildtoParentObject method ADO.NET classic queries, performance versus Entity Framework ADO.NET Data Services aggregates chaining in grouping queries with EntityCollections in EntitySQL in LINQ methods and query builder methods AllowImplicitConversion enum anonymous types accessing properties of implicit and explicit, creation of projections of EntityCollection entities in web services ANY method ANYELEMENT operator application cache ApplyPropertyChanges calling in WCF service application ApplyPropertyChanges method calling in ASMX Web Service ObjectContext class ASMX Web Services, using Entity Framework building client application designing the form ignorance of client of Entity Framework methods to interact with web service New and Save buttons for form setting up project and proxy building the service change tracking across tiers Customers entity with EntityObject schema GetCustomer method to return single entity GetCustomers method insert and update web methods interoperability of service
testing GetCustomers operation ASP.NET EntityDataSource control [See EntityDataSource controls] n-tier applications using Entity Framework ASP.NET state solutions designing object provider classes for use with ObjectDataSource EntityObjects in read-only web pages how ObjectContext fits into web page life cycle not using object graphs in business layer ObjectDataSource control options for updating entities wiring provider classes to ObjectDataSource web application with hierarchical data assemblies compiled project, schema files embedded in compiling model into creating an entity from referenced assembly creating MetadataWorkspace from EDM files embedded in assembly file Entity Framework for custom classes getting a reference to Assembly.CreateInstance method AssociationChanged event 2nd event arguments associations 2nd [See also relationships] AssociationTypes attributes defining for EDM creating for entity created by DefiningQuery creation by EDM Wizard in CSDL file EdmRelationshipNavigationPropertyAttribute impact on native queries mapping in response to new inheritance mapping, many-to-many relationships navigation properties and querying across with referential constraint in CSDL self-referencing in SSDL AssociationSetMapping element AssociationSets 2nd naming of SSDL AssociationSet Attach method ObjectContext and IRelatedEnd ObjectContext class using CreateSourceQuery to create queries for versus Add method AttachTo method, ObjectContext attributes metadata partial class implementation, rules for [top]
[B] binary serialization BindingSource control 2nd AddingNew event adding flag coordination of user actions with entities CurrentChanged event DataSource property editing of data in forms EndEdit method example form with query results bound to, entity updates and blogs and forums for Entity Framework BoundField controls created when binding control to ObjectDataSource bulk processing of commands business layer [See also n-tier client-side applications] ASP.NET n-tier application no use of object graphs in n-tier client-side applications 2nd WCF client application calling service operations from contents of business classes business logic 2nd [top]
[C] C.html# anonymous types in version 3.0 auto-implemented properties direct type comparison example queries grouping combining aggregates in C.html# LINQ INTO clause, specifying group name implementing PropertyChanged and PropertyChanging events implicitly typed local variables lock keyword naming anonymous types ObjectStateEntry visualizer partial method implementation projections cached queries caching application cache query plan caching for Entity SQL canonical functions (Entity SQL) cascading deletes 2nd in the database
in the EDM recommendation for use in both EDM and database, or neither chaining methods change tracking 2nd IEntityWithChangeTracker interface problems in distributed applications problems in multi-tiered applications remembering original values and tracking changes Changing/Changed methods class level event parameters implementing order of events property level implementing checksum in data store, concurrency checks on class constructor, WCF service client application classes automatically created from the EDM class diagram for entities (example) custom assemblies and EDM files conflicts with Designer-generated classes implementing IPOCO interfaces mapping to Entity Data Model querying with support by Object Services entity 2nd [See also entity objects] in LINQ to Entities queries example marking as DataContract partial classes, Entity Framework and client applications [See also n-tier client-side applications] ASMX Web Service client designing form ignorance of Entity Framework methods to interact with service reference to service and proxies client for WCF service 2nd adding and deleting reservations adding functionality to view reservations business layer between UI and services calling service operations from business layer content of business classes editng configuration finishing the form no EntityKey or EntityReference in client rules for client running application and saving changes Entity Framework and using Entity Framework, basic architecture for ClientWins ObjectContext.Refresh method refreshing set of entities RefreshMode value Code Gallery project samples provided by code generation
custom default Collection class, IQueryable and IEnumerable versus CollectionChangeAction enumeration CollectionChangeEventArgs, AssociationChanged event collections, ObservableCollection object ComboBox controls binding to ListBox and to data in WPF SelectionChanged event command execution exceptions, SaveChanges method command trees conversion by entity client to store commands conversion of Entity SQL and query builder methods to query processing from LINQ to Entities query to CommandExecutor class, building (example) compilation of queries EDM Generator for precompiled views exceptions precompiling frequently used query reducing cost of compiled queries CompiledQuery class, Compile method complex types ComplexObjects rather than EntityObjects data binding using EntityDataSource without EntityDataSource defining EDM designer and mapping entities with properties of complex type, getting from ObjectStateEntry querying, creating, and saving entities with removing from the EDM replacing properties with Windows Forms DataSource and ComplexObject composable queries composition versus inheritance Conceptual Schema Definition Language [See CSDL] concurrency 2nd [See also optimistic concurrency] checking using rowversion field ConcurrencyMode attribute exceptions when multiple parties edit data concurrently handling conflicts in handling exceptions for your transactions handling of low-level exceptions handling multiple conflicts handling without user intervention optimistic concurrency optimistic concurrency options in Entity Framework OptimisticConcurrencyException ConcurrencyMode property 2nd derived types how Entity Framework uses it conditional mapping creating for an entity querying, creating, and saving conditionally mapped entities
removing ConfigurationManager class connection pooling Connection property, ObjectContext connection strings ConnectionString to find EDM files in file path EntityConnection and EntityConnectionString exceptions handling exceptions in specifying EntityConnection string to use for context connections disposal of store connections EntityConnection and database connections opening and closing Entity and database connections forcing explicit connection manually many calls on single connection multiple connections protecting data from connection piggybacks constraints association with a constraint in CSDL checked by Entity Framework in calls to SaveChanges checks deferred to database database constraints enforcing referential integrity, cascading deletes and deletions from grids with referential constraints involved exceptions thrown by broken constraints in database model and mapping constraints broken, UpdateException referential integrity and ReferentialConstraint element in the SSDL context 2nd [See also ObjectContext class] EntityDataSource context events using your own for EntityDataSource ContextCreated event, EntityDataSource ContextCreating event, EntityDataSource 2nd ContextDisposing event, EntityDataSource control variable 2nd default alias for specifying ControlState, ASP.NET page Create factory method, overloading CreateInstance method CreateQuery method Entity SQL expression as parameter ToList appended to, focing query execution using Include method with CreateSourceQuery method, enhancing deferred loading CSDL (Conceptual Schema Definition Language) 2nd associations AssociationSet objects complex type defined in constraints in EntityContainer class EntitySet class EntityType data type foreign keys, missing navigation properties returning collections NavigationProperty
schema viewing in Designer's Model Browser CurrentValueRecord.DataRecordInfo CurrentValueRecord.SetBoolean CurrentValues property, ObjectStateEntry 2nd 3rd modifying values with ObjectStateManager reading with visualizer tool CurrentValues, ObjectStateEntry reading customization of entities creating common methods or properties for all entities custom code generation event handlers methods overloading context and entity methods partial classes properties [top]
[D] data access layer (DAL) client-side applications server-side applications data access logic data binding 2nd 3rd [See also EntityDataSource controls] complex types using EntityDataSource without EntityDataSource data source for controls [See EntityDataSource controls] in master/detail form late binding by Relationsip Manager to provide related objects support by Object Services Windows Form controls, WCF service client Windows Forms applications adding code for EDM query adding new entities adding related EntityCollection to form allowing user to edit data creating Object data source for an entity editing navigation properties and trimming query entities, BindingSources, and important rule getting entity details onto form testing using data sources WPF applications adding Activities EntityCollection adding items to child EntityCollection adding new entities binding with ListBox on WPF form editing entities and related data selecting entity and seeing its details XAML's role in data binding
data contract serialization data contract, client agreement to Data Manipulation Language (DML) 2nd Data Source Configuration wizard data sources adding new entities to Windows Forms application creating Object data source for an entity helping with data binding Data Transfer Objects [See DTOs] data validation [See validation of data] data-binding controls how ObjectDataSource gets data into and out of ObjectDataSource as data source data-bound controls, complex types with DataAdapter, performance test for database updates database connections connection pooling disposal of EntityConnection versus security concerns database constraints exceptions thrown by broken constraints database queries, translation of LINQ to Entities or Entity SQL query to database tables defining relationships between mapping single entity to multiple tables database views in the EDM databases cascading deletes in choosing your backend database hits versus busy memory generation of database from the model limiting demands on using projections programming against the model, not the database querying EDM, not the database schema of normalized database tables updates to, anonymous types and DataBridge class (example) bridging UI, business logic, and data access logic building caching or refreshing pick list with MergeOptions class for lightweight objects to use in UI pick lists creating main entity pick list entity graph for use by UI implementing primary elements of lists for UI drop-downs rolling back user changes saving changes master/detail form using using for data binding in master/detail form DataContract classes, defining for WCF service 2nd DataContractAttribute attribute DataGridView default, problems with data binding to EntityCollection forcing to display navigation properties DataMember attribute
adding to partial class properties marking properties as 2nd DataReader objects performance test performance test results DataRecordInfo objects DataSets DataSource objects, building reports from DataSpace enumeration testing to see if SSpace is loaded DataTemplate DateTime field concurrency checks DateTime values in Entity SQL DateTimePicker controls DbConnectionStringBuilder classes DbDataReader objects 2nd forward-only access to fields shaping EntityClient results unwrapping results DbDataRecord objects 2nd non-scalar properties and returned by dynamic Entity SQL query created with MetadataWorkspace returned by EntityClient returned by projection with query builder methods shaped data returned by ObjectQuery DbTransaction classes DbTransaction objects Commit method created within SaveChanges method deferred loading 2nd 3rd entity collections with Load for EntityReference navigation properties lazy loading versus performance considerations using CreateSourceQuery to enhance DefiningQuery creating views with implementing solving more complex problems with stored procedures versus testing Delete function, mapping to an entity DeleteObject method, ObjectContext DeletePayment function, mapping to stored procedure (example) deletion of objects [See also cascading deletes] cascading delete rule cascading deletes Customer.RemoveChild method (example), WCF service client defining delete stored procedure directly in the model DeleceCustomer method (example), WCF service deleting entities deleting entities from EDM deleting from grids involving EntityCollections and referential constraints deleting reservations in WCF service client inherited objects mapping delete stored procedure to types in inheritance structure derived types [See also inheritance; inheritance mapping]
design of .NET 4.0 version of Entity Framework Designer class generation by, preventing complex types and customizations not supported by, implementing Designer section in EDMX file displaying EDM limitations or problem areas mappings support Model Browser Update Model from Database Detach method, ObjectContext detached entities adding entities to EntityCollection of adding new detached entities calling Load method on Detached value, EntityState enum 2nd detaching graphs serialization and detaching objects from ObjectContext DetailsView control creating parent EntityDataSource control for on data-driven form DisplayFields method disposal of store connections Distributed Transaction Coordinator [See DTC] DML (Data Manipulation Language) 2nd domain-driven development DropDownList control binding to EntityDataSource control DTC (Distributed Transaction Coordinator) 2nd DTOs (Data Transfer Objects) 2nd 3rd building DTO to take place of an EntityObject replacing EntityCollection navigation properties replacing EntityReference navigation properties EntityState properties not relying on ObjectContext EntityStateLocal enums and interface implementing IEntityStateLocal in DTO classes implementing IEntityStateLocal in entity classes incoming from client, transferring to entities initializing DTO children methods to create DTOs from entity objects using instead of EntityObjects in WCF service [top]
[E] eager loading using in EDM query in form load using in WPF form using Include method for using Include method for entity references EDM (Entity Data Model) 2nd 3rd
cascading deletes in code generation from EDM to classes components of creating 2nd building model into an assembly class library project to host collisions between property and entity names Entity and EntitySet names many-to-many relationships mapping stored procedures navigation property names, cleaning up CSDL (Conceptual Schema Definition Language) customizing complex types encapsulating sets of properties creating specialized mappings with QueryView implementing customizations not supported by Designer mapping stored procedures mapping TPT inheritance Multiple Entity Sets per Type self-referencing associations TPC inheritance for tables with overlapping fields database views from deleting entities from design tools display in Designer window EDMX file, runtime and Designer sections within Entity Framework entity properties files for custom classes inspecting model's XML mapping custom classes to entity classes EntityContainer inheriting from EntityObject metadata attributes navigation properties rules for custom classes and properties scalar properties MSL (Mapping Specification Language) performance test for queries programming against, not the database querying avoiding inadvertent query execution Entity SQL queries returning objects EntityClient queries example LINQ to Entities queries 2nd method-based syntax model, not the database shortest query translation to database queries reasons for using SSDL (Store Schema Definition Language) types unsupported by Designer EDM Generator Edm.EntityTypes EdmEntityTypeAttribute 2nd
EdmRelationshipAttribute EdmRelationshipNavigationPropertyAttribute EdmScalarPropertyAttribute 2nd EDMX Code Generator EDMX file impact of project compilation on runtime information and Designer information StorageModels section EFExtensions 2nd Element property, CollectionChangeEventArgs entities creating dynamically with reflection, MetadataWorkspace, and ObjectStateManager creating with CreateInstance method defined with DefiningQuery defining a partial class for deleting from the model managing with ObjectContext adding new entities from Entity Framework to native command inserting new parents and children SaveChanges method mapping functions to inspecting mappings in XML using Model Browser with multiple relationships to single entity names of, collisions with property names partial classes queries retrieving single entity using GetObjectByKey method using an entity to find an ObjectStateEntry entity classes [See also classes] custom, mapping to EDM Entity Data Model [See EDM] Entity Data Model Wizard Entity Framework assemblies building reports with change tracking challenges in distributed applications clients and database, choosing for backend design tools for Entity Data Model entities Entity Data Model [See EDM] EntityClient API extensions learning tools for LINQ to SQL and managing objects with Object Services migrating LINQ to SQL to next version of problem areas or limitations of query samples relationship management samples for using with ASMX Web Services using with WCF services
in web services Entity Framework Design blog Entity Framework Mapping Helper entity graphs [See graphs] Entity object names entity objects Create methods creating DTOs from, method for EntityKey and EntityState navigation properties, understanding Entity Relationship Model (ERM) 2nd 3rd entity splitting mapping single entity to multiple database tables Entity SQL 2nd aggregates in building queries dynamically using metadata cached queries canonical functions conversion of query builder methods to expressions deconstructing simple query differences from T-SQL dynamic, and generics for reference lists EntityClient performance test eSqlBlast query helper filtering and sorting with EntityCollections grouping filtering on group properties returning entities from GROUP BY query injection of Entity SQL into strings JOIN syntax literals method-based syntax queries nested queries ObjectQuery performance test parameters of query builder methods performance, differences in querying with EntityClient and Object Services projecting into an EntityReference projecting object with projections in queries that return objects query builder method using an aggregate query built with, conversion to command tree query created dynamically using MetadataWorkspace, reading results of query expression, specifying for EntityDataSource query plan caching for security concerns with SQL injection attacks SET operators shaped data returned by queries testing tool, eSqlBlast type casting with TREAT AS operator type operators wrapped and unwrapped results rules for EntityClient 2nd caching of queries calling functions that return primitive types catching an exception when using
creating the EntityCommand EntityConnection and the connection string EntityConnection class EntityTransaction ExecuteReader method explicitly creating and opening EntityConnection forward-only access to fields handling command execution with Object Services and performance test queries using projections queries with, performance comparison to Object Services querying with (example) results, queries and results to expect rows returned by queries EntityCollection class EntityCollection objects Add method 2nd adding new or existing attached entities adding new, detached entities adding to EntityCollection of detached entity adding EntityCollection to WPF form adding items to child EntityCollection in WPF form Attach method attachment to an entity clients and data binding to, problems with default DataGridView deleting from grids with EntityCollections involved filtering and sorting with identifying changes in, WCF service client implementation of AssociationChanged event Load method loading with Load method MetadataWorkspace item collections loaded as needed navigating to navigation properties 2nd query returning information from all replacing to create DTO projecting properties from entities related end, navigation properties related, adding to form Remove method using aggregates with EntityCommand, creating EntityCommandCompilationException EntityConnection objects components of connection string and database connections through, security concerns Dispose method dynamic explicitly creating to use with a context information furnished to ObjectContext loading MetadataWorkspace from metadata attribute programming strings specifying string to use for a context
StoreConnection property EntityConnectionString objects constructing using EntityConnectionStringBuilder exceptions caused by EntityConnectionStringBuilder class EntityContainer class 2nd changing name of 2nd custom class, mapping to EDM finding name using MetadataWorkspace supplying for custom classes EntityContainerMapping element EntityDataReader objects 2nd how to parse materialization of results into entity objects EntityDataSource controls 2nd 3rd configuring through its wizard context events ContextCreating event creating concurrently with GridView database hits by events GridView hooked to, formatting ObjectContext for property settings to create query query setting property values dynamically or programmatically testing web application using with complex types ViewState and 2nd working with related child entities adding third level of hierarchical data binding DropDownList DetailsView controlled by DropDownList displaying read-only child data filtering query results with Where property Inserting event, using for new entities specifying Entity SQL query expression working with related EntityReference data binding to another control with WhereParameters displaying navigation properties data editing data concurrently with multiple controls editing navigation properties using Include property EntityEntry objects EntityException _entityKey class-level variable EntityKey objects contained by EntityReference creating on the fly, using EntitySets 2nd critical identity information for entities defining EntityReferences with 2nd deleting for derived type in TPT inheritance mapping EntityState property locating relationships for an entity object without, attaching to context retrieving ObjectStateEntry with sychnronizing with EntityReference.Value
using to find an ObjectStateEntry WCF service client and EntityKey property IEntityWithKey interface tracking changes to EntityModelCodeGenerator property EntityObject class 2nd creating entity objects without entity classes custom classes inheriting from dependence on Entity Framework classes navigation properties, working with PropertyChanging and PropertyChanged events relationships between instantiated EntityObjects [See relationships] ReportPropertyChanged method ReportPropertyChanging method using DTOs instead of, WCF service using in read-only web pages EntityReference objects concurrency checks for navigation properties creating from EntityKeys in WCF service deferred loading for navigation properties defining with an EntityKey eager loading with Include method empty EntityReference EntityDataSource working with related data filtering and sorting with implementation of AssociationChanged event inserting and updating entities containing, using ObjectDataSource events inserting records using its properties Load method navigation properties editing, using ObjectDadaSource events query returning information from all updating using ObjectDataSource navigation properties and navigation to replacing navigation properties for DTO returning a foreign key from Value object synchronizing with its EntityKey WCF service client and EntitySet objects 2nd defined with DefiningQuery defining EntityKey from metadata of names of query builder methods and relationship to EntityContainer renaming in example model using to construct EntityKey EntitySet property, ObjectStateEntry EntitySetMapping element 2nd EntitySetName EntitySQLException EntityState enumeration Added value Load method and attachment and detachment of objects from ObjectContext
creating for class implementing IEntityWithChangeTracker data validations by ObjectContext.SavingChanges event definition and listing of values Deleted value Detached, for objects not in ObjectContext cache DTOs (Data Transfer Objects) impact of calling SaveChanges method ObjectStateEntries resulting from calls to Add and Attach methods properties for IPOCO-enabled objects properties not relying on ObjectContext RelationshipEntry objects relationships instantiated as objects serialization and specifying for GetObjectStateEntries method EntityStateLocal enums and interface (example) EntityTransaction, using to read queries EntityType objects getting all from a type in the CSDL inspecting with System.Type metadata information exposed in requesting array of all in CSDL EntityTypeMapping element 2nd EntriesFromContext custom extension method EntryDetails extension method Enum support, Entity Framework and ERM (Entity Relationship Model) 2nd 3rd eSqlBlast tool 2nd event handlers creating for AssociationChanged event customizable, in Entity Framework events context events related to EntityDataSource EntityDataSource control exception handling catching exception and disposing ObjectContext in Finally clause catching exception when ObjectContext is automatically disposed catching exception when using EntityClient connection string exceptions low-level concurrency exceptions handling multiple conflicts handling without user intervention OptimisticConcurrencyExceptions catching exceptions handling recursively refreshing collections of entities refreshing related entities in graph relationship change without scalar properties change using ClientWins Refresh using ObjectContext.Refresh using StoreWins Refresh for your own transactions exceptions EntityCommandCompilationException EntityConnectionString general entity exceptions InvalidOperationException multiple parties editing data concurrently
Object Services, ObjectStateEntries returned by query compilation invalid Entity SQL query expressions raised by calling SaveChanges Execute method 2nd 3rd MergOption defined as parameter of returning ObjectResult ExecuteReader method EXISTS method expression trees conversion of LINQ to Entities queries to extension methods use for discoverability using to overload GetObjectStateEntries method extensions for Entity Framework [top]
[F] FieldMetadata objects, type hierarchy FirstorDefault method foreign key fields, mapping of navigation properties to foreign keys getting value for navigation properties and primary key foreign key relationship primary key/foreign key relationship returning from EntityReference property uniqueness constraints, checking of FormView control forums for Entity Framework FROM clause, LINQ to Entities queries functional programming FunctionImportMapping element functions attributes of composing queries against created in SSDL, representing stored procedures debugging function metadata Entity SQL canonical functions getting list of from SSDL implementing mapping functions to entities mapping to entities CustomersbyState function entity created from a view inspecting mappings in XML using Model Browser mapping to stored procedures using mapped functions FunctionTypes
[top]
[G] generic method dynamically querying for reference lists GetFunctions/TryGetFunctions methods GetItem/TryGetItem functions GetItemCollection function GetModifiedProperties method, ObjectStateEntry GetObjectByKey method 2nd considerations in n-tier client application GetObjectStateEntry method versus GetObjectStateEntries method 2nd GetObjectStateEntry method GetObjectByKey method versus golden rule of Entity Framework graphs 2nd [See also relationships] adding to ObjectContext building directly with RelationshipManager building using reflection, MetadataWorkspace, and ObjectStateManager concurrency exception caused by entity within, Refresh method and creating deep graph to return to web page for display entity graph for use by user interface moving entity to new graph, relationships and not using object graphs in ASP.NET application business layer object graphs requirement to be entirely in or out of ObjectContext updating object graph in WCF service updating, SaveCustomer method (example) in WCF service WCF service that updates XML serialization and GridView control binding EntityDataSource, using WhereParameters creating concurrently with EntityDataSource design-time, displayed with configured EntityDataSource hooked to EntityDataSource, formatting grouping chaining aggregates in Entity SQL filtering on group properties returing entities from GROUP BY query filtering on grouping conditions naming properties GROUPJOIN operator [top]
[H] HAVING clause
[top]
[I] IEntityStateLocal interface (example) implementing in DTO classes implementing in entity classes IEntityWithChangeTracker interface 2nd implementing IEntityWithKey interface implementing EntityKey property IEntityWithRelationships interface entry point for RelationshipManager implementing navigating relationships and loading related data IEnumerable interface Collection versus creating for attaching or loading entities into EntityCollection ObjectResult, returned by CustomersbyState function IExtendedDataRecord interface implicit transactions implicitly typed local variables Include method accessing properties from Include in a query choosing between Load method and data shaping by using for eager loading using in EDM query in form load using with ObjectQuery Include property, EntityDataSource control 2nd inheritance composition versus concurrency checks and inherited types deletion of inherited objects mapping stored procedures to inherited types query stored procedures and inherited types querying derived types specifying or excluding types in queries querying inherited types QueryView used with inherited types inheritance mapping TPC inheritance for tables with overlapping fields TPH for tables with multiple types abstract entity types creating derived type setting default value on table schema testing TPT inheritance for tables describing derived types explicit queries for derived types limitations of TPT inheritance mapping TPT inheritance property name conflicts
querying inherited types remapping associations SaveChanges and new derived types testing TPT inheritance TPT with abstract types Initialize method, WCF service client application inserts defining insert stored procedure directly in the model EntityDataSource control events for Insert command breakdown of Insert function, mapping to an entity InsertCustomer ASMX web service method (example) Inserting event, EntityDataSource inserting records with EntityReference properties mapping Insert function mapping insert stored procedure to types in inheritance structure integrity of data referential integrity and constraints in database and EDM relationships interoperability of web services InvalidOperationException IPOCO (Plain Old CLR Objects) interfaces implementing IEntityWithChangeTracker IEntityWithKey IEntityWithRelationships working with custom classes IQueryable interface anonymous type implementing Collection versus implementation by ObjectQuery query builder methods and IRelatedEnd interface Attach versus Add methods attaching an IEnumerable to EntityCollection EntityReference methods to load, add, and attach navigation properties IsDirty property creating for ObjectContext of n-tier client application using to validate edits IService interface IsolationLevel ItemCollection objects, MetadataWorkspace loading as needed by EntityCollection querying for EDM ItemsSource property ItemTemplate control [top]
[J]
joins JOIN and GROUPJOIN operators JOIN syntax for LINQ [top]
[K] Key element Key property KeyListItem class KeyMembers property 2nd properties used to build EntityKey [top]
[L] lambdas LINQ to Entities query to be precompiled using aggregate methd with, in LINQ late binding, provision of related objects by Relationship Manager lazy loading deferred loading versus Entity Framework Lazy Loading learning tools for Entity Framework LINQ chaining aggregates in grouping queries compiled queries example queries filtering and sorting with EntityCollections filtering on Group property Group By with explicitly named groups and targets JOIN syntax method syntax query using aggregates method-based queries 2nd chaining methods methods, query builder methods versus out-of-scope variables projections and new language features anonymous types implicit and explicit anonymous type creation implicitly typed local variables projections using query methods queries, avoiding inadvertent execution of query against EDM (example) querying items of model shaped data returned by queries SQL injection attacks and type filtering with OfType method
Union query method using an aggregate method with a lambda using nested query as a projection using nested query as collection to be queried LINQ to Entities aggregates in casting a query to ObjectQuery to use ToTraceString method grouping in Include method in a query invalid query experssions error ObjectQuery and performance comparisons for compiled and noncompiled queries performance test precompiled queries processing query to command tree projecting into an EntityReference projecting into an EntityReference and returning unshaped data projections in queries queries query containing an ObjectQuery query example query retrieving a single entity query using a JOIN LINQ to SQL ADO.NET DataSets and future of major differences between Entity Framework and performance test performance tests for database updates performance, Entity Framework versus List controls, complex types with ListBox controls adding Activities ListBox and binding it to Trips ListBox in WPF form binding ComboBox controls to ListBox binding TextBox controls to a ListBox data binding, XAML for populating using Selected event of EntityDataSource Properties window, binding attributes in WPF tricks for more interactive ListBox lists binding drop-down at runtime in ASP.NET application class for lightweight objects to use in UI pick lists drop-down lists for ASP.NET web page main entity pick list for client application UI pick lists in master/detail form, consumption of reference lists, dynamic Entity SQL and generics for UI drop-downs using MergeOptions to cache or refresh a pick list ListView control binding to EntityDataSource data binding to complex types literals in Entity SQL Load method behind-the-scenes query creation and execution choosing between Include method and deferred loading of entity collections detached entities and
inability to call on Added entities loading [See also deferred loading; eager loading; lazy loading] deferred loading, using CreateSourceQuery to enhance lazy versus deferred loading [top]
[M] managing resources [See resource management] many-to-many relationships 2nd [See also relationships] association mapping related entities in WPF form Mapping element Mapping Specification Language [See MSL] MappingFragment element mappings complex types conditional mapping of entity to data store creating with QueryView custom classes to EDM entity classes EntityContainer inheriting from EntityObject metadata attributes navigation properties rules for scalar properties Designer support for entity created by DefiningQuery to virtual table Entity Framework Mapping Helper functions to stored procedures MSL section of the EDM single entity to multiple tables, using entity splitting merging entities testing entity splitting stored procedures stored procedures to inherited types TPC inheritance for tables with overlapping fields TPH inheritance for tables with multiple types abstract entity types creating derived type querying types in TPH mapping setting default value on table schema TPT inheritance for tables describing derived types abstract types with TPT explicit queries for derived types limitations of TPT inheritance mapping entity associations mapping TPT inheritance property name conflicts querying inherited types SaveChanges and new derived types testing TPT inheritance
master/detail form data binding in MergeOption property default listing of values No Tracking 2nd using to cache or refresh UI pick list MEST (Multiple Entity Sets per Type) metadata accessing with ObjectStateEntry properties and methods attributes for mapping custom class to EDM EntityConnection string EntityConnectionString, files can't be found error FieldMetadata hierarchy furnished by EntityConnection to ObjectContext updating during SavingChanges Metadata Artifact Processing property 2nd metadata attribute, EntityConnection MetadataWorkspace 2nd builiding Entity SQL queries dynamically using metadata clearing from memory creating without an EntityConnection Entity SQL and generics for reference lists item collections contained in item collections loaded as needed with EntityCollection loading querying items of EDM reading metadata from reading results of dynamically created query using with reflection and ObjectStateManager using with System.Reflection to create EntityObjects without entity classes methods aggregates in LINQ and query builder methods combining LINQ and query builder methods 2nd creating common method for all entities creating DTOs from entity objects customizable, in Entity Framework extension methods (.NET) generic method dynamically querying for reference lists LINQ and Entity SQL queries LINQ method-based queries LINQ versus query builder overloading context and entity methods partial query builder 2nd conversion to Entity SQL expressions EntitySets and Include method projecting with model [See EDM] Model Browser ModifiedDate field concurrency checks MSDN Code Gallery website MSL (Mapping Specification Language) 2nd elements Multiple Entity Sets per Type (MEST) multiplicity between entities
[top]
[N] n-tier ASP.NET applications [See ASP.NET, n-tier applications using Entity Framework] n-tier client-side applications allowing users to roll back edits building CommandExecutor class building DataBridge class additional lists for UI drop-downs caching or refreshing pick list with MergeOptions class for lightweight objects to use in UI pick lists creating main entity pick list entity graph for use by UI implementing primary elements of DataBridge rolling back user changes saving changes using long-running ObjectContext helping user who forgets to save changes implementing logic fitting best in entity partial classes IsDirty property for ObjectContext keeping non-UI logic out of the UI layered architecture master/detail entry form using DataBridge class organizing layers using DataBridge class for data binding in master/detail form namespaces assembly namespace changing model namespace creation of additional parts for partial class EDM namespace for Entity Framework existing, containing new classes and functionality for Entity Framework finding NamespaceName using MetadataWorkspace new namespaces for Entity Framework native queries adding to the model impact of associations on navigating navigation properties 2nd adding with EntityCollection.Add attaching and removing created by EDM for associations creating AssociationChanged event for custom class, defining and mapping to EDM editing for an entity editing in Windows Forms application of entity objects in entity objects, understanding EntityCollection replacing to create DTO EntityReference concurrency checks for
deferred loading for flattened by EntityDataSource replacing for DTO forcing display by DataGridView in Windows form loading, adding, and attaching loading, EntityReference.Load and EntityCollection.Load names entities with multiple relationships to single entity mapping to foreign key field NavigationProperty of entity in CSDL not required for associations query returning data from all EntityCollections and EntityReferences returned by dynamic Entity SQL query, determining if entity or EntityCollection returning collections returning Entity and EntityReference properties returning EntityCollection properties rolling back changes in setting to create relationships value object of EntityReference navigation to an EntityReference nested queries Entity SQL LINQ nested query as a projection LINQ nested query as collection to be queries .NET 4.0 .NET impact of methods on generated SQL new keyword (C.html#) normalized database tables, schema for [top]
[O] object graphs [See graphs] object materialization 2nd firing of PropertyChanged and PropertyChanging events during Object Services 2nd core functionality, areas of custom classes support data binding support exceptions, ObjectStateEntries returned by object materialization ObjectContext class position in Entity Framework projections queries with, performance comparison to EntityClient queries query processing relationship management sending changes to the database state management and ObjectStateEntry transaction support XML and binary serialization support ObjectContext class 2nd
AcceptAllChanges method 2nd controlling in transactions Add method 2nd adding WCF service object graphs to ApplyPropertyChanges method Attach method 2nd attaching and detaching objects from AttachTo method cache for in-memory objects change tracking problems in multi-tiered applications Changed and Changing events changing scope of context Connection property 2nd connection strings creating EntityState properties not relying on creating IsDirty property for declaration of DeleteObject method 2nd Detach method Dispose method entities in multithreaded applications entity objects managed by EntityDataSource and exceptions in catching exception when automatically disposed forcing to use its own thread functions in n-tier client application functions of GetObjectByKey and TryGetObjectByKey methods GetObjectByKey method GetReferenceList extension method how it fits into web page life cycle state solutions in ASP.NET updating entities in ASP.NET application using EntityObjects in read-only web pages in-memory objects not managed by inability to use transactions within information passed by EntityConnection instantiating for Windows Form in Form Load event loading MetadataWorkspace from long-running ObjectContext for n-tier client application management of association objects managing entities adding new entities DeleteObject method inserting new parents and children ObjectStateEntry SaveChanges method marked as partial class merging results into the cache moving out of ASP.NET page into DataBridge class in middle tier ObjectStateEntries for each entity and relationships managed by ObjectStateManager property OnContextCreated parital method opening and closing connections Refresh method
related entities attached or detached, platinum rule for relationship management RelationshipManager and SaveChanges command execution exceptions SaveChanges method SavingChanges event customizing data validation with ObjectStateManager and state management with ObjectStateEntry synchronization of value of EntityReference and its EntityKey tracking of relationships ObjectDataSource control 2nd designing object provider classes to use with enforcing its rules on your objects how it gets data into and out of data-binding controls wiring up provider classes to tweaking ObjectDataSource to work with entities using ObjectDataSource events to solve problems with entities ObjectDataSource Wizard ObjectEntityParameters ObjectQuery class advantages of caching of queries casting a LINQ to Entities query to CommandText property Context property EntityClient providing command execution functions for Execute method 2nd implicitly created by LINQ to Entities query LINQ to Entities and MergeOption property 2nd methods and properties of parameterized queries Parameters property performance tests for queries 2nd query builder methods query built with, conversion to command tree returned by ObjectContext class properties for EntitySets shaped data from ToTraceString method 2nd unwrapping results using Include method with wrapped results ObjectResult 2nd 3rd data binding and objects deleting in WCF service application returned by Entity SQL queries ObjectStateEntry objects attachment and detachment of entities from context change tracking on the client CurrentValues and OriginalValues methods CurrentValues property Entity value EntityState property Added value
Deleted value examining in debug view finding relationships with a particular entity, extension method for information exposed in managing with ObjectStateManager OriginalValues property passing between multi-tiered applications reasons for reading relationships and resulting from calls to Add and Attach methods returned by Object Services exceptions serialization and state management and using EntryDetails extension method with visualizer for determining if property is modified getting ComplexType properties reading OriginalValues and CurrentValues retrieving entry using EntityKey setting up project and code file State and Entity properties ObjectStateManager class examining an ObjectStateEntry in debug view GetObjectStateEntries method 2nd getting a single ObjectStateEntry getting for ObjectContext interaction with, IEntityWithChangeTracker managing ObjectStateEntry objects modifying CurrentValues for ObjectStateEntry reasons for reading ObjectStateEntries taking advantage of in ObjectContext.SavingChanges event using EntriesFromContext extension method with using with MetadataWorkspace reflection versus using with MetadataWorkspace and reflection working with relationships ObservableCollection object ODBC (Open Database Connectivity) OfType method On[Property]Changed method On[Property]Changing method OnContextCreated parital method one-to-many relationships Open Database Connectivity (ODBC) operation contracts defining for WCF services WCF smater service using entities operators type operators used with QueryView optimistic concurrency concurrency checks without rowversion field implementing concurrency checks and inherited types concurrency checks and stored procedures concurrency checks for EntityReference navigation properties concurrency checks on checksum in data store
flagging property for concurrency check use of ConcurrencyMode property options in Entity Framework OptimisticConcurrencyException 2nd 3rd handling catching a number of exceptions recursively refreshing collections of entities refreshing related entities in graphs relationship change without scalar property change reporting an exception using ClientWins refresh using ObjectContext.Refresh using StoreWins refresh handling for your own transaction StateEntries property OrderBy method OriginalValues property, ObjectStateEntry 2nd 3rd reading with visualizer out-of-scope variables in LINQ [top]
[P] Page class Page events paging, dynamic paging and Entity Framework queries Parameter element ParameterTypeSemantics attribute partial classes creating custom properties for defining for an entity logic fitting best in, implementing in n-tier client application other uses of overloading context and entity methods partial methods implemented by VB and C.html# OnContextCreated method performance comparisons for compiled and noncompiled LINQ to Entities queries comparisons for Entity Framework, ADO.NET, and LINQ to SQL database updates and query plan caching for Entity SQL reducing cost of query compilation precompiled LINQ to Entities queries precompiled views test for LINQ to Entities compiled query persistent entries rolling back edits within ObjectContext rolling back from the database platinum rule for related entities attached to or detached from ObjectContext POCO (Plain Old CLR Objects) POCO (Plain Old CLR Objects) Adapter for version 1
precompilation frequently used query for n-tier client application precompiled views primary key/foreign key relationship 2nd primary keys database tables with common key uniqueness constraints, checking of primitive types, queries returning PrimitiveTypeKind class PrimitiveTypes projections 2nd anonymous type returned by LINQ to Entities query in Entity SQL projecting objects using query builder methods EntityCollection, using LINQ Include method results and in LINQ to Entities query LINQ queries properties from EntityCollection entities returning shaped data using LINQ query methods using nested LINQ query as properties anonymous types as creating common property for all entities custom class navigation properties custom class scalar properties custom class, mapping to EDM customizing for entities calculations on child collections editing for an entity encapsulation in complex types flagging for concurrency checking marking as DataMember name conflicts caused by inheritance, handling names of, collisions with entity names non-nullable properties of a class non-scalar, DbDataRecords and reporting changes in scalar and navigation properties of entities Property elements PropertyChanging and PropertyChanged events, EntityObject calculating database-computed columns locally using PropertyChanged PropertyChanged event in WCF client providers Access and ODBC available providers for Entity Framework connection string data provider names furnished by EntityConnection to ObjectContext Entity Framework Sample Provider generating SQL from command tree object provider classes to use with ObjectDataSource proxy classes creating for ASMX Web Service client methods calling, updating for WCF service
[top]
[Q] query builder methods [See methods] query execution avoiding inadvertent execution using Execute method using ToList or ToArray mthod Query Notification QueryView 2nd creating simple QueryView important considerations with returning read-only entities testing using with inherited types testing [top]
[R] reference lists, dynamic Entity SQL and generics for referential integrity database constraints enforcing, cascading deletes and enforced by constraints deletion from grid involving ReferentialConstraint element in SSDL reflection System.Reflection, using with MetadataWorkspace System.Reflection.Assembly, instantiating using with MetadataWorkspace and ObjectStateManager Refresh method, ObjectContext ClientWins refresh ClientWins refresh on set of entities graphs not impacted by StoreWins refresh 2nd relationship span experimenting with relationship span (ObjectContext) RelationshipEntry objects attempts to read metadata entity moved from one graph to another EntityState example entry defining relationship between entities using EntityKeys inspecting iterating through, using KeyIsInRelationship method (example) searching to find Related EntityKeys RelationshipManager class building graphs directly with
creating a relationship on the fly creation of EntityCollection using 2nd implementing for custom class IRelatedEnd interface and relationships 2nd between instantiated EntityObjects deletes and cascading deletes experiments with relationship span management by ObjectContext navigation properties in entity objects referential integrity and constraints Relationship Manager and IRelatedEnd change in, without scalar property change constraints as defining between entities Attach versus Add method CLR way, setting navigation property getting foreign key value loading, adding, and attaching navigation properties moving entity to new graph setting EntityReference using EntityKey using CreateSourceQuery to enhance deferred loading using EntityCollection.Add using EntityReference.Load and EntityCollection.Load EdmRelationshipAttributes IEntityWithRelationships interface 2nd implementation of instantiation as objects by context management by ObjectContext management of many-to-many, working with multiplicity between entities updates in SaveChanges working with, in ObjectStateManager Remove method, EntityCollection reporting resource management database connections performance resources for additional information and discussion rollbacks allowing users to roll back edits user changes in client application rowversion field, using for concurrency checks [top]
[S] SaveChanges method 2nd command execution exceptions concurrency exceptions and constraints checked by Entity Framework in calls to creating your own System.Transaction for
database transactions integer returned by newly added derived types and not calling AcceptAllChanges relationship updates in rolling back automatically on UpdateException using with Windows Forms SavingChanges event customizing setting default values (example) data validation with taking advantage of ObjectStateManager scalar properties class implementing IEntityWithChanage Tracker custom class, defining and mapping to EDM data binding to by controls of entity objects resetting entity's current values to original values tracking of changes by ObjectStateEntry scalar values, functions returning ScalarProperty element schema files created from EDMX file in compiled project EDM (Entity Data Model) schemas for the schemas separating from assembly in build process validation of security protecting data from connection piggybacks SQL injection attacks Selected event, EntityDataSource controls 2nd selection ComboBox control, events for EntityDataSource control events for Serializable attribute serialization automatic binary binary serialization of entity graphs DataMembers EntityCollection.Attach method and explicit making entities serializable for web and WCF services multi-tiered applications and 2nd object graphs, WCF services object state and ObjectContext, ObjectStateManager, and ObjectStateEntry, not serializable support by Object Services XML and DataContract serialization service reference for WCF service session state using to limit database hits SET operators, Entity SQL shaped data returned by queries Entity SQL queries EntityClient results Include method
performance tests for Entity SQL queries returning Silverlight applications smart client 2nd [See also Silverlight applications; Windows Forms; WPF] SortedDescriptions collection SQL generated from queries, impact of .NET methods on SQL injection attacks 2nd SQL Query Notification SQL Server Management Studio property editor SQL Server, Reporting Services SqlClient providers, generating SQL from command trees SQLServer.CheckSum function SSDL (Store Schema Definition Language) 2nd adding native queries to model adding StoreGeneralPattern attribute for unmapped non-nullable field associations and AssociationSet database constraints in deleting entities entity representing database view mapped to DefiningQuery function attributes function mappings functions created in modifying manually native objects in stored procedures and viewing in Designer's Model Browser StackPanel container control state management ASP.NET's state solutions, evaluating against Entity Framework change tracking EntityDataSource control ObjectStateEntry and StateEntries property, OptimisticConcurrencyException store commands, conversion of LINQ to Entities and ObjectQuery queries to store providers, issues causing entity command compilation exception Store Schema Definition Language [See SSDL] StoreConnection objects StoreConnection property, EntityConnection stored procedures 2nd adding into the model adding native queries to the model adding native views to the model composing queries against functions concurrency checks and DefiningQuery versus function mapping functions, working with function attributes implementing and querying with UDFs implementing functions insert, update, and delete, defining directly in the model limited support by Designer mapping mapping to types within inheritance structure query, mapping and executing functions matching entity with changed properties inherited types and queries returning multiple resultsets
queries returning primitive types queries returning randomly shaped results replacing stored procedures with views using commands that affect persisted database functions not returning entities functions returning entities StoreWins ObjectContext.Refresh with RefreshMode value subqueries (Entity SQL), aggregates in System.Argument exception System.Data namespace System.Data.Common.DbTransaction classes System.Data.Entity namespaces System.Data.EntityException System.Data.EntityState enumeration System.Data.Metadata.Edm namespace System.Data.MetadataException System.Data.Objects namespace System.Data.Objects.CompiledQuery class System.InvalidOperationException System.NotSupportedException System.Reflection System.Reflection.Assembly class System.Runtime.Serialization namespace System.Serializable System.Transaction class creating your own for SaveChanges reading queries using System.Transaction.TransactionScope object System.Type, using to inspect EntityType [top]
[T] T-SQL differences from Entity SQL querying entity with 1:0:1 relationship Table per Hierarchy inheritance [See TPH inheritance] Table per Type inheritance [See TPT inheritance] table-valued functions (TVFs) templated controls, data binding to complex types TemplateField controls using for navigation properties testing, problems in Entity Framework TextBlock controls TextBox controls binding to ListBox threading, entities in multithreaded applications forcing ObjectContext to use its own thread managing threads for concurrent processing TimeStamp field, using for optimistic concurrency check ToArray method, query execution with
ToList method, query execution with ToList( ) method ToTraceString method, ObjectQuery TPC (Table per Concrete) inheritance TPH (Table per Hierarachy) inheritance TPT (Table per Type) inheritance transactions creating your own handling exceptions for your own implicit ObjectContext and reading queries using System.Transaction or EntityTransaction rolling back edits within ObjectContext support by Object Services TransactionScope objects isolation levels and TREAT AS operator TryGetItemCollection function TryGetItems function TryGetObjectByKey method use in n-tier client application TryGetObjectStateEntry method TVFs (table-valued functions) type filtering Type objects type operators in Entity SQL TypeOf operator [top]
[U] UDFs (user-defined functions) 2nd attributes of implementing and querying with UI (user interface) class for lightweight object to use in UI pick lists creating main entity pick list for entity graph for use by master/detail form, data binding using DataBridge class (example) preventing non-UI logic from leaking into rolling back changes from saving changes supplying lists for UI drop-downs Union query method (LINQ) uniqueness, constraints on unit testing, problems in Entity Framework unwrapping Update Model from Database (Designer) Update Model Wizard UpdateException automatic rollback of SaveChanges for updates anonymous types and
ApplyPropertyChanges method, ObjectContext database updates and performance defining update stored procedure directly in the model EntityDataSource control events for mapping of update stored procedure to types in inheritance structure mapping Update function to stored procedure options for updating entities in ASP.NET application problems with entites passed in multi-tiered applications T-SQL Update command, using TimeStamp field for optimistic concurrency check Update function, mapping to an entity UpdateChildren method (example), WCF service UpdateCustomer ASMX web service method (example) UpdateException UpdatePayment stored procedure (example), concurrency checking with updating entities in ASP.NET application WCF service, UpdateCustomer method (example) deleted reservations logic for existing reservations logic for new reservations logic for wrapping up with SaveChanges [top]
[V] validation of data customizing ObjectContext.SavingChanges event using IsDirty property to facilitate using IsDirty to validate user edits using SavingChanges event validators working with entities without knowing their type XML for schema value objects variables in chained methods Entity SQL implicitly typed local variables out-of-scope variables in LINQ VB (Visual Basic) anonymous types in version 9 code for entity object scalar property example queries grouping chained aggregates in VB LINQ implementing PropertyChanged and PropertyChanging events implicitly typed local variables ObjectStateEntry visualizer partial method implementation projections Threading.Monitor class TypeOf operator for type filtering vertical splitting 2nd views adding native views to the model
creating views with DefiningQuery DefiningQuery implementing a DefiningQuery precompiled, EDM Generator for replacing stored procedures with ViewState EntityDataSource control and impact of entities stored in storing original EntityReference values for later use using to limit database hits virtual objects Visual Studio 2010 [top]
[W] WCF (Windows Communication Foundation) building a service adding graphs to ObjectContext ApplyPropertyChanges client rules to identify changes in EntityCollection creating service application defining DataContract classes defining service operation contracts deleted reservations for UpdateCustomer method deleting objects enabling partial class properties to participate existing reservations for UpdateCustomer method implementing service interface new reservations for UpdateCustomer method testing services and operations UpdateCustomer method (example) updating object graphs wrapping up UpdateCustomer with SaveChanges building client for service adding functionality to view reservations editing client configuration finishing the form data contract serialization services smarter service for working with entities client agreement with data contract designing service interface EntityState properties not relying on ObjectContext implementing client implementing GetCustomer service operation implementing GetCustomerList service operation implementing GetTrips service operation implementing service operations initializing DTO children methods to create DTOs from entity objects SaveCustomer method to update graph saving edits from client with SaveCustomer
service that updates entities testing with console application UpdateChildren method using DTOs, not EntityObjects web services, Entity Framework in Where clause WHERE clause (Entity SQL) Where( ) method WhereParameters element defining for EntityDataSource Windows Distributed Transaction Coordinator [See DTC] Windows Forms ASMX Service client application client for WCF service application creating an application data binding adding code for EDM query adding new entities creating Object data source for an entity editing navigation properties and trimming the query getting entity details onto form testing using data sources DataSource and complex types designing form for ASMX Web Service client Entity Framework, use in client applications Windows Presentation Foundation [See WPF] WPF (Windows Presentation Foundation) 2nd adding code to query entities driving the form creating new project creating WPF form data binding adding Activities EntityCollection adding items to child EntityCollection adding new entities selecting entity and seeing its details XAML for ListBox on form XAML's role in editing entities and related data Entity Framework, use in client applications WSDL (Web Services Description Language) [top]
[X] XAML, data binding in WPF XML for an EntityType output by ASMX Web Service viewing EDMX as XML serialization object graphs and
[top]