Cover

Programming Entity Framework, 1st Edition
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 corporate@oreilly.com.

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 (Chapters Chapter 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 in Programming 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.NET DataSource 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 EntityObjects 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 the EntityDataSource 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 EntityObjects, 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 permissions@oreilly.com.

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:

bookquestions@oreilly.com

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 DataSets.

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 DataReaders 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     SalesPerson.*, PersonalDetails.*, Person.*
FROM       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 from PersonalDetails are now part of Person. 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<SalesPerson> select p

In return, you will have a set of SalesPerson 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 called entity 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 an Order entity to the model. Notice the relationship lines between SalesPerson and Order, showing a one-to-many relationship between them. There is also a one-to-many relationship between Customer 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 use JOINs. 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 a Customer 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 from EntityObject. 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 Orders, there will be one relationship object between the SalesPerson 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 new DataSource 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 of EntityClient. 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 by EntityClient 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, and Chapter 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. DataSets and DataReaders 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 DataReaders 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 DataReaders as well as the EntityDataReader, which inherits the same DbDataReader as SqlDataReader. This is what a query with EntityClient 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 with DataSets 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, and Chapter 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: a Contact 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. The Contact entity has an Address property that will enable the application to navigate from a Contact to any Addresses related to that Contact. The Address entity has a Contact property that allows you to navigate from an Address to the single Contact associated with the Address.

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 the Contact'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 (see Figure 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 related Address 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 common Fear-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 rightwards double arrow Advanced rightwards double arrow Format Selection. This will make the XML formatting much more palatable.

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. AssociationSets 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:

<EntitySet Name="Addresses"
           EntityType="ProgrammingEFDB1Model.Address" />
<EntitySet Name="Contacts"
           EntityType="ProgrammingEFDB1Model.Contact" />

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

<EntityType Name="Address">
  <Key>
    <PropertyRef Name="addressID" />
  </Key>
  <Property Name="addressID" Type="Int32" Nullable="false" />
  <Property Name="Street1" Type="String" MaxLength="50"
            Unicode="true" FixedLength="true" />
  <Property Name="Street2" Type="String" MaxLength="50"
            Unicode="true" FixedLength="true" />
  <Property Name="City" Type="String" MaxLength="50"
            Unicode="true" FixedLength="true" />
  <Property Name="StateProvince" Type="String" MaxLength="50"
            Unicode="true" FixedLength="true" />
  <Property Name="CountryRegion" Type="String" MaxLength="50"
            Unicode="true" FixedLength="true" />
  <Property Name="PostalCode" Type="String" MaxLength="20"
            Unicode="true" FixedLength="true" />
  <Property Name="AddressType" Type="String" Nullable="false"
            MaxLength="10" Unicode="true" FixedLength="true" />
  <Property Name="ModifiedDate" Type="DateTime" Nullable="false" />

  <NavigationProperty Name="Contact"
            Relationship="ProgrammingEFDB1Model.FK_Address_Contact"
            FromRole="Address" ToRole="Contact" />

</EntityType>

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 <Property> element inside the Address EntityType. IntelliSense will provide a list of options within the property. Alternatively, break something! For example, change the spelling 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

<Association Name="FK_Address_Contact">

   <End Type="ProgrammingEFDB1Model.Contact"
             Multiplicity="1"
             Role="Contact">
     <OnDelete Action="Cascade" />
   </End>

   <End Type="ProgrammingEFDB1Model.Address"
             Multiplicity="*"
             Role="Address" />

</Association>

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 Addresses 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:

<AssociationSet Name="FK_Address_Contact"
                Association="ProgrammingEFDB1Model.FK_Address_Contact">
  <End EntitySet="Contacts" Role="Contact" />
  <End EntitySet="Addresses" Role="Address" />
</AssociationSet>

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 the EntitySet. 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:

<NavigationProperty Name="Contact"
              Relationship="ProgrammingEFDB1Model.FK_Address_Contact"
              FromRole="Address"
              ToRole="Contact" />

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 EntityCollection 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.

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 AssociationSets 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

<Association Name="FK_Address_Contact">
  <End Role="Contact" Type="ProgrammingEFDB1Model.Store.Contact"
       Multiplicity="1">
    <OnDelete Action="Cascade" />
  </End>
  <End Role="Address" Type="ProgrammingEFDB1Model.Store.Address"
       Multiplicity="*" />
  <ReferentialConstraint>
    <Principal Role="Contact">
      <PropertyRef Name="ContactID" />
    </Principal>
    <Dependent Role="Address">
      <PropertyRef Name="ContactID" />
    </Dependent>
  </ReferentialConstraint>
</Association>

<AssociationSet Name="FK_Address_Contact"
          Association="ProgrammingEFDB1Model.Store.FK_Address_Contact">
  <End Role="Contact" EntitySet="Contact" />
  <End Role="Address" EntitySet="Address" />
</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 AssociationSets for any SSDL associations, which, like their CSDL counterparts, act as wrappers for the associations. The SSDL AssociationSets 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 the Mappings 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 <edmx:Mappings> 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 Customers 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

<EntitySetMapping Name="Contacts">
  <EntityTypeMapping TypeName="IsTypeOf(ProgrammingEFDB1Model.Contact)">
    <MappingFragment StoreEntitySet="Contact">
      <ScalarProperty Name="ContactID" ColumnName="ContactID" />
      <ScalarProperty Name="FirstName" ColumnName="FirstName" />
      <ScalarProperty Name="LastName" ColumnName="LastName" />
      <ScalarProperty Name="Title" ColumnName="Title" />
      <ScalarProperty Name="AddDate" ColumnName="AddDate" />
      <ScalarProperty Name="ModifiedDate" ColumnName="ModifiedDate" />
    </MappingFragment>
  </EntityTypeMapping>
</EntitySetMapping>

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 AssociationSets. 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 EntitySetMappings are used to map entities, AssociationSetMappings 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 the contactID 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

<AssociationSetMapping Name="FK_Address_Contact"
               TypeName="ProgrammingEFDB1Model.FK_Address_Contact"
               StoreEntitySet="Address">

  <EndProperty Name="Contact">
    <ScalarProperty Name="ContactID" ColumnName="ContactID" />
  </EndProperty>

  <EndProperty Name="Address">
    <ScalarProperty Name="addressID" ColumnName="addressID" />
  </EndProperty>

</AssociationSetMapping>

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 the vOfficeAddresses 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 into DefiningQuery. 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 in Chapter 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 the For Each/foreach block. For VB the breakpoint is Next, 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 word con 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's EntityContainer 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 in Chapter 2, you saw an EntityContainer that contained the EntitySets and AssociationSets.

The ProgrammingEFDB1Entities class represents that EntityContainer and inherits from an Entity Framework type called ObjectContext. This is why context 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 the ObjectContext itself.

Looking more closely at the Contacts property, you can see that it returns an ObjectQuery of Contact types:

Public ReadOnly Property Contacts() As  _
  Global.System.Data.Objects.ObjectQuery(Of Contact)

public global::System.Data.Objects.ObjectQuery<Contact> 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 (see Figure 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 the Contact 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.

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 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 in Example 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<Contact>(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<Contact>(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.

ObjectParameters 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 standard Where 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 a predicate. 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 for OrderBy 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 evaluates context.Contacts. Then it applies the Where method to those results. Finally, it applies the OrderBy method to the results of the Where method. The c in the Where method refers to the items returned by context.Contacts. The foo in the OrderBy method refers to the IQueryable 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 called query 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 the CommandText 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, called composable 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<Contact>("[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: via EntityClient. 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 to SqlClient, 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 familiar DataReaders, 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

EntityDataReaders 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 an EntityDataReader, 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 saw EntityKey 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 with EntityClient. 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 the Main method.

    NOTE

    You may want to comment out the call to QueryContacts 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 the EntityCommand.

3.7.1. EntityConnection and the Connection String

With other client providers, the connection connects directly to the data store. However, the EntityConnection 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 the app.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 its CommandText. 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 the ExecuteReader method. With other DbDataReaders, 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

DbDataReaders 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, whereas FirstName is the second column and LastName is the third; therefore, you cannot read the Title 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. The String 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 handy LEFT function, because of the way .NET evaluates StartsWith it is not interpreted as LEFT. Instead, you get the following WHERE 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<T> 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) (<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 the EntityContainer class that you will instantiate to perform queries.

  • PEF will be the new name of the connection string in the app.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 called lazy 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 the Title, 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 replacing Select 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 use select 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 <anonymous type>)

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 existing Dim 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 like Dim 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 replacing int with var the benefit is not very obvious. Had that type been MyCustomType<Myothercustomtype<T>>, 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 and var 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 the SELECT 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, the Address 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 the Select 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 in Example 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<DbDataRecord>(queryStr);

Notice that in the Entity SQL string, the keyword VALUE 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 a DbDataRecord 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 in Example 4-6, you'll see that it's OK to use Title 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 a DbDataRecord. 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 a DbDataRecord 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<DbDataRecord>(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 to String types. In this case, because you know the first item will contain a Contact type, you can cast the column to Contact 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 returns DbDataRecords. 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 between Contact and Address. The association provides two navigations—one from Contact to all of its related addresses and one from Address to its related contact.

Navigations Point to a Related End

When working with navigation properties in EntityObjects, 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 an EntityCollection 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 in Chapter 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 the Addresses 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 and EntityCollections, 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 named a 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 simpler a, 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 (from Example 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 an EntityReference 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 a DateTime 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 are Time, 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 the EntityCollection of Addresses related to that Contact.

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 as Street 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 the Contact'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 (see Example 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 the ANY method. You'll need to pass a subquery into EXISTS so that it knows what to search. Look closely at the subquery in Example 4-19. It is querying c.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 (see Example 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 as Max, 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 of FROM 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 against c.Addresses that returns a collection of AddressIDs. You can then COUNT the results of that query, as shown in Example 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 PostalCodes, 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. The ANYELEMENT 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 are ANYELEMENT, 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 and MaxCode as the second. MaxCode is defined by using the Max 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 from Example 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 a JOIN operator as well as GROUPJOIN. Entity SQL provides a variety of options in the JOIN 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's Title. Because there is no association between vOfficeAddresses and Contact, you will need to use JOIN to combine the vOfficeAddresses entity properties with the Title 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 LINQ JOIN.

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 from Contact 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 matching ContactIDs 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 SQL JOIN.

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, a JOIN 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 same JOIN 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 a Contact 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 a JOIN. The query in Example 4-32 uses a nested query (which is highlighted) combined with the FirstOrDefault 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 the FirstOrDefault 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 in Example 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 a Group 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 a GroupBy 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 optional INTO 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 an IQueryable 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 word Key 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 syntax Group 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 properties Max 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 the Group. With LINQ you can continue to use the WHERE 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 the Select does need to go before the Group 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 at Example 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 the GROUP 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 the GROUP BY clause and is used by name in the SELECT.

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 the WHERE clause to filter within a group. In Entity SQL, you can use the HAVING 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, DbDataRecords, or DbDataReaders, 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 of Address 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 the Contact.

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 in DbDataRecords, as opposed to the anonymous types that LINQ returns. However, even in these DbDataRecords, you can still find complete entities and navigate through their associations.

You can express the query in Example 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 DbDataRecords 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 a Contact entity is contained in the second field.

Figure 4-7 shows the first column of one of the DbDataRecords in the results. The item is an Address entity. The second column contains a Contact entity. So, even though it is a DbDataRecord, 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 the Contact 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 in Example 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 as deferred 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 an ObjectQuery 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 of Count will be correct and all of the Address 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 for EntityReference 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 sees Address.Contact as merely a Contact entity, and the Contact class does not have the Load method. It is the ContactReference property that has the knowledge of how to load the related information. Each EntityReference navigation property will have a related property with the word Reference 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 load ContactReference 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 an ObjectQuery. Because context.Contacts is an ObjectQuery, you can use Include even within a LINQ query, as shown in Example 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 for contact, 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 a query 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 of Include. 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 return DbDataRecords.

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 an Include 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<Contact>(str).Include("Addresses");

The same rule applies for projections when using Entity SQL with Include. 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 a JOIN, 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 following ObjectQuery:

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<Address>(querystring)
                   .Include("Contact")

Just as you saw when using Include 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 the Load 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 with Include 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 with Load.

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 called TryGetObjectByKey and TryGetObjectStateEntry. The methods exist in another Entity Framework class, and you will learn more about GetObjectByKey later in this chapter and the rest in Chapter 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 IQueryables or ObjectQuery and you need to dig into those to get at the actual data, which might be entities, anonymous types, or DbDataRecords.

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 an IQueryable 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 though First 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 be Nothing/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 its EntitySet, 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 the EntityClient'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, the ObjectQuery 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 an IQueryable, 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 the ObjectQuery 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 an EntityCollection. Each row in the results of Table 4-3 contains an entity and an EntityCollection. 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
Column 1 Column 2 Column 3 Column 4
Row 1 Mr. John Doe Address entity

Address entity
Row 2 Sr. Pablo Rojas Address entity

Address entity
Row 3 Mrs. Olga Kolnik Address entity

Address entity

Table 4-3. Rows containing an entity and an EntityCollection
Column 1 Column 2
Row 1 Contact entity Address entity

Address entity
Row 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 a DbDataRecord, 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 the EntityObject 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<DbDataRecord>(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<Contact>(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 DbDataRecords can contain nested DbDataRecords, or even nested DbDataReaders, 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 a DbDataRecord 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 a DbDataRecord with one column. The column contains an IExtendedDataRecord, which is a type of a DbDataRecord. 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 a DbDataRecord. There are two columns: the first contains an IExtendedDataRecord with one column for each property of the Contact 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 an IExtendedDataRecord. There is one column for every property of the Contact entity, filled with the relevant data.

  • Query projecting a single simple type using SELECT 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, and EntityClient, 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 called object 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 any EntityObjects that are materialized, the ObjectContext creates an extra object behind the scenes, called an ObjectStateEntry. It will use these ObjectStateEntry 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 in Chapter 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, the ObjectContext 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 the EntityState 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 the ObjectStateEntry.EntityState value was Unchanged at the time of the modification, its value will be set to Modified.


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 separate Insert, 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 the Contacts EntitySet. Remember that context.Contacts is a method that will return an ObjectQuery 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 parameterized Update 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 the FirstName and ModifiedDate properties for the Contact 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 the FirstName 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 the Street 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 single Address that was modified, and only its Street 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 was Unchanged, 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 their EntityState 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. The Modified 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, because ObjectContext 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 to Added. When SaveChanges is called, because the EntityState 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 a Select 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 its AddressID 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 between Contact 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 the ContactID field of the address being inserted into the database.

When the insert completes, not only will the address in memory have its new AddressID value, but like the update in the preceding section, the entity will be refreshed and its EntityState will be set to Unchanged.


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 (ContactID). 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 the Contact class's CreateContact factory method.

NOTE

Recall that the model's code generator creates a factory class for every EntityObject, which uses all of the non-nullable properties as its arguments.

The example then creates a new address in the same manner as with Example 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 to SaveChanges. There are two commands. The first command inserts the new contact and does a Select to return the new contact's ContactID.

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 new ContactID. This command also selects the new address's AddressID 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 a Contact. When DeleteObject is called, the context sets the EntityState of that object's ObjectState Entry to Deleted. When SaveChanges is called, the context notes the Deleted EntityState and constructs a Delete 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 typed EntitySet name (which includes the name of the EntityContainer, PEF), the name of the property that is the EntityKey, and the value of the key. Therefore, the EntityKey is for a Contact whose ContactID is 438.

The EntityKey is passed into the GetObjectByKey method, which will first inspect the existing EntityObjects 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 the DeleteObject 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 the Delete 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 the ContactID 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 to SaveChanges. 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

<Function Name="CustomersbyState" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="statecode" Type="nchar" Mode="In" />
</Function>

<Function Name="DeleteContact" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="contactid" Type="int" Mode="In" />
</Function>

<Function Name="InsertContact" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="firstname" Type="nchar" Mode="In" />
  <Parameter Name="lastname" Type="nchar" Mode="In" />
  <Parameter Name="title" Type="nchar" Mode="In" />
</Function>

<Function Name="UpdateContact" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="contactid" Type="int" Mode="In" />
  <Parameter Name="firstname" Type="nchar" Mode="In" />
  <Parameter Name="lastname" Type="nchar" Mode="In" />
  <Parameter Name="title" Type="nchar" Mode="In" />
</Function>

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 are InOut, 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 the CustomersbyState 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 term CRUD 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 "<Add ResultBinding>". 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

<EntitySetMapping Name="Contacts">

  <!-- PROPERTY MAPPINGS-->
  <EntityTypeMapping TypeName="IsTypeOf(ProgrammingEFDB1Model.Contact)">
    <MappingFragment StoreEntitySet="Contact">
      <ScalarProperty Name="ContactID" ColumnName="ContactID" />
      <ScalarProperty Name="FirstName" ColumnName="FirstName" />
      <ScalarProperty Name="LastName" ColumnName="LastName" />
      <ScalarProperty Name="Title" ColumnName="Title" />
      <ScalarProperty Name="AddDate" ColumnName="AddDate" />
      <ScalarProperty Name="ModifiedDate" ColumnName="ModifiedDate" />
    </MappingFragment>
  </EntityTypeMapping>

  <!-- FUNCTION MAPPINGS-->
  <EntityTypeMapping TypeName="ProgrammingEFDB1Model.Contact">
    <ModificationFunctionMapping>

      <InsertFunction
          FunctionName="ProgrammingEFDB1Model.Store.InsertContact">
        <ScalarProperty Name="FirstName" ParameterName="firstname" />
        <ScalarProperty Name="Title" ParameterName="title" />
        <ScalarProperty Name="LastName" ParameterName="lastname" />
      </InsertFunction>

      <UpdateFunction
          FunctionName="ProgrammingEFDB1Model.Store.UpdateContact">
        <ScalarProperty Name="Title" ParameterName="title"
                        Version="Current" />
        <ScalarProperty Name="LastName" ParameterName="lastname"
                        Version="Current" />
        <ScalarProperty Name="FirstName" ParameterName="firstname"
                        Version="Current" />
        <ScalarProperty Name="ContactID" ParameterName="contactid"
                        Version="Current" />
      </UpdateFunction>

      <DeleteFunction
          FunctionName="ProgrammingEFDB1Model.Store.DeleteContact">
        <ScalarProperty Name="ContactID" ParameterName="contactid" />
      </DeleteFunction>

    </ModificationFunctionMapping>
  </EntityTypeMapping>
</EntitySetMapping>

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. 'Apartment 42' is the new value of Street2 and 2260 is the AddressID of the address to update.

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 the EntityContainer element:

<FunctionImport Name="CustomersbyState" EntitySet="Contacts"
                ReturnType="Collection(ProgrammingEFDB1Model.Contact)">
  <Parameter Name="statecode" Mode="In" Type="String" />
</FunctionImport>

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's EntitySet mappings, but rather stands alone:

<FunctionImportMapping
   FunctionImportName="CustomersbyState"
   FunctionName="ProgrammingEFDB1Model.Store.CustomersbyState" />

6.5.1. Using the CustomersbyState Function

After you map the function, a new method is automatically generated for the ProgrammingEFDB1Entities 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<Contact>), 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's EntityType section for that table, as well as its EntitySet. 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 like Figure 7-2.

Figure 7-2. The initial model created from the BreakAway database


7.3. Inspecting and Cleaning Up a New Model

The first thing you should always do with a newly generated model is make sure the Entity names and EntitySet names make sense. The Entity names should be singular (Contact, Address, Customer, etc.) and the EntitySet names should be the plural form of the Entity names (Contacts, Addresses, Customers, etc.). The word equipment poses a challenge since the singular and plural versions are the same. The rule that the Designer follows when you create new entities in the Designer is to append the word Set to the name of the new entity, so we'll follow that convention and use EquipmentSet in this model. Although you can edit the Entity names right in the Designer, you can edit the EntitySet names only in the Properties window. You may find it more efficient to edit both in the Properties window.

NOTE

There are three ways to get an entity's properties to display in the Properties window:

  • Select the entity in the Designer.

  • Select the entity in the Model Browser.

  • Select the entity from the drop-down list of objects in the Properties window.

7.3.1. Modifying the Names of Entities

The database has a table named Events that refers to the trips that BreakAway schedules. The original name of this table was an unfortunate choice because the .NET word Event is a reserved keyword in both VB and C#. This normally isn't a problem, but if you were to use Event as the entity name, the EntityObject named Event would create a conflict. With the EDM, you can rename the entity without having to rename the database table. The term Trip also makes more sense, so renaming this will be a bonus. As you fix the names of the Entity objects and EntitySets, rename the Events entity to Trip. The entity will still map back to the Events table, so everything will stay in sync.

You should also change the EventID property name to TripID so that as you are working with objects, you won't have the confusion of an entity whose ID property doesn't match the name of the entity.

Do the same for the entity named Locations, changing it to Destination. You'll need to change the ID and Name properties as well, to DestinationID and DestinationName.

7.3.2. Collisions Between Property Names and Entity Names

The Designer will pop up an error message when you try to change the names of two entities. The first is CustomerTypes and the second is Activities. The problem is that the entities contain properties with the same names you would like the entities to have.

Before you can change the CustomerTypes entity name to CustomerType, you will need to rename its CustomerType property. Change the name to CustomerTypeName. Then change the Activity property of the Activities entity to ActivityName. You can make these changes in the Designer or in the Properties window.

You may also notice that the Equipment entity has a property called Equipment1. That is because the wizard saw that the property name matched the entity name and therefore appended a "1" to the property. Change that property to EquipmentName.

Now you will be able to change the names of the entities without problems.

Table 7-1 shows the names that result. If you make these same changes, the queries and other code that follow will work properly.

Table 7-1. Entity and EntitySet name changes
Entity | EntitySet
Activity *Change the Activity property to ActivityName | Activities
Address | Addresses
Contact | Contacts
Customer | Customers
CustomerType *Change the CustomerType property to CustomerTypeName | CustomerTypes
Equipment *Change the Equipment1 property to EquipmentName | EquipmentSet
Trip (was Events) | Trips (was Events)
Destination (was Locations) | Destinations
Lodging | Lodgings
Payment | Payments
Reservation | Reservations
vOfficeAddress | vOfficeAddresses

You'll notice some bad behavior by the Designer as you rename some of the entities. When you're renaming the EntitySets, all is well, but when you rename the entity, the Designer will automatically rename the EntitySet using its pattern for new entities, which is to append the word Set onto the end.

This means that after changing the entity names, you'll need to reedit the EntitySet names. Thankfully in this model, this will occur with only eight entities.


7.4. Cleaning Up Navigation Property Names

There is still a bit of cleaning up to do. Next, you will fix the names of the navigation properties so that they make sense when you are building queries and working with the objects returned from those queries.

Navigation property names should be plural when pointing to a collection (e.g., reservations that a customer has made) and singular when pointing to a reference (the customer who made a reservation).

The Customer entity has some odd navigation properties: Locations and Locations1, and Activities and Activities1. The Activity and Location entities each have Customers and Customers1 in the Activity entity and the Destination entity. Fix the other navigation properties as shown in Table 7-2, and then we'll come back to these.

Table 7-2. Changes made to navigation names to better represent their multiplicity
Entity | Original navigation property name | Fixed navigation property name | Reason
Contact | Address | Addresses | 1:* Navigation
Customers | Customer | 1:1 Navigation
Lodging | Lodgings | 1:* Navigation
Customer | CustomerTypes | CustomerType | *:1 Navigation
Trip | Destinations | Destination | *:1 Navigation and entity name changed
Activity | Events | Trips | Entity name changed
Destination | Lodging | Lodgings | 1:* Navigation
Events | Trips | Entity name changed
Lodging | Destinations | Destination | *:1 Navigation and entity name changed
Events | Trips | Entity name changed
Payment | Reservations | Reservation | *:1 Navigation
Reservation | Customers | Customer | *:1 Navigation
Events | Trip | *:1 Navigation and entity name changed

As you write queries against this model going forward, the queries will be more logical. Rather than coding against Reservation.Customers, when there will always be just one customer, you will be able to use Reservation.Customer.

7.4.1. Entities with Multiple Relationships to a Single Entity

Now we can go back to work on the Customer entity. It has those two funny pairs of navigation properties: Activities and Activities1, and Locations and Locations1. These property pairs will make more sense if you check the Customers table in the database, shown in Figure 7-3. BreakAway keeps track of each customer's first and second preferences for destination and activity. This is not an uncommon database scenario, but the wizard will always create the names in this way, so let's see how to add clarity to these names.

Figure 7-3. The Customer table in the database, with two columns that relate to the Destination table (PrimaryDesintation—a database typo that BreakAway developers have had to live with for years; and SecondaryDestination) and two columns that point to the Activities table

The navigation property names are derived simply from the name of the table on the other end of the relationship. Since there are two associations to one entity, the wizard appended a "1" to the second navigation property.

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 Customer.Activities refer to the PrimaryActivity or the SecondaryActivity?

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 term Table 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


7.6. Working with Many-to-Many Relationships

There is one more thing to point out about this model: the two many-to-many relationships. BreakAway Adventures' database keeps track of which type of equipment is needed for which activities. It also tracks which activities will be available on which events ("trips" in the model). To accomplish this, an ActivityEquipment join table between Equipment and Activities defines many-to-many relationships between equipment and activities, and an EventActivities join table between Activities and Events defines many-to-many relationships between activities and events, as shown in Figure 7-9.

Figure 7-9. The database join tables, EventActivities and ActivityEquipment

These tables did not appear in the model as entities. The EDM has the ability to represent many-to-many relationships while hiding the join in the mappings. But it can do this only when the join table has just the relevant keys and no extraneous fields. These two tables meet that criterion, as they have only the IDs of the items they are joining. If the join tables had additional properties, such as DateCreated, the EDM would have created entities for them.

Instead, the joins are controlled in mappings; in the conceptual layer the relationships are expressed as navigation properties. Example 7-1 shows the mapping for the EventActivities association in the XML file. The mapping identifies the EventActivities table as the target of the mapping, and then shows its ActivityID field wired up to the ActivityID field of the EventActivities table. Meanwhile, its EventID field is wired up to the EventID field of the Events table.

Example 7-1. Many-to-many association mapping

<AssociationSetMapping Name="EventActivities"
                       TypeName="BreakAway.EventActivities"
                       StoreEntitySet="EventActivities">
  <EndProperty Name="Activities">
    <ScalarProperty Name="ActivityID" ColumnName="ActivityID" />
  </EndProperty>
  <EndProperty Name="Events">
    <ScalarProperty Name="EventID" ColumnName="EventID" />
  </EndProperty>
</AssociationSetMapping>

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 to BAEntities. 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, the ConnectionString 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 to BAModel.

7.7.2. The Impact of Compiling a Project on an EDMX File

When a project containing an EDMX is compiled, the compiler extracts the StorageModels, 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 the metadata 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 DataSets 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.


8.1. Data Binding with Windows Forms Applications

To demonstrate data binding of an EDM to a Windows form, let's build a small application to let you view customers and their reservations as well as edit the customers and add new reservations. Figure 8-1 shows this form with a DataGridView to work with the customers, a ListBox control for reservations, and a ComboBox for selecting new trips to add to a customer's reservations.

NOTE

This chapter will not presume that you are familiar with Windows Forms or WPF data-binding techniques in the IDE. So, you'll get a step-by-step walkthrough to be sure that the UI tasks don't trip you up.

You'll be building up the form in stages, adding a little more functionality in each stage and then testing what you've built so far.

Figure 8-1. The Windows Forms application you'll be building

8.1.1. Creating a Windows Forms Application

The first task is to create a Windows Forms project. In our example, we'll use the BreakAwayModel project you created in Chapter 7. You should add this new Windows Forms project into the same solution that contains the BreakAwayModel project. In that way, you can easily reference the model and make changes to it as needed.

NOTE

If you did not follow the walkthroughs in Chapter 7, you can download the completed BreakAwayModel project from the Downloads page of the book's website.

  1. Add a new Windows Forms Application project to the solution and give it the name BreakAwayWinForms.

    NOTE

    The next three steps will be common for any application that needs to use an EDM that is in a separate assembly.

  2. Add a reference to the BreakAwayModel project. To do this, right-click the BreakAwayWinForms project in the Solution Explorer and select Add Reference. In the Add Reference dialog, select the Projects tab, then select BreakAwayModel and click OK. This will allow the new application to use everything in the model as well as the generated entity classes.

  3. Add a reference to System.Data.Entity under the .NET tab of Add References.

    When you created the model in the BreakAwayModel project, the Entity Data Model Wizard automatically pulled in the necessary references to the Entity Framework APIs. Your new application will need this particular reference as well, which is why you need to add them manually.

  4. Copy the app.config file from the BreakAwayModel project into the new Windows Forms project.

Because you are using the model in the BreakAwayModel project, this Windows Forms project will need access to the entity connection string. By default, no app.config file is created for a Windows Forms project. If you had already created an app.config file, you only need to copy the BAEntities connection string element from the model's app.config to this project's app.config. In this case, since the app.config doesn't exist yet, you can cheat by just copying the whole file.

8.1.2. Using Windows Forms Data Sources to Help with Data Binding

Data sources have been a feature of Windows Forms since Visual Studio 2005 and are a very convenient way to perform data binding. They provide a bridge between your data and the controls to which you are binding your data. There are three types of data sources: those that bind directly to a database, those that bind to a service, and those that bind to objects.

In this application, you will be creating data sources that bind to objects—specifically, to entity classes that were dynamically generated by an EDM. An Object data source won't bind to the entire EntityContainer; it will bind to only one individual class. Additionally, although a data source that is derived from a database will trigger the interaction with the database, an Object data source will not. It provides the schema of the classes to the controls to which you are binding data, and it provides the ability to interact with the objects. You will have to write the actual code that populates the data source at runtime.

Using data sources is a great example of how the EDM and the Entity Framework work seamlessly with existing tools in Visual Studio.

NOTE

This is not to say that the Entity Framework works with all of the existing features of Visual Studio. You will find that some gaps still exist, such as the inability of the Microsoft Report control to work with hierarchical data that comes from the EDM.

In the following walkthrough you will create an Object data source that binds to the Customer entity. Then, using the properties that are exposed through the Data Sources window, you will add controls to the form so that the controls are automatically bound to these properties. After adding a simple query to the form's code, you will be able to run the application and scroll through the customers' data. With a few more minor changes to the form, you will also be able to edit the data.

8.1.3. Creating an Object Data Source for a Customer Entity

The first step is to create the data source that will help you to create the data-bound controls on the form. The Object data source you will need is for the Customer class. Here's how to create it:

  1. From the Visual Studio menu, select the Data menu item, and then select Show Data Sources from that menu's drop-down, to open the Data Sources window (Figure 8-2).

Figure 8-2. The Data Sources window before the new data source has been created

  1. Click the Add New Data Source hyperlink to open the Data Source Configuration Wizard.

  2. Select Object in the Choose Data Source Type window, and then click Next.

  3. The next window will present you with the available assemblies in the current solution. Expand the BreakAwayModel assembly to reveal the BAGA namespace, and then expand that to reveal the entity classes, as shown in Figure 8-3.

Figure 8-3. The classes from your model, available for creating a data source

  1. Select Customer and then click Next.

  2. Click Finish.

The Customer data source will now display in the Data Sources window. Expand the Customer data source to see its properties. Notice that the navigation properties are there, including Reservations with its properties, as shown in Figure 8-4. Entity Framework classes are built to expose their navigation properties for data binding.

Figure 8-4. The Customer data source, which includes its navigation properties

What's interesting about the Customer entity and class is that the most critical information—the customer's name, address, reservations, and preferences—is not available in its scalar properties. You need to navigate to other entities to get this data. Although the Customer entity represents contacts who are customers, it depends on the related Contact entity to provide name information and to give you access to the contact's addresses. Customer relies on its relationship to Reservations to supply details about what makes each customer a customer—all of the trips they have taken or are planning to take. Even their Activity and Destination preferences are navigation properties.

If you were to create a DataGridView from the Customer data source, the only properties that would display by default are the scalar properties. Instead, you can leverage the related contact and reservations details to build a more useful form.

8.1.4. Getting the Entity's Details onto the Form

Data sources allow for some very convenient drag-and-drop operations that make it easy to specify which properties of an object are displayed on a Windows form. You don't want to drag the entire Customer object, because, by default, that will result in a DataGridView with the scalar properties and the IDs of the entity reference properties. Instead, you'll want to drag the properties, and you can do those only one at a time.

NOTE

Later in this chapter, you'll see the problem of the default DataGridView when you data-bind to the EntityCollection. But if you are curious now, nothing is stopping you from dragging the entire Customer object onto the form so that you can see the effect.

Since in this example we'll want most of the customer's contact details on the form, you can select Contact in the Data Sources window and drag it onto the form. Because Contact is a property, all of its details will come over to the form at once. The impact of dragging the properties onto the form is that a new navigation toolbar will be created, as well as the appropriate controls for the various properties—TextBox controls for the integers and strings, and DateTimePicker controls for the date properties. Additionally, on the perimeter of the design window, you'll see that a CustomerBindingSource and a CustomerBindingNavigator were added. These are components that work hand in hand with the data sources in Windows Forms. The BindingSource will coordinate the form fields with the data. The BindingNavigator coordinates the actions of the toolbar (navigation, inserts, deletes, and updates) with the BindingSource. Remember that these are not Entity Framework features, but standard functionality in Windows Forms.

Next, expand the PrimaryActivity property and drag its ActivityName to the form. You'll need to modify the label so that it reads "Primary Activity". Do the same for the other preferences.

Finally, add the Customer.InitialDate field. This represents the first date the contact became a customer, which is useful in case the person was on a mailing list for a while before finally becoming a customer.

If you care to line up and organize the fields on the form, your form will look something like Figure 8-5 after you have added these fields.

Figure 8-5. The form with the first bits of data binding

8.1.5. Adding Code to Perform the EDM Query

You'll need to execute a query when the form loads to retrieve the customers along with their related data from the database. You will bind these query results with the BindingSourceControl that was created for the Customer data source.

You'll get to take advantage of eager loading, which you learned about in Chapter 4, by using a number of Include methods in this query. This is because the form relies on five navigation properties.

In the form's Load event, add the code from Example 8-1. I will explain the use of ObjectResult and Execute shortly.

Example 8-1. Querying for customers in the form load

Dim context = New BAGA.BAEntities
Dim customers As ObjectResult(Of BAGA.Customer) = _
    context.Customers _
    .Include("Contact") _
    .Include("PrimaryActivity") _
    .Include("SecondaryActivity") _
    .Include("PrimaryDestination") _
    .Include("SecondaryDestination") _
    .Include("Reservations.Trip.Destination") _
    .Execute(Objects.MergeOption.AppendOnly)
CustomerBindingSource.DataSource = customers

var context = new BAGA.BAEntities();
ObjectResult<BAGA.Customer> customers=
    context.Customers.Include("Contact")
    .Include("PrimaryActivity")
    .Include("SecondaryActivity")
    .Include("PrimaryDestination")
    .Include("SecondaryDestination")
    .Include("Reservations.Trip.Destination")
    .Execute(Objects.MergeOption.AppendOnly);
customerBindingSource.DataSource = customers;

The query is written to ensure that all of the necessary related information is retrieved from the data store. Each Customer in the results will be a graph shaped as shown in Figure 8-6.

Figure 8-6. The Customer graph

This particular query is not designed for efficiency, but rather to give you an idea of how the Include method works and how the data binding implements related data. Notice that the query does not even bother to filter the data. The SQL query that results and the amount of data returned may make you gasp. So, although it's very important to be aware that this is not a best practice, it's a handy query for this lesson based on what you've learned so far.

The query contains a lot of Include methods. Most of the related entities are small. Activity and Destination have only an ID field and a Name field. Reservations and Trip don't have a lot of fields, either. But a lot of redundant data will be sent back to the application. For example, each customer who has Madagascar selected as her primary or secondary destination will cause that row of data to be transmitted back to the application. If 100 people favor that locale, 100 copies of that row will be returned.

As Object Services materializes objects from those rows, it will recognize the redundancy and will not create multiple copies of that particular object in memory, so on the application side the query results will be efficient.

Later in this chapter, we'll look at more efficient ways to return the related data without this redundancy.

8.1.6. Testing the Sample

Go ahead and run what you've built so far. As you use the navigation toolbar to move from one customer to another, you'll be able to see that all of the navigation properties automatically change as well (see Figure 8-7).

Figure 8-7. A customer's details on display

8.1.7. Entities, BindingSources, and a Very Important Rule

When you bind query results to a BindingSource, the BindingSource will act as an agent to coordinate among the entities, the fields, and the navigation toolbar. The BindingSource will update the entities when a change is made in the form's fields. In this example, when it's time to have the Entity Framework send the entity changes to the data store, all of the change information will be available.

But the code in Example 8-1 follows a very specific rule: the Customer query uses the Execute method.

I introduced the Execute method in Chapter 3, with a teaser about using it in data binding. Execute returns an ObjectResult(Of T); in our example, it returns an ObjectResult(Of Customer).

NOTE

Although most of the code samples you have seen so far have taken advantage of implicitly typed variables, the customers variable was explicitly typed in this code so that you would be clear about the fact that the type is an ObjectResult.

ObjectResult is an enumerable collection that has special features to enhance data binding and the binding's ability to interact with the ObjectContext. Although you may find that some updates and additions will work properly even if you returned a List(Of Customer), you may also get unexpected results. One bad result that you can count on is that when you delete an item, no delete command will be sent to the database. With an ObjectResult, the delete will work as expected.

NOTE

As you dig deeper into Object Services in the next chapter, you'll have a better understanding of why the delete command won't reach the database in this scenario.

Execute takes a MergeOption parameter. MergeOption specifies what to do if the query results already exist in the context. It's possible to execute many queries against the same context (or even to execute one query multiple times). If duplicate data is pulled down from the data store, you can control how those duplicates are handled. In this case, the AppendOnly option tells the context only to add entities that don't already exist in the context. In this way, you won't have to worry about overwriting changes you have made. You'll learn more about MergeOptions in Chapter 9.

There's also another caveat with using ObjectResults, which you will learn about when you encounter it in the upcoming WPF sample.

If you bind a LINQ query or an ObjectQuery directly to a BindingSource.DataSource property, you'll get your data, but the query will execute every single time the user moves to a new customer. Imagine querying for 100 customers every time the user moves to a new customer.

If you work with highly concurrent data and want to be sure that each record has fresh data, you would be better off using a query that returns a single customer. Then you can bind the query directly to the BindingSource.DataSource property and have the query automatically execute each time the user moves to another Customer record.

8.1.8. Adding the Related EntityCollection to the Form

Now it's time to get the reservations onto the form. The Reservation entity presents the same problem as the Customer entity in that most of the best information is in its navigation property, Trip. If you were to drag the Reservations property onto the form, the default control, DataGridView, will display the reservations as shown in Figure 8-8 when the application is run. This is the same problem that I alluded to earlier regarding dragging the entire Customer object onto the form.

Figure 8-8. Default grid for the Reservations navigation property

The control is not able to work out the navigations to the Reservation's Customer and Trip references and therefore displays only the type name for each. Even if you edited the DataPropertyName property of the Trip column to be Trip.StartDate, Windows Forms will not be able to navigate into Trip to find its StartDate. This problem is not specific to the Entity Framework, but a result of how the DataGridView functions.

8.1.8.1. Displaying the properties of reference properties in the grid

There is a way to display properties of related objects in DataGridView, and you can take advantage of that here. The grid will be useful if it displays the reservation date, the start and end dates of the trip, and the destination of the trip. Destination comes from a navigation property of the Trip entity, so first we'll need to modify the columns in the grid.

Edit the grid's columns. You can make the ReservationID's Visible property false, and then remove the TimeStamp, Customer, Trim, and Payments columns. Add three new TextBox columns named tripStartColumn, tripEndColumn, and destinationColumn. Make sure their ReadOnly property is True.

The trick to displaying the navigation properties is in the code. You must override the individual cells as the grid is being rendered for display, and the place to do this is in the DataGridView.RowPrePaint event. Example 8-2 shows how to do this.

NOTE

In C#, you can access the DataGridView.RowPrePaint event from the Events page of the grid's Properties window. In VB, you can do the same or access the event in the code window by choosing ReservationsDataGridView from the Class Name drop-down and then RowPrePaint from the Method Name drop- down.

Example 8-2. Forcing the DataGridView to display navigation properties

Private Sub ReservationsDataGridView_RowPrePaint _
 (ByVal sender As Object, ByVal e As _
  System.Windows.Forms.DataGridViewRowPrePaintEventArgs) _
  Handles ReservationsDataGridView.RowPrePaint

 'get a reference to the reservation currently being painted
  Dim res = _
      CType(ReservationsBindingSource.Item(e.RowIndex),BAGA.Reservation)
 'push the reservations's navigation properties into the correct cells
  With ReservationsDataGridView
    .Rows(e.RowIndex).Cells(tripStartColumn.Index).Value = _
     res.Trip.StartDate.ToShortDateString
    .Rows(e.RowIndex).Cells(tripEndColumn.Index).Value = _
      res.Trip.EndDate.ToShortDateString
    .Rows(e.RowIndex).Cells(destinationColumn.Index).Value = _
      res.Trip.Destination.DestinationName
  End With
End Sub

private void reservationsDataGridView_RowPrePaint
 (object sender, DataGridViewRowPrePaintEventArgs e)
{

 //get a reference to the reservation currently being painted
  var res = (BAGA.Reservation)(reservationsBindingSource[e.RowIndex]);

 //push the reservations's navigation properties into the correct cells
  var grid = reservationsDataGridView;
  grid.Rows[e.RowIndex].Cells[tripStartColumn.Index].Value =
   res.Trip.StartDate.ToShortDateString();
  grid.Rows[e.RowIndex].Cells[tripEndColumn.Index].Value =
   res.Trip.EndDate.ToShortDateString();
  grid.Rows[e.RowIndex].Cells[destinationColumn.Index].Value =
   res.Trip.Destination.DestinationName;
}

8.1.8.2. Testing the new version of the sample

Notice that you don't have to add any additional querying or binding code into the form load. The fact that you have already bound the customers to the BindingSource is enough. The BindingSource will work out all of the relationships. In the form, as you navigate from one customer to the next, that customer's reservations will be displayed in the grid (see Figure 8-9).

Figure 8-9. The sample with reservations added to the form

The trip start and end dates were formatted in the RowPrePaint event using ToShortDateString. To format the ReservationDate column, you'll need an additional line of code in the Form Load event. After renaming the column to resDateColumn, the formatting code is as follows:

ReservationsDataGridView _
.Columns(resDateColum.Index) _
.DefaultCellStyle.Format = "d"

reservationsDataGridView
.Columns[resDateColumn.Index]
.DefaultCellStyle.Format = "d";

NOTE

Because of the convenient but inefficient query, all of the Customer objects with all of their reservations and related trip data are in memory. So, in this example, the application does not need to return to the database to retrieve additional data as you move from one customer to the next. In a properly designed application, you will need to be more diligent about retrieving only the data that the user will need, and you'll need to be considerate about balancing the client-side resources with the trips to the server and the amount of data being transmitted based on your scenario.

8.1.9. Allowing the User to Edit the Data

So far, you have been using the form to view data. What about adding or editing data? BindingSource supports editing, but you'll need to make a few small modifications to the form and the code to get this functionality. We'll start with editing, and in the next section you'll enable adding.

8.1.9.1. Enabling the Save button

By default, the navigation toolbar disables the Save button when the toolbar is first created. Right-click the button and check the Enabled property in the context menu.

Before you can add the method to save data, you have to make an important change in the existing code. Currently, you are instantiating the BAEntities ObjectContext in the Form Load event. The context is not available outside the event.

When you query data with the context, by default the entities that result are managed by the context that keeps track of changes made to those entities. You can then use the ObjectContext.SaveChanges method to save those changes back to the data store. Although you will learn much more about this later in the book, here you'll need to be aware of the fact that only the context that is tracking the changes is able to save them. You can't instantiate a new context and expect it to save changes to the entities that you're working with in the form. It won't know anything about them.

Therefore, it is important to be sure that when you call SaveChanges, you are working with the same context you used to retrieve the data.

To do this in the form, you need to declare the context in the form declarations, not within a method. In this way, all of the form's methods can work with the same context and you will be able to call SaveChanges in the Click event of the Save button.

8.1.9.2. Changing the scope of the ObjectContext

Just beneath the line of code that declares the form, add the following code to declare the context:

Public Class Form1
  Private context As BAGA.BAEntities

public partial class Form1 : Form
{
  BAGA.BAEntities context;

In the form's Load event, change the code that declared and instantiated the context so that it instantiates only the context, as shown in the following code snippet:

context = New BAGA.BAEntities

context = new BAGA.BAEntities();

8.1.9.3. Adding the save logic to the Save button

In the form's Design view, double-click the Save button to get to its Click event handler. Then add the SaveChanges code into the Click event, as shown in the following code:

context.SaveChanges

context.SaveChanges();

That is the complete code for saving all of the entity changes! There is no connection code, no need to build commands, and no need to worry about what entities are being saved or what types of changes are being made. Object Services reads all of the change information that it has kept track of for the entities that it is managing, and works out the proper commands (Insert, Update, or Delete) and then executes them.

If the model had any function mapping, Object Services would use stored procedures to perform the changes on the entities that have functions mapped to them.

8.1.9.4. Test editing a customer

In its current state, you can test edit a customer. Another tweak is necessary before you can edit the navigation properties, though, and yet another before you can add a new customer. So for now, try editing and saving the name fields of a customer.

8.1.10. Editing the Navigation Properties (and Trimming Down the Query)

The next stage of building up the functionality in this form is to make it possible to edit the other navigation properties.

You are already able to edit the Contact navigation property because it has a one-to-one relationship with the customer. It is essentially an extension of the customer. The preference properties—PrimaryActivity, among others—are values selected from a list of possible items. You'll need two things to be able to change the selections. You will need access to a complete list of the items (activities and destinations). Therefore, the first thing you will need is queries in the code, which will retrieve these lists. The second thing you'll need is some type of selection control, for example, a drop-down list, on the form to display the lists.

8.1.10.1. Reorganizing data retrieval

Currently, the only activities and destinations in memory are those that are attached to the customers, and this may not even be all of the available options.

Instead, we'll need the full lists, so we'll need a new query to retrieve all of the activities and another for all of the destinations.

This will add a big benefit to the application. When the activities and destinations are queried, their objects are stored in memory. More specifically, they will be managed by the ObjectContext. This means it won't be necessary to include the activity and destination data in the Customers query. When the customers are queried, they will still have the reference EntityKeys even if they don't have the actual related entities. Figure 8-10 shows the PrimaryActivity and PrimaryActivityReference properties of a Customer that was queried without any of its related data.

Figure 8-10. PrimaryActivity and PrimaryActivityReference properties of a Customer that was queried without any of its related data

When the context creates the Customer, if the Activity object with that same EntityKey is already in the context, the two will be hooked up. The same happens if you were to query the customers first and then the activities.

So, now you can add the new queries and trim down the Customers query so that it doesn't pull all of that extra data out of the database and over to the application. The new queries will be method queries so that you can keep your various querying skills tuned up.

At the same time, we'll create two new form-level variables to contain the activities and destinations.

Add the new activities and destinations variables to the form declarations, as shown in Example 8-3.

Example 8-3. Adding variables to hold the new lists

Public Class Form1
  Private context As BAGA.BAEntities
  Private activities As List(Of BAGA.Activity)
  Private destinations As List(Of BAGA.Destination)

public partial class Form1 : Form
{
  BAGA.BAEntities context;
  List<BAGA.Activity> activities;
  List<BAGA.Destination> destinations;

Next, add the new queries shown in Example 8-4 into the form load and modify the existing Customers query to remove the extraneous navigations. Notice that Destination is also removed from the Reservations.Trip navigation path. Trip will also be able to find its related destinations in the context after they are created from the Destinations query.

Example 8-4. Querying for the list data

Private Sub Form1_Load _
 (ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

  context = New BAGA.BAEntities
  activities =  context.Activities _
                .OrderBy(Function(a) a.ActivityName).ToList
  destinations = context.Destinations _
                 .OrderBy(Function(d) d.DestinationName).ToList
  Dim customers = context.Customers.Include("Contact") _
                  .Include("Reservations.Trip") _
                  .Execute(Objects.MergeOption.AppendOnly)

private void Form1_Load(object sender, EventArgs e)
{
  context = new BAGA.BAEntities();
  activities = context.Activities
               .OrderBy((a) => a.ActivityName).ToList();
  destinations = context.Destinations
                 .OrderBy((d) => d.DestinationName).ToList();
  var customers = context.Customers.Include("Contact")
                  .Include("Reservations.Trip")
                  .Execute(Objects.MergeOption.AppendOnly);

NOTE

You can run the application again if you want to see that all of the reference properties are still intact.

Notice that the example uses ToList on the new queries instead of Execute. This data will be used only for pick lists and will not be edited directly, so you don't have to worry about the BindingSource being able to pass along change information for activities and destinations to the context.

8.1.10.2. Replacing the navigation property TextBoxes with ComboBoxes

Now that the data for the lists exists, you can change the controls for PrimaryActivity and the other navigation properties to ComboBoxes so that it will be possible to edit a customer's preferences.

You can bind the ComboBox controls in code or in the UI. Since there are four properties to change, let's do both methods so that you can learn each one.

Replace the TextBox controls for the four target properties with ComboBox controls, giving them names to help you differentiate them. The FillCombos method in Example 8-5 performs the standard bindings for a ComboBox and additionally binds them to the Customer's PrimaryActivity and PrimaryDestination navigation properties. Notice that the first argument for the new binding is SelectedItem. You may be more familiar with using Text in that argument. SelectedItem will cause the control to read the entire Activity object attached to the Customer and will work out how to match it up with the items in the ComboBox.

Example 8-5. Code for filling two of the ComboBoxes

Private Sub FillCombos()
  With PrimaryActivityComboBox
    .DisplayMember = "ActivityName"
    .ValueMember = "ActivityID"
    .DataSource = activities
    .DataBindings.Add _
      (New Binding("SelectedItem", CustomerBindingSource, _
                  "PrimaryActivity", True))
  End With
  With PrimaryDestinationComboBox
    .DisplayMember = "DestinationName"
    .ValueMember = "DestinationID"
    .DataSource = destinations
    .DataBindings.Add _
      (New Binding("SelectedItem", CustomerBindingSource, _
                  "PrimaryDestination", True))
  End With
End Sub

private void FillCombos()
{
  PrimaryActivityComboBox.DisplayMember = "ActivityName";
  PrimaryActivityComboBox.ValueMember = "ActivityID";
  PrimaryActivityComboBox.DataSource = activities;
  PrimaryActivityComboBox.DataBindings.Add
   (new Binding("SelectedItem",customerBindingSource,
               "PrimaryActivity", true));

  PrimaryDestinationComboBox.DisplayMember = "DestinationName";
  PrimaryDestinationComboBox.ValueMember = "DestinationID";
  PrimaryDestinationComboBox.DataSource = destinations;
  PrimaryDestinationComboBox.DataBindings.Add
   (new Binding("SelectedItem",customerBindingSource,
               "PrimaryDestination", true));

Run the application again if you want to see how the ComboBoxes have been populated so far.

The other ComboBoxes will be bound in the UI. To do this, you will need to create one new Object data source for the BAGA.Activity class and one for the BAGA.Destination class. Then, in the form load, you will bind those new DataSources to the list variables, as shown in the following code:

ActivityBindingSource.DataSource = activities
DestinationBindingSource.DataSource = destinations

customerBindingSource.DataSource = customers;
destinationBindingSource.DataSource = destinations;

Using the ComboBox Tasks window, you can fill out three of the four properties for each ComboBox. Figure 8-11 shows the combo properties for the SecondaryActivity navigation property.

Figure 8-11. Binding some of the properties through the ComboBox Tasks window

The last property is not filled because you will need to set the SelectedItem property, not the SelectedValue property. Do this in the Properties window, as shown in Figure 8-12.

Figure 8-12. Binding the ComboBox selection using SelectedItem, which is available in the ComboBox Properties window

8.1.10.3. Testing the sample again

Now you can view, edit, and save these properties along with the rest of the customer data, as shown in Figure 8-13.

Figure 8-13. Editing the navigation properties with pick lists

8.1.11. Adding New Customers

We'll take on one last task in this Windows form before moving on: adding new customers.

The data sources don't handle the related entities quite as seamlessly when adding as they do when editing, so we'll have to add a little bit of code to make this work.

When you click the plus sign icon to add a new customer, a new customer is added to the CustomerBindingSource. But you also need a new Contact entity to be created at the same time. The BindingSource has an AddingNew event, but this occurs before the new entity is created. The next event to fire is CurrentChanged as the BindingSource moves its pointer to the newly created Customer. In the CurrentChanged event, you can add the new contact and set any other properties that are necessary.

Here you will have your first opportunity to see how to create new entities in code, to create a relationship, and to be sure the new entity is being managed by the context.

8.1.11.1. From toolbar to BindingSource to context

We've established that clicking the plus sign icon adds a new Customer to the BindingSource, and that the CurrentChanged event is your first opportunity to work with the new Customer entity. But the CurrentChanged event is hit anytime the BindingSource points to a different Customer. You'll need a way to discern the newly added Customer from those that already existed in the BindingSource.

One way might be to check the Customer's ContactID, because it will not have been created yet. But if the user has added a number of customers prior to saving, ContactID=0 will not necessarily mean that the user just clicked the Add New icon.

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<BAGA.Activity> activities;
  List<BAGA.Destination> 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 bind ListBoxes and TextBoxes 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 to System.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 the ConnectionString into the current application.

  4. Drag a WPF ListBox control from the Controls Toolbox onto the default form. This will be the ListBox for displaying the trips.

  5. Change the ListBox's name to lstBoxTrips.

NOTE

If you haven't used WPF before, you might appreciate that the name 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. (See Example 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<Activity> activities;
    private List<Destination> destinations;
    private List<Lodging> lodgings;
    private List<Trip> 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 the Execute 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 in Example 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 the ItemsSource, 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 its ItemsSource 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:

<ListBox Margin="26,12,36,137" Name="lstBoxTrips" />

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 an ItemTemplate:

<ListBox Margin="25,22,42,197" Name="lstBoxTrips">
  <ListBox.ItemTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Within the ItemTemplate, you will use a DataTemplate because you are binding to a set of data:

<ListBox Margin="25,22,42,197" Name="lstBoxTrips">
  <ListBox.ItemTemplate>
    <DataTemplate >
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

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 a StackPanel (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.

<ListBox Margin="25,22,42,197" Name="lstBoxTrips">
  <ListBox.ItemTemplate>
    <DataTemplate >
      <StackPanel Orientation="Horizontal">
        <TextBlock Width="200" />
        <TextBlock />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

StackPanels 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 TextBlocks. This is where the data binding finally comes in. The Text 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:

<ListBox Margin="25,22,42,197" Name="lstBoxTrips">
  <ListBox.ItemTemplate>
    <DataTemplate >
      <StackPanel Orientation="Horizontal">
        <TextBlock Width="200"
                   Text="{Binding Path=Destination.DestinationName}"/>
        <TextBlock Text="{Binding
                         Path=StartDate, StringFormat=MM/dd/yyyy}"/>
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

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 in Figure 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 the ListBox'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:

<TextBox Height="23" Width="85" Margin="124,0,207,101" Name="txtStart"
         VerticalAlignment="Bottom" Grid.Column="0"/>

You'll be adding a Binding element as a child of each TextBox, 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 the Binding elements.

Example 8-12. The txtStart and txtEnd controls after adding the Binding element

<TextBox Height="23" Width="85" Margin="124,0,207,101" Name="txtStart"
         VerticalAlignment="Bottom" Grid.Column="0">
  <Binding ElementName="lstBoxTrips"
           Path="SelectedItem.StartDate"
                StringFormat="{}{0:MM/dd/yyyy}" />
</TextBox>

<TextBox Height="23" Width="85" Margin="0,0,54,101" Name="txtEnd"
         VerticalAlignment="Bottom" Width="120" >
  <Binding ElementName="lstBoxTrips"
           Path="SelectedItem.EndDate"
                 StringFormat="{}{0:MM/dd/yyyy}" />
</TextBox>

NOTE

In the ListBox you added a binding into the Text property of a TextBlock. Here you are using a Binding 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 the TextBox.

First, the code.

In the Window.Loaded event, bind the results of the Destinations and Lodgings queries to the ComboBoxes. 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 the TextBoxes, as shown in Example 8-13.

Example 8-13. Adding more attributes to the ComboBox controls

Before:
<ComboBox Height="23" Margin="0,0,36,72" Name="cboLodging" Width="198"/>

After:
<ComboBox Height="23" Margin="0,0,36,72" Name="cboLodging" Width="198"
          DisplayMemberPath="LodgingName"
          SelectedValuePath="LodgingID"
          SelectedValue="{Binding ElementName=lstBoxTrips,
                         Path=SelectedItem.Lodging.LodgingID}"/>

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

<ComboBox Height="23" Margin="26,0,0,72" Name="cboDestination"
          DisplayMemberPath="DestinationName"
          SelectedValuePath="DestinationID"
          SelectedValue="{Binding ElementName=lstBoxTrips,
                         Path=SelectedItem.Destination.DestinationID}"/>

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

You still have three more tasks to complete: viewing the activities for a trip, editing trip details, and adding new trips.

8.2.8. Adding Another EntityCollection to the Mix: Activities

The Activities property is an EntityCollection and you need to display it in a control that can display sets. For that, we'll use another ListBox.

8.2.8.1. The *:* relationship between trips and activities

Activities and trips are joined in a many-to-many relationship. Although the Entity Framework can query across this type of relationship and coordinate inserts, updates, and deletes without you having to work directly with the Join table, there is one thing that the Entity Framework is unable to do with this type of relationship, which is explained in the following paragraphs.

In previous examples, you saw how Object Services can automatically wire up related objects that are in the context. It will find entities that are related and build graphs between them. You took advantage of this in the Windows Forms application earlier. Because the activities and destinations were being returned in their own queries, you were able to remove the Include paths to the Customer preference properties.

In the Window.Loaded event for this WPF form, you have a query that returns a list of activities. You will use this as a pick list when you create a new trip. So, since those activities are already in the cache, it would make sense that they will automatically be wired up to the existing trips. But they aren't. This is expected behavior and you'll need to either load the related data with Include or Load, or manually attach the entities. In this example, you will use an Include. You'll learn more about this in Chapter 15.

NOTE

Object Services can automatically wire related entities only when one of the ends of the relationship has an EntityReference property that points to the other end of the relationship. Because both the Activities property of Trip and the Trips property of Activity are EntityCollections, the relationship information that is needed to bind them doesn't exist within either entity. That is why you need to explicitly create the graph with one of the Object Services methods for joining related entities.

8.2.8.2. Modifying the code to eager-load the related activities

The bottom line is that you need to change the Customer query in the Window.Loaded event. To do this, add an Include method to pull in the activities, as shown in the following code:

trips = context.Trips.Include("Activities") _
               .OrderBy("it.Destination.DestinationName") _
               .ToList

trips = context.Trips.Include("Activities")
               .OrderBy("it.Destination.DestinationName")
               .ToList()

8.2.8.3. Adding the Activities ListBox and binding it to the Trips ListBox

Drag another ListBox onto the Window you are designing. You can take advantage of binding to get its data from the Trips ListBox just as you have done with the TextBoxes and ComboBoxes. The syntax is a little different because of the nature of the control.

The ListBox will look similar to when it is first added to the Window:

<ListBox Height="88" Margin="36,0,238,12" Name="lstActivities" />

You need to use a number of attributes to data-bind in a ListBox. For example, DataContext performs the binding to the source—in this case, lstBoxTrips. ItemsSource points to the actual items that will be used to populate the ListBox. You want the Activities EntityCollection of the selected trip for this attribute. Next, DisplayMemberPath identifies which property of the ItemsSource will be displayed in the box. ActivityName will do the trick here. Finally, the ListBox needs a little extra help syncing up with the source ListBox. You need to set IsSynchronizedWithCurrentItem to True.

Example 8-15 shows the modified ListBox with all of the data-binding attributes in place.

Example 8-15. The modified ListBox

<ListBox Height="88" Margin="36,0,238,12" Name="lstBoxActivities"
     DataContext="{Binding ElementName=lstBoxTrips, Path=SelectedItem}"
     ItemsSource="{Binding Path=Activities}"
     DisplayMemberPath="ActivityName"
     IsSynchronizedWithCurrentItem="True"
/>

Don't forget that you can set some of these values in the Properties window for the control. Figure 8-16 shows the IsSynchronizedWithCurrentItem and DisplayMemberPath properties. The other binding attributes are not available in the Properties window, so you'll have to type them manually.

Figure 8-16. A modified view of the ListBox control's Properties window showing some of the properties that you can modify

8.2.8.4. Testing the application again

Once you have the ListBox control configured, you should be able to see the effect of having each trip's activities displayed in this ListBox as you select different trips from the main ListBox. Figure 8-17 shows the application performing its newest trick.

Figure 8-17. The WPF form with the Activities ListBox displaying an EntityCollection

8.2.9. Editing Trip Entities and Their Related Data

Now it's time for some editing.

In the Windows Forms application, the BindingSource coordinated user actions with entities. If a user edited data on the form, the BindingSource automatically pushed that change into the entity, even for the related entities.

Not so with WPF. Although the scalar properties, such as the StartDate and EndDate properties, will be updated with no more effort than calling SaveChanges, you'll need to do a little extra coding to get the related entities to update.

Add a new button to the form and change its Content property to Save. Then double-click the button to get to the Click event handler, the button's default event, and add a call to SaveChanges in the event handler, as shown in Example 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 that ComboBox 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 a SortedDescriptions 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 about SortedDescriptions 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 for cboLodgings:

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 the Activities 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 an ObservableCollection of trips as the source for the Trip ListBox control, as you add and remove items from this collection the ListBox 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 the ListBox.

To pull this off, you'll need a new variable that is scoped to the whole Window class. Add this to the Window'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<Trip>();

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 the ListBox. 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 StartDates 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 the Destination 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 the OrderBy 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 the ListBox 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 bound Trip ListBox displays the new trip at the top of the ListBox. When you change the default destination, the trip will reappear alphabetically sorted in the ListBox, 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.


9.1. Where Does Object Services Fit into the Framework?

Object Services is at the top of the food chain in the Entity Framework. The namespace for this API is System.Data.Objects, and it provides all of the necessary functionality for generating and interacting with the objects that are shaped by the conceptual layer and are populated from a data store.

As shown in Figure 9-1, Object Services initially processes your LINQ to Entities and ObjectQuery queries, as well as materializes the query results into objects.

Figure 9-1. Object Services as it relates to the rest of the Entity Framework stack

You can divide the core functionality of Object Services into seven areas:

  • Query processing

  • Object materialization

  • Object management

  • Object relationship management

  • Object state management

  • 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, whereas ObjectQuery 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<Contact>(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<T> 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

<Global.System.ComponentModel.BrowsableAttribute(false)>  _
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<Contact> Contacts
{
  get
  {
    if ((this._Contacts == null))
      this._Contacts = base.CreateQuery<Contact>("[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]
ObjectQuery with Entity SQL
context.CreateQuery(Of Contact)
("SELECT VALUE c
    FROM PEF.Contacts AS c
    WHERE c.FirstName='Robert'")

SELECT  VALUE c
FROM PEF.Contacts AS c
WHERE c.FirstName='Robert'

Query builder
context.Contacts
.Where("it.FirstName = 'Robert'")
.OrderBy("it.LastName")

SELECT VALUE it
FROM (
SELECT VALUE it
FROM (
[Contacts]
) AS it
WHERE
it.FirstName = 'Robert'
) AS it
ORDER BY
it.LastName

LINQ to Entities
From c In context.Contacts
Where c.FirstName = "Robert"

 (empty)

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<Contact>(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 MergeOptions 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.

AppendOnly 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.

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 ObjectResults 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 ObjectContext. 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.

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 DbDataRecords 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 the ObjectContext 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 the ObjectContext. That means that although the object instance exists, the ObjectContext is not aware of the object. You can have an EntityObject 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 the ObjectContext 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 an ObjectContext when they are deserialized.

The EntityState of an object that is not in the cache is always Detached.

Chapters Chapter 15 and Chapter 17 will provide much more insight into controlling the ObjectContext 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 the ObjectContext 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 the ObjectContext is considered to be "attached" to that context. This is when the ObjectContext maintains an ObjectStateEntry for that entity. You first learned about the ObjectStateEntry 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 the FirstName scalar property.

Example 9-9. The VB code related to the FirstName scalar property

<Global.System.Data.Objects.DataClasses.EdmScalarPropertyAttribute _
         (IsNullable:=false),_
         Global.System.Runtime.Serialization.DataMemberAttribute()>  _
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 the FirstName 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 word Create. 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 the Title parameter is not being used by the CreateContact 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 from EntityObject, and therefore it exposes two additional properties that come from EntityObject: 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 BreakAway Contact. It says that this entity belongs to the BAEntities 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 the EntityKey. 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. When SaveChanges 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. When SaveChanges 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 EntityKeys are instrumental in enabling this to happen. The EntityKeys 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 MergeOptions. 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<Contact>(queryString);
contacts.MergeOption = MergeOption.PreserveChanges;

The second way to define a MergeOption is as a parameter of ObjectQuery.Execute, as you saw earlier in this chapter.

Remember that you can cast a LINQ to Entities query to an ObjectQuery 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. The ObjectContext'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 returned Customers 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. The ObjectContext takes care of that for you through its relationship span capability.

When poking around the entity classes, you may have noticed that the EntityCollection properties, such as Addresses and Reservations, were read-only. Because of the way ObjectContext 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 the Address entities to the Contact.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 to Detached.

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 an ObjectContext 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 to Added. 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 existing EntityKey. An object will have an EntityKey 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 the EntityKey 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-new ObjectStateEntry 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-new ObjectStateEntry 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 the AttachTo method, which also requires that you indicate to which EntitySet the object belongs. With the name of the EntitySet, the Context can dynamically create an EntityKey for the object. The following example shows how to use the AttachTo 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 EntityKeys. 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 KeyValuePairs and then adding them to the EntityKey.

9.6.1.4. Creating an EntityKey on the fly

You may need to create an EntityKey on the fly if, for example, you are creating a new Customer 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 a CustomerType 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. In Chapter 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 an EntityKey, which you can then use for setting the CustomerTypeReference of the new Customer. The simplest constructor for an EntityKey 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 the CustomerType 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 of ObjectContext 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 in Chapter 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 the EntitySet defined in the first parameter. For example, the Contacts EntitySet is defined to return Contact entities, and the Contact property that is used for the EntityKey is ContactID. That's enough information to construct the EntityKey if necessary. With the EntityKey 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 call SaveChanges, 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 the ObjectContext.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 by SaveChanges 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 the ObjectContext and every object's EntityState 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 when SaveChanges 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 actual SaveChanges method.

In this single location, you can perform validation on all of the entities that the ObjectContext 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 implement SavingChanges 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 called ConcurrencyMode. By default, this is set to None. 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 to Fixed, 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 the SaveChanges method, are transactional by default. You can override the default behavior using System.Transaction.TransactionScope, EntityTransaction, or one of the other System.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 of SaveChanges 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 the Commit 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 the Serializable and DataContractAttribute attributes, as shown in the following code:

<Global.System.Data.Objects.DataClasses.EdmEntityTypeAttribute _
    (NamespaceName:="BAEntities", Name:="Contact"),  _
 Global.System.Runtime.Serialization.DataContractAttribute(),  _
 Global.System.Serializable()>  _
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, EntityKeys 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 Chapters Chapter 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 the MSDN 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 a ContactID must be sent from the client and that a Contact entity is returned to the client, as shown in the following code:

<OperationContract()> _
  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 is Detached; when you deserialize the objects they remain in a Detached state. If you attach them to an ObjectContext, whether it's a new ObjectContext 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 the System.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.NET EntityDataSource 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 term Interface 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, and SaveChanges will persist these changes to the store.

9.8.3.2. IEntityWithKey

Implementing IEntityWithKey will ensure that the ObjectContext 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 with EntityClient, 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 the ObjectContext (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 from ObjectContext (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 of PropertyChanging and PropertyChanged—for example, FirstNameChanged and FirstNameChanging. Like OnContextCreated, there is no default implementation for PropertyChanging 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 (see Example 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 (see Example 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 the Activity entity, and name the file Activity.vb or Activity.cs. In the file, add the code shown in Example 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 in Example 10-5.

In this method, we'll supplement the Activity 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 the Changed 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 to compute 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, that LineTotal 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 a SubTotal 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 the SaveChanges 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 per EntityState.

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 of ObjectContext.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's Or 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 the ObjectContext. You can do this in the entities code file.

Example 10-6 demonstrates overriding the SavingChanges 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 the EntityKey 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 the SavingChanges event handler. You can do this by overriding the OnContextCreated partial method, as shown in Example 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, the Customer'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 EntityReferences 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 the PropertyChanged 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. The EventArgs 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 the PropertyChanging and PropertyChanged events is in an entity's partial class.

In VB, these events are available in the IDE drop-downs. Using the Address 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 is AssociationChanged.

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 the Address, 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 for EntityCollection.

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 the CollectionChangeAction 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 the CustomerReference 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 Contact partial class.

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 the Contact'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 the ObjectContext, sometimes you will find that these are not enough. The EntityObjects are designed to encapsulate schema and relationships, not behavior. The lack of an opportunity to tap into AssociationChanging when you do have access to AssociationChanged 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 the ObjectContext 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 the EntityDataSource 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 an EntityDataSource 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.NET Page 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 <New data source> 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 AutoPage property set to True to support the feature (AutoPage is True by default).

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 GridViews 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 the EntityConnectionString, EntityContainer, and EntitySet properties you defined. It then creates a new ObjectContext 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 the EntityContainer 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, the EntityDataSource's internal method uses the Entity SQL query builder methods to build its queries.

The EntityDataSource control also has an Include property that emulates the ObjectQuery.Include method. EntityDataSource provides an EntityTypeFilter property that internally leverages Entity SQL's OFTYPE 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 in Table 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 single EntityDataSource on the page to manage Contact entities. As you'll see later in the chapter, you can have multiple EntityDataSource controls on a page. By default, each EntityDataSource on a page creates its own ObjectContext. If you have more than one EntityDataSource, 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 the DataSource 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 EntityDataSources.

Relationships among entities will not be recognized unless the same ObjectContext 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 and Reservation.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 the EntityDataSource'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 of ViewState 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 are True 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 in ViewState, 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 is a GridView 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, including DetailsView 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 Customers 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 <asp:BoundField> controls. However, you cannot access properties from the related entity with these BoundFields, even if the related entity has been loaded. Instead, you need to use <asp:TemplateField> 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

<asp:TemplateField HeaderText="PrimaryActivity"
                   SortExpression="PrimaryActivity">
  <EditItemTemplate>
    <asp:Label ID="Label1" runat="server"
               Text='<%# Eval("[PrimaryActivity.ActivityName]") %>'>
    </asp:Label>
  </EditItemTemplate>
  <ItemTemplate>
    <asp:Label ID="Label1" runat="server"
               Text='<%# Bind("[PrimaryActivity.ActivityName]") %>'>
    </asp:Label>
  </ItemTemplate>
</asp:TemplateField>

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 <asp:DropDownList> 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:

    <EditItemTemplate>
  <asp:DropDownList runat="server" id="act1DDL"
                    DataSourceID="EntityDataSource2"
                    DataTextField="ActivityName"
                    DataValueField="ActivityID"
                    SelectedValue=
                     '<%# Bind("PrimaryActivity.ActivityID") %>'>
  </asp:DropDownList>
</EditItemTemplate>

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:

<asp:DropDownList runat="server" id="act1DDL"
     DataSourceID="EntityDataSource2"
     DataTextField="ActivityName"
     DataValueField="ActivityID"
     AppendDataBoundItems="True"
     SelectedValue=
       '<%# Bind("PrimaryActivity.ActivityID") %>'>
   <asp:ListItem Value="">Select...</asp:ListItem>
</asp:DropDownList>

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 TemplateFields 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

<asp:EntityDataSource ID="ContactDataSource" runat="server"
  ConnectionString="name=BAEntities"
  DefaultContainerName="BreakAwayEntities" EnableUpdate="True"
  EntitySetName="Contacts" EntityTypeFilter="Customer"
  AutoGenerateOrderByClause="True" AutoGenerateWhere="True">
  <WhereParameters>
    <asp:ControlParameter
        ControlID="GridView1"
        Name="ContactID"
        PropertyName="SelectedValue"
        DbType="Int32" />
    </WhereParameters>
 </asp:EntityDataSource>

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 WhereParameters when the AutoGenerateWhereClause=False. Remember that AutoGenerateWhereClause=False plus the WhereParameters is 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:

<asp:EntityDataSource ID="Customers" runat="server"
     ConnectionString="name=BreakAwayEntities"
     DefaultContainerName="BreakAwayEntities"
     EnableUpdate="True"
     EntitySetName="Contacts" EntityTypeFilter="Customer"
     AutoGenerateWhereClause="True"
     Include="Reservations.Trip">
</asp:EntityDataSource>

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 EntityDataSources 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

<asp:EntityDataSource ID="EntityDataSource2" runat="server"
                      ConnectionString="name=BAEntities"
                      DefaultContainerName="BAEntities"
                      EntitySetName="Contacts"
                      AutoGenerateWhereClause="True">
  <WhereParameters>
    <asp:ControlParameter ControlID="DropDownList1" Name="ContactID"
        PropertyName="SelectedValue" DbType="Int32" />
  </WhereParameters>
</asp: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<BAGA.Contact>();
  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

    <asp:ListView ID="ListView1" runat="server" DataKeyNames="PaymentID"
              DataSourceID="PaymentsDataSource"
              InsertItemPosition="LastItem" Style="font-size: small">
  <ItemTemplate>
    <tr style="">
      <td></td>
      <td>
        <asp:Label ID="PaymentDateLabel" runat="server"
                   Text='<%# Eval("PaymentDate","{0:d}") %>' />
      </td>
      <td>
        <asp:Label ID="AmountLabel" runat="server"
                   Text='<%# Eval("Amount","{0:c}") %>' />
      </td>
    </tr>
  </ItemTemplate>
  <InsertItemTemplate>
    <tr style="">
      <td>
        <asp:Button ID="InsertButton" runat="server"
                    CommandName="Insert" Text="Insert" />
        <asp:Button ID="CancelButton" runat="server"
                    CommandName="Cancel" Text="Clear" />
      </td>
      <td>
        <asp:TextBox ID="PaymentDateTextBox" runat="server"
                     Text='<%# Bind("PaymentDate") %>' />
      </td>
      <td>
        <asp:TextBox ID="AmountTextBox" runat="server"
                     Text='<%# Bind("Amount") %>' />
      </td>
    </tr>
  </InsertItemTemplate>
  <LayoutTemplate>
    <table runat="server">
      <tr runat="server">
        <td runat="server">
          <table id="itemPlaceholderContainer" runat="server"
                 border="0" style="">
            <tr runat="server" style="">
              <th runat="server"></th>
              <th runat="server">PaymentDate</th>
              <th runat="server">Amount</th>
            </tr>
            <tr id="itemPlaceholder" runat="server"></tr>
          </table>
        </td>
      </tr>
      <tr runat="server">
        <td runat="server" style=""></td>
      </tr>
    </table>
  </LayoutTemplate>
</asp:ListView>

    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:

    <WhereParameters>
   <asp:ControlParameter Name="Reservation.ReservationID"
     ControlID="ListBox1" PropertyName="SelectedValue"
     DbType="Int32"
     DefaultValue="0" />
</WhereParameters>

    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 the EntityDataSource 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 a ListBox with its related data.

Each of these events provides relevant information through its EventArgs 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 new EntityKey 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 the NewEntity 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 with Updating.

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 the DataSource 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 the EntityDataSource 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 as MaximumRows, 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. The Contact 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 between Contact and Customer in the database. This means a Contact could have a related Customer entity, but it's not required. It also means a Contact cannot have more than one related Customer 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 between Contact 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 and Customer 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 for Customer.

  7. Map the Customer's new ContactID property (which now comes from the Contact 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 from Contact. 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 from Contact, 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 the CustomerTypeID 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 the ContactID 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 between Customer 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 between Customer and Activity. In the following exercise, you will create the first Activity association and mapping:

  1. Create the first association by dragging an Association control from the Toolbox and attaching one end to Customer 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 select Customers 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 between Customer 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 to Customers.SecondaryActivity and Customers.ContactID.

Follow steps 1–4 again to remap the associations used for the Customer'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 be SecondaryCustomerPrefs and SecondaryDestination. This association will map to the Customers.SecondaryDestination property.

12.2.3. Handling Properties with the Same Name

Both the Customers and the Contact tables have a TimeStamp column for concurrency checking. However, because the Customer 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 the Addresses 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 a Customer 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<Customer> 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:

    1. Add a reference to System.Data.Entity.

    2. Add a reference to the model project.

    3. 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 following commands:

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<Customer>()
        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 instantiates newCust.

  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 with context.SaveChanges. When SaveChanges is called, the Entity Framework constructs commands to first create a new Contact 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 in Example 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 for CustomerTypeID. These values are coming from the custom SavingChanges event you built in the preceding chapter. The new Customer 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 for Contact 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<Customer>

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 are OFTYPE 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 not Customer 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 like Contacts. 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 as Customer 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 and EntityClient with Entity SQL to build more complex queries around types.

In LINQ, the safest way to do type filtering is to use the OfType 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 on Customer properties.

When you place the type filter in the Where 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 on Customer 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 new Customer object will cause a new Contact 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 new Customer and will try to add the Contact to the database.

Get the Contact into the context and add a new Customer with the same ContactID

Both the Contact and the Customer are members of the Contacts entity set. You will not be able to add the Customer to the context because a member of the Contacts entity set with the same EntityKey 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 call SaveChanges

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 the Customer "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, the TREAT 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 a Customer 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, and Customer 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 the Contact 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 the Contact 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 it NonCustomer.

  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 of NonCustomer you will find in the XML (other than the Designer information) is this element in the CSDL:

    <EntityType Name="NonCustomer" BaseType="BAModel.Contact" >
</EntityType>

    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 the Contact 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 Customers 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 between Customers and ContactPersonalInfo using ContactID. That would make Customer a navigation property of ContactPersonalInfo 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 the Customer 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 the ContactPersonalInfo 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<Customer>()
            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 the newcust variable.

  4. Run the project.

When the process stops at the breakpoint, debug the firstCust variable and you can see in the QuickWatch window that the new properties of Customer are populated, as shown in Figure 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 a Customer and when adding a new Customer. The first two commands update the ModifiedDate field in Contact and the BirthDate field in ContactPersonalInfo for the first Customer that was queried and edited. The newly added Customer results in the creation of a Contact, 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 between Customer 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 <Add a Condition>, 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 in Example 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 after query.ToList is called.

  5. Run the application.

When you hit the breakpoint, look at the activities 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 the ActivityName, 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 all Activity 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 as WaterActivity 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 the Category field in the Activities table.

  1. Click the When Category mapping in the Mapping Details window.

  2. Select <Delete> 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 the Category 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 from Lodging. 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 define Resort 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 the Lodgings 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 the Resort entity? The answer is conditional mapping.

First, we'll use conditional mapping to filter Lodging 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 the Lodging entity and click <Add a Condition>.

  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 for Resort = 1 (or True).

NOTE

The ContactID and LocationID are foreign keys in the Lodging 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 the Lodging 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 the ResortChainOwner 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 in Figure 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. The LuxuryResort 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 the Lodging entity. The Lodging entity maps to the Lodging table but does not map the LuxuryResort 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 the Lodging entity is unable to modify the value.

The only way to correct this is to add the StoreGeneratedPattern 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 the Lodging 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 in Example 12-5.

Example 12-5. Adding the StoreGeneratedPattern attribute for an unmapped non-nullable field

<EntityType Name="Lodging">
  <Key>
    <PropertyRef Name="LodgingID" />
  </Key>
  <Property Name="LodgingID" Type="int" Nullable="false"
            StoreGeneratedPattern="Identity" />
  <Property Name="LodgingName" Type="nchar" Nullable="false"
            MaxLength="50" />
  <Property Name="ContactID" Type="int" Nullable="false" />
  <Property Name="LocationID" Type="int" />
  <Property Name="Resort" Type="bit" Nullable="false" />
  <Property Name="ResortChainOwner" Type="nchar" MaxLength="30" />
  <Property Name="LuxuryResort" Type="bit" Nullable="false"
            StoreGeneratedPattern="Computed"/>
</EntityType>

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<Lodging>()
        select lodge;
    Console.WriteLine("NonResort Results: " + query.Count().ToString());
  }
  using (var context = new BAEntities())
  {
    var query =
        from lodge in context.Lodgings.OfType<Resort>()
        select lodge;
    Console.WriteLine("Resort Results: " + query.Count().ToString());
  }
}

  2. Call the TPHMap method from the module's Main 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 actual Lodging 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 the NonCustomer 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 its BaseType 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 value 0 for the comparison.

  5. Validate the model using the Designer's context menu.

    You will see two errors regarding overlapping partitions. This is because the Lodging 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 the Lodging entity into an abstract type and remove the conditional mapping.

  6. Open the Mapping Details window for Lodging.

  7. Delete the conditional mapping. Remember that you can do this by selecting <Delete> 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<NonResort>() select lodge

  1. Run the application again. This time you can see that the derived NonResort 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 EDM Designer

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 the Reservations 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 called Table 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. The Reservation EntityTypeMapping contains a mapping for the Reservation entity and another mapping for the derived OldReservationEntity.

Example 12-7. TPC mapping

<EntitySetMapping Name="ReservationSet">
  <EntityTypeMapping TypeName="IsTypeOf(BreakAwayModel.Reservation)">
    <MappingFragment StoreEntitySet="Reservations">
      <ScalarProperty Name="ReservationID" ColumnName="ReservationID" />
      <ScalarProperty Name="ReservationDate" ColumnName="ReservationDate" />
      <ScalarProperty Name="ContactID" ColumnName="ContactID" />
      <ScalarProperty Name="EventID" ColumnName="EventID" />
      <ScalarProperty Name="TimeStamp" ColumnName="TimeStamp" />
    </MappingFragment>
  </EntityTypeMapping>
  <EntityTypeMapping TypeName="IsTypeOf(BreakAwayModel.OldReservation)">
    <MappingFragment StoreEntitySet="OldReservations">
      <ScalarProperty Name="ReservationID" ColumnName="ReservationID" />
      <ScalarProperty Name="ReservationDate" ColumnName="ReservationDate" />
      <ScalarProperty Name="ContactID" ColumnName="ContactID" />
      <ScalarProperty Name="EventID" ColumnName="EventID" />
      <ScalarProperty Name="TimeStamp" ColumnName="TimeStamp" />
    </MappingFragment>
  </EntityTypeMapping>
</EntitySetMapping>

With this mapping, you would be able to work with the OldReservations table when you need to. Also with this mapping, you will get the OldReservations anytime you query for Reservation 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 EntityObjects, but ComplexObjects. They don't have EntityKeys and are not contained in their own EntitySet; therefore, they cannot be queried for directly or persisted into 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

<ComplexType Name="AddressDetail">
  <Property Name="Street1" Type="String" MaxLength="50"
            FixedLength="true" />
  <Property Name="Street2" Type="String" MaxLength="50"
            FixedLength="true" />
  <Property Name="City" Type="String" MaxLength="50"
            FixedLength="true" />
  <Property Name="StateProvince" Type="String" MaxLength="50"
            FixedLength="true" />
  <Property Name="CountryRegion" Type="String" MaxLength="50"
            FixedLength="true" />
  <Property Name="PostalCode" Type="String" MaxLength="20"
            FixedLength="true" />
</ComplexType>

As shown in Figure 12-22, the ComplexType element is positioned in the CSDL section as a sibling of the EntityTypes. 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

<EntityType Name="Address">
  <Key>
    <PropertyRef Name="addressID" />
  </Key>
  <Property Name="addressID" Type="Int32" Nullable="false" />
  <Property Name="Detail" Type="Self.AddressDetail" Nullable="false" />
  <Property Name="ModifiedDate" Type="DateTime" Nullable="false" />
  <NavigationProperty Name="Contact"
                      Relationship="BAModel.FK_Address_Contact"
                      FromRole="Address" ToRole="Contact" />
</EntityType>

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<EntitySetMapping Name="Address">
 <EntityTypeMapping TypeName="IsTypeOf(BAModel.Address)">
  <MappingFragment StoreEntitySet="Address">
   <ScalarProperty Name="addressID" ColumnName="addressID" />
   <ComplexProperty Name="Detail"
                    TypeName="BAModel.AddressDetail">
     <ScalarProperty Name="Street1" ColumnName="Street1" />
     <ScalarProperty Name="Street2" ColumnName="Street2" />
     <ScalarProperty Name="City" ColumnName="City" />
     <ScalarProperty Name="StateProvince" ColumnName="StateProvince" />
     <ScalarProperty Name="CountryRegion" ColumnName="CountryRegion" />
     <ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
   </ComplexProperty>
   <ScalarProperty Name="AddressType" ColumnName="AddressType" />
   <ScalarProperty Name="ModifiedDate" ColumnName="ModifiedDate" />
  </MappingFragment>
 </EntityTypeMapping>
</EntitySetMapping>

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 EntityKeys, 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

<asp:ListView runat="server" ID="ListView1">
  <LayoutTemplate>
    <table runat="server" id="table1" >
      <tr runat="server" id="itemPlaceholder" ></tr>
    </table>
  </LayoutTemplate>
  <ItemTemplate>
    <tr runat="server">
      <td id="Td1" runat="server">
        <%-- Data-bound content. --%>
        <asp:Label ID="NameLabel" runat="server"
          Text='<%#Eval("Detail.Street1") %>' />
      </td>
      <td id="Td2" runat="server">
        <%-- Data-bound content. --%>
        <asp:Label ID="Label1" runat="server"
          Text='<%#Eval("Detail.City") %>' />
      </td>
    </tr>
  </ItemTemplate>
</asp:ListView>

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 use QueryView 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 generate Insert, 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 to SaveChanges.

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 <. However, because QueryView returns read-only data, if you need the entity that results to be updatable, you can still achieve this by mapping stored procedures to the entity that results.

Before using QueryView, you should be aware of the following:

  • QueryView is another mapping that the Designer does not support. Unlike complex types, however, it doesn't prevent you from opening the model in the Designer. The lack of support in this case means you can only design the query view directly in the XML of the model.

  • If an EntitySet is being mapped with a QueryView, all related EntitySets and AssociationSets must be mapped with QueryViews as well.

    This could get a little trying in the BreakAway model, as every entity is related to at least one other entity through associations. So, you need to plan ahead if you want to take advantage of QueryViews.

    For a nice example of adding QueryViews to a model with TPH inheritance, see the blog post by Danny Simmons, of the Entity Framework team, titled "Mapping Read-only Entities" (http://blogs.msdn.com/dsimmons/archive/2007/11/08/mapping-read-only-entities.aspx/).

  • Entities returned by QueryView are read-only. If you want the entities that result to be updatable, you can use function mappings to map stored procedures to the entity, as you did earlier in this book with the Payment entity. The next chapter deals with additional stored procedure mappings.

  • In the EntitySetMapping, you need to remove the StorageSetName as well as the property mappings.

  • QueryViews impact other types of mappings in the model. As per the MSDN documentation, you need to pay attention to these scenarios as well:

    • Many-to-many associations

    • Inheritance hierarchies

  • The syntax for writing a QueryView is a subset of the Entity SQL language. Functions are not allowed, which means you can't do something like create a FullName property by concatenating FirstName and LastName.

    Here are the operators you can use with QueryView:

    Cast, Case, Not, Or, And, IsNull, Equals, NotEquals, LessThan, LessThanOrEquals, GreaterThan, GreaterThanOrEquals, Project, NewInstance, Filter, Ref, Union, UnionAll, Scan, FullOuterJoin, LeftOuterJoin, InnerJoin, EntityRef

12.9.1. Creating a Simple QueryView

Because of the complexity of the BreakAway model, if you would like to test the QueryView, it would be best to get your first look with a simpler model.

In this exercise, you will be adding a condition to the Contacts EntitySet to make sure all queries will return only customers who were added to the database since January 1, 2007:

  1. Create a new Console Application project with its own model created from the BreakAway database. Use the names QVEntities and QVModel for the connection string and model name. Add the Contact and Customers tables to this model when prompted by the EDM Wizard.

  2. Change the entity and EntitySet names to Contact/Contacts and Customer/Customers. On the latter, watch out for the EntitySet name changing to CustomerSet when you change the entity name.

  3. Save the model and then open it in the XML Editor.

  4. Scroll down to the Mapping Schema Layer (MSL) content section. Comment out or delete everything inside the EntitySetMapping element for Contact.

  5. In place of those elements, add the following QueryView, which will be inside the EntitySetMapping tags:

    <EntitySetMapping Name="Contacts">
 <QueryView>
     SELECT VALUE QVModel.Contact(c.ContactID,c.FirstName,c.LastName,
     c.Title,c.AddDate,c.ModifiedDate,c.TimeStamp)
     FROM QVModelStoreContainer.Contact as c
     WHERE c.AddDate>= DATETIME'2007-01-1 00:00'
</QueryView>
</EntitySetMapping>

    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 for QueryViews.

    NOTE

    Notice that the WHERE parameter uses a DATETIME literal, which you learned about in Chapter 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 of c.

    NOTE

    In some cases, you may have to open the model in the Designer to highlight the QueryView 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 the QueryView:

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 the Using clause and run the test.

When the debugger stops at the breakpoint, check out the contacts 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:

<QueryView>
SELECT VALUE QVModel.Contact(c.ContactID,c.FirstName,c.LastName,c.Title
                             c.AddDate,c.ModifiedDate,c.TimeStamp)
FROM QVModelStoreContainer.Contact as c
</QueryView>

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 call SaveChanges, 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 a QueryView on an entity that is part of an inheritance hierarchy, you need to have QueryViews for all other entities in that hierarchy.

Let's add some inheritance into this sample model and see how to set up the QueryViews for base and derived entities:

  1. Define a TPT inheritance between Contact 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 the QueryView before.

  4. Comment out the entire EntityTypeMapping section that was created for the Customer 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

    <QueryView TypeName="IsTypeOf(QVModel.Customer)">

  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'
</QueryView>

    The hierarchy should now be as follows:

    <EntitySetMapping Name="Contacts">
  <QueryView> (the contact query) </QueryView>
  <QueryView (the customer query) </QueryView>
</EntitySetMapping>

There are a few things to note regarding this new QueryView.

First, the QueryView's TypeName attribute is specified. This is a requirement. The rule is that the first QueryView must not have a TypeName attribute. A runtime exception will specifically point this out if you break the rule. But all other QueryViews within an EntitySetMapping must have the TypeName attribute. It is the same attribute and value that are used in the regular mapping.

The next point to note is that the query is returning all of the values for the customer record using a JOIN query. Standard mappings need to map only the properties that are in the derived type. But with a QueryView, you need to return properties from the base type. Except for the type constructor, this JOIN query is similar to the JOIN queries you saw in Chapter 4.

12.9.4.1. When will the Entity Framework use the QueryView for the derived type?

Here is an interesting point. This Customer's QueryView will be used only when the code explicitly requests Customers—for example, by querying:

TypeOf(Of Customer)

context.Contacts.TypeOf<Customert>

12.9.4.2. The default QueryView needs to account for derived types

If you were to run the code that queries for context.Contacts right now, you would get all of the records back, yet they would all be returned as Contact types. Recall the results of a similar TPT query displayed in Figure 12-7, which returned both Contact and Customer types. Why is QueryView not doing the same? The reason is that when querying the base type, only the base type's QueryView will be used. The Entity Framework does not use the QueryViews for each derived type. Instead, the base QueryView needs to know how to return Contacts and Customers.

Example 12-15 shows the Entity SQL that will accomplish this.

NOTE

This expression demonstrates a number of operators for Entity SQL that we haven't covered in previous chapters, as well as patterns that will give you a better understanding of the power of Entity SQL.

Example 12-15. A QueryView that can return a base type or an inherited type

<QueryView>
  SELECT VALUE
    CASE
    WHEN (cu.ContactID IS NULL) THEN
      QVModel.Contact(c.ContactID,c.FirstName,c.LastName,
      c.Title,c.AddDate,c.ModifiedDate,c.TimeStamp
    ELSE
      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
    END
  FROM QVModelStoreContainer.Contact as c
  LEFT OUTER JOIN QVModelStoreContainer.Customers as cu
  ON cu.ContactID=c.ContactID
  WHERE c.AddDate>= DATETIME'2007-01-1 00:00'
</QueryView>

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 whether Customer.ContactID is null. If it is null, the SELECT is completed with a type constructor for a Contact type. This is the same expression that was used for the first Contact QueryView. If the test for Customer is not null, the SELECT is completed with a type constructor for a Customer 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<BAModel.Customer>.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, the InsertPayment and CustomersbyState functions, which line up with the Payment 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 with EntityClient (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 is ActivitiesonaTrip. Example 13-1 shows the database procedure for this function. Because of the SELECT *, the procedure returns all of the columns from Activities.

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 the Activities table, so the fields and properties should line up exactly. The Activity entity has the same fields; or does it? The field names in the Activities table are ActivityID, Activity, imagepath, and Category. You may recall that when changing the original entity name from Activities to Activity, there was a conflict with the property named Activity, so you changed the property name to ActivityName. 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 of EntityClient. You can perform an EntityClient 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 the Customer table, fields from the Contact table, and fields from the ContactPersonalInfo table.

NOTE

You will see the originally misspelled Customer 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<=@enddate
  GROUP BY Customers.contactid)

You can use the Model Browser to create a function import for this stored procedure and point the return type to the Customer entity. You can test the function with the code in Example 13-2.

Example 13-2. Calling a function that returns a derived type

Using context As New BAEntities
  Dim customers = context.CustomersWhoTravelledinDateRange _
    (New Date(2006, 1, 1), New Date(2006, 12, 31))
End Using

using (BAEntities context = new BAEntities())
{
  var customers = context.CustomersWhoTravelledinDateRange=
   (new DateTime(2006, 1, 1), new DateTime(2006, 12, 31));
}

13.4.3. Queries That Return Randomly Shaped Results

More commonly, your existing stored procedures will not conveniently return results that match up with a predefined entity. Unfortunately, the Designer does not directly support these procedures. However, there are two ways to incorporate them into your model.

The first is to create an entity that matches up with the returned data. This is a little involved because the model requires every entity to map to something in the store layer. Therefore, although creating an entity in the conceptual layer is simple, you'll need to manually create a matching entity in the store layer and then map the two. In effect, you are creating in the Store Schema Definition Layer (SSDL) a "fake" table; a representation of a table that does not actually exist in the database. This isn't so bad for a handful of stored procedures, but it may become tiresome if you have a lot of stored procedures of this type.

NOTE

It would be nice to see a third-party tool that is able to do this automatically. I will definitely be on the lookout for this.

13.4.4. Replacing Stored Procedures with Views

If you have control of the database, a "trick" to get around the problem of stored procedures that return randomly shaped results is to create a view in the database that returns data of the same structure as the stored procedure. Remember that views become read-only entities in the model.

Once you have done this, you now have two roads you can follow.

The first is to just use the view in your model and forget the stored procedure. A nice benefit of this is that a view is composable, whereas the function derived from the stored procedure is not. You can write queries against the view and those queries will become native store commands, executed on the server. When using the function, though, you can call the function and you can even use it in a query; however, the function itself, as you saw in Chapter 6, will execute the stored procedure on the server, and then the results will be further manipulated on the client side by the rest of the query operators. This could result in very inefficient queries if your stored procedure returns many more entities than your query specifies.

If you have Insert, Update, and Delete procedures that align with the results of that view, you can map them back to that new entity using function mapping and use it as your object. If you do this, you'll want to remove the entity that this is replacing so that you don't have update collisions.

The other path to follow is useful if you still want to use the function that represents the read stored procedure. You can map the FunctionImport to that entity.

However, you are still looking at a hack here, and this particular hack of using a view leaves you with your results sitting in a new entity type.

Let's walk through the scenario with the PaymentsforPeriod stored procedure. This procedure pulls fields from a number of tables defined by INNER JOINs. The results do not match up with any entities in the model. Example 13-3 shows the SQL for this procedure.

Example 13-3. The PaymentsforPeriod stored procedure in the BreakAway database

PROCEDURE PaymentsforPeriod
--returns payment, reservation, trip, and contact information
@startDate AS DATETIME,
@endDate AS DATETIME

AS

SELECT     Payments.PaymentDate, Payments.Amount,
           Reservations.ReservationDate, Contact.FirstName,
           Contact.LastName, Events.StartDate, Events.EndDate,
           Locations.LocationName
FROM       Payments INNER JOIN
           Reservations ON Payments.ReservationID =
           Reservations.ReservationID INNER JOIN
           Contact ON Reservations.ContactID = Contact.ContactID
           INNER JOIN Events ON Reservations.EventID = Events.EventID
           INNER JOIN Locations ON Events.LocationID =
             Locations.LocationID
WHERE payments.[PaymentDate] BETWEEN @startdate AND @enddate

The problem with this procedure is that it returns fields with the original column names from the table, and these don't match the modified property names in the entity.

You could create a new view in the database, such as vPaymentsforPeriod, that matches the procedure with the exception that it takes no input parameters and has no WHERE clause. When this view is pulled into the model, you will have a new entity named vPaymentsforPeriod. Now, as shown in Figure 13-1, you can simply create a FunctionImport from the PaymentsforPeriod function that returns a vPaymentsforPeriod type.

Figure 13-1. Mapping a function to an entity that was created from a view

Now you can call that function and work with the read-only results. Example 13-4 shows this function being called in code.

Example 13-4. Calling a function that returns entities

Using context As New BAEntities
  Dim pmts = context.PaymentsforPeriod
   (New Date(2008, 1, 1), New Date(2008, 12, 31))
End Using

using (BAEntities context = new BAEntities())
{
  var pmts = context.PaymentsforPeriod
   (new DateTime(2008, 1, 1), new DateTime(2008, 12, 31));
}

The benefit of using the view to create an entity for capturing the results of the stored procedure is that you will receive an object that can be change-tracked and that will have its relationships managed by the ObjectContext.

13.4.5. Queries That Return Multiple Resultsets

The Entity Framework does not directly support queries that return multiple resultsets either. However, Colin Meek, one of the members of the Entity Framework team, created a project called EFExtensions that contains a method for using stored procedures that return multiple resultsets. In its current iteration, each resultset can match up with an existing entity or it can return arbitrary types. You can find EFExtensions on the MSDN Code Gallery at http://code.msdn.microsoft.com/EFExtensions/. Colin wrote an in-depth explanation of how these extensions work, along with a walkthrough of his sample application, on his blog, at http://blogs.msdn.com/meek/archive/2008/03/26/ado-entity-framework-stored-procedure-customization.aspx/.

13.4.6. Queries That Return Primitive Types

Although the Designer supports queries that return primitive types, such as Int32 and String, the Designer does not generate code for them. Therefore, you won't be able to call the function directly from the context, as you saw with some of the functions discussed earlier in this chapter.

However, you can use EntityClient to call these function imports.

The TripSalesforDateRange procedure shown in Example 13-5 returns a scalar value representing the total sales from reservations made within a specific date range.

Example 13-5. The TripSalesforDateRange stored procedure

PROCEDURE TripSalesforDateRange
    @startdate DATETIME,
    @enddate datetime
AS
SET NOCOUNT ON;
SELECT     SUM(Events.TripCostUSD) AS Expr1
FROM       Reservations INNER JOIN
           Events ON Reservations.EventID = Events.EventID
           WHERE reservationdate BETWEEN @startdate AND @enddate

When creating a FunctionImport from this, you would specify that the return type is a scalar value of type Int32, as shown in Example 13-6.

Example 13-6. Using EntityClient to call TripSalesforDateRange

Private Sub ScalarFunction()
  Dim result As Int32
  Dim eConn = New EntityConnection("Name = BAEntities")
  Dim eComm = eConn.CreateCommand
  With eComm
    .CommandType = CommandType.StoredProcedure
    .CommandText = "BAEntities.TripSalesforDateRange"
    .Parameters.AddWithValue("startdate", New Date(2006, 1, 1))
    .Parameters.AddWithValue("enddate", New Date(2006, 12, 31))
    eConn.Open()
    result = Convert.ToInt32(.ExecuteScalar())
    eConn.Close()
  End With
End Sub

private static void ScalarFunction()
{
  Int32 result = 0;
  var eConn = new EntityConnection("Name = BAEntities");
  var eComm = eConn.CreateCommand();
  eComm.CommandType = CommandType.StoredProcedure;
  eComm.CommandText = "BAEntities.TripSalesforDateRange";
  eComm.Parameters.AddWithValue("startdate", new DateTime(2006, 1, 1));
  eComm.Parameters.AddWithValue("enddate", new DateTime(2006, 12, 31));
  eConn.Open();
  result = Convert.ToInt32(eComm.ExecuteScalar());
  eConn.Close();
}


13.5. Adding Native Queries to the Model

Sometimes a query you would like to use in your application does not exist in the database. Although you cannot define native queries on the fly in code, you can add native queries directly into the model using the Function element, and then add your own query into the function using the CommandText element.

As an example, it would be very convenient to use the PaymentbyPeriod query to return payments for a particular contact, rather than for all contacts in a date range. You could do this by changing the WHERE clause. The results of this new query would still match up with the vPaymentsbyPeriod entity in the model.

You can do this directly in the SSDL without adding a new stored procedure to the database.

If the procedure did exist in the database, the following function would represent it in the model:

<Function Name="PaymentsforContact" IsComposable="false">
  <Parameter Name="ContactID" Type="int" Mode="In"/>
</Function>

The Function element has a child element called CommandText. You can enter native store commands, such as a SQL Server T-SQL query, directly into the CommandText element. Therefore, you can add the new query directly into the SSDL of the model.

The entire function would now look like this:

<Function Name="PaymentsforContact" IsComposable="false">
  <CommandText>
    SELECT   Payments.PaymentDate, Payments.Amount,
             Reservations.ReservationDate, Contact.FirstName,
             Contact.LastName, Events.StartDate, Events.EndDate,
             Locations.LocationName
    FROM     Payments INNER JOIN
             Reservations ON Payments.ReservationID =
             Reservations.ReservationID INNER JOIN
             Contact ON Reservations.ContactID = Contact.ContactID
             INNER JOIN Events ON Reservations.EventID = Events.EventID
             INNER JOIN Locations ON Events.LocationID =
             Locations.LocationID
    WHERE Contact.ContactID=@ContactID
  </CommandText>
  <Parameter Name="ContactID" Type="int" Mode="In"/>
</Function>

NOTE

If you have any less than (<) signs in your query, you'll need to use the escaped notation (&lt;) so that there is no conflict with the XML, which interprets < as the beginning of a node. You'll see this in action in Section 13.6.3.

Now you can create a FunctionImport for the PaymentsforContact function and map it to the vPaymentsbyPeriod entity, and then call this function in your code.


13.6. Adding Native Views to the Model

In addition to being able to add your own stored procedures to the model, you can also add views that don't exist in the database. However, rather than using a function, you use an element called DefiningQuery.

13.6.1. DefiningQuery Is Already in Your Model

A DefiningQuery is comparable to a database view, which is a virtual table. Like a view, a DefiningQuery cannot take parameters or return multiple resultsets, as a stored procedure can.

However, because a DefiningQuery is a virtual table, when you write LINQ or Entity SQL queries against these views, query operators such as WHERE filters will be processed on the server side. This is quite different from working with functions that are embedded in the model. Queries written against functions will have filters performed on the client side.

Native Objects in the Store Layer

One of the interesting features of the SSDL is that it does not have to be a perfect reflection of your database. You can add objects into the SSDL that don't exist in the database; such objects are referred to as virtual.

When working with the DefiningQuery in this chapter you will create a virtual table in the SSDL that does not exist in the database. You'll do this because the DefiningQuery will actually perform the query in the database and return data. The data must be returned to an entity. That's not a problem, since you can create that entity in the conceptual layer of the model. However, the EDM has a rule that every entity in the Conceptual Schema Definition Layer (CSDL) must map to something in the SSDL.

The solution to this is to create a fake table in the SSDL to map to. The fake table must have an EntitySet, so you will also create a virtual EntitySet. None of these things actually exist in the database, and the model doesn't care about that. The only problem is that the current version of the Designer will overwrite any customizations of the SSDL if you run the Update Model Wizard. So, keep a copy of these changes in a separate file in case you need to re-create them.

You'll get a step-by-step walkthrough for creating virtual tables later in this chapter.

If you open the BreakAway model in the XML Editor, you will find that you already have two DefiningQuery elements. The EDM Wizard turned all of the database views into DefiningQuery elements in the SSDL.

The first one is for vOfficeAddresses and contains a T-SQL query against the database's vOfficeAddresses view:

<EntitySet Name="vOfficeAddresses"
 EntityType="BreakAwayModel.Store.vOfficeAddresses" store:Type="Tables"
 store:Schema="dbo" store:Name="vOfficeAddresses">
   <DefiningQuery>
     SELECT
      [vOfficeAddresses].[FirstName] AS [FirstName],
      [vOfficeAddresses].[LastName] AS [LastName],
      [vOfficeAddresses].[addressID] AS [addressID],
      [vOfficeAddresses].[Street1] AS [Street1],
      [vOfficeAddresses].[Street2] AS [Street2],
      [vOfficeAddresses].[City] AS [City],
      [vOfficeAddresses].[StateProvince] AS [StateProvince],
      [vOfficeAddresses].[CountryRegion] AS [CountryRegion],
      [vOfficeAddresses].[PostalCode] AS [PostalCode],
      [vOfficeAddresses].[AddressType] AS [AddressType],
      [vOfficeAddresses].[ContactID] AS [ContactID],
      [vOfficeAddresses].[ModifiedDate] AS [ModifiedDate]
     FROM [dbo].[vOfficeAddresses] AS [vOfficeAddresses]
   </DefiningQuery>
</EntitySet>

The EntitySet attributes are different from standard EntitySet definitions in the store layer. For the sake of comparison, here is the EntitySet for the Payments table:

<EntitySet Name="Payments" EntityType="BreakAwayModel.Store.Payments"
 store:Type="Tables" />

The DefiningQuery in the vOfficeAddresses EntitySet surfaces the results of the vOfficeAddresses view as a SELECT query. Rather than just duplicating the SQL of the existing view, it does a SELECT against the existing view. Since database views are most often read-only, using this SELECT explicitly restricts the Entity Framework from attempting to perform inserts, updates, or deletes against the view.

Because a database view has no primary key, the wizard infers an EntityKey by combining the non-nullable fields of the view. In the SSDL, the wizard also inserts a comment indicating this action:

<!--Errors Found During Generation:
warning 6002: The table/view BreakAway.dbo.vOfficeAddresses'
does not have a primary key defined. The key has been inferred and the
definition was created as a read-only table/view.
-->
<EntityType Name="vOfficeAddresses">
  <Key>
    <PropertyRef Name="FirstName" />
    <PropertyRef Name="LastName" />
    <PropertyRef Name="addressID" />
    <PropertyRef Name="AddressType" />
    <PropertyRef Name="ContactID" />
    <PropertyRef Name="ModifiedDate" />
  </Key>
  <-- Property Elements -->
</EntityType>

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 a DefiningQuery, there are other uses for DefiningQuery. 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, the Entity 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 an Entity

  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)<min(TripCostUSD)
UNION
SELECT r.reservationid, 0,MIN(events.[TripCostUSD]) AS cost
FROM reservations r
JOIN events ON  r.[EventID]=events.[EventID]
WHERE r.[ReservationID] NOT IN (SELECT reservationID FROM payments)
GROUP BY r.[ReservationID]

DefiningQuery Versus Stored Procedure

A DefiningQuery is comparable to a view in the database in that it does not take parameters. However, like other views in the model, you can query against the entity that is mapped to a DefiningQuery and add additional filters that become part of the query sent to the store. Additionally, since you are working with the entity that results, you can perform eager or deferred loading with related data.

A stored procedure, on the other hand, is seen as a function, not an entity. You cannot query a function. Also, a stored procedure is executed immediately; it is not deferred. From a maintenance perspective, when using a stored procedure or view directly, the database administrator can maintain the object in the database, and as long as the parameters and result schema of the procedure don't change, you're good to go. Changes to a view that is part of the model will be accounted for when you update the model.

This DefiningQuery will be a permanent addition to the BreakAway model, not the test model you used for QueryViews. Be sure to switch back to the BreakAway model when making these changes:

  1. Create a new entity in the Designer, named UnpaidReservation, and name its EntitySet UnpaidReservations.

  2. Add the following scalar properties which match the resultset of the query:

    • ReservationID (Type=Int32, Nullable=false)

    • PaymentTotal (Type=Decimal, Nullable=false)

    • Cost (Type=Int32, Nullable=false)

  3. Make ReservationID the EntityKey for this entity.

  4. Open the model in the XML Editor and scroll down to the EntityContainer element of the SSDL section. This is where you will add the EntitySet with the DefiningQuery.

  5. Add the EntitySet and DefiningQuery element:

    <EntitySet Name="UnpaidReservations"
   EntityType="BAModel.Store.UnpaidReservation">
  <DefiningQuery>

  </DefiningQuery>
</EntitySet>

    NOTE

    The additional attributes that the vOfficeAddress EntitySet uses (that is, store:Schema and store:Name) are not used here. Those attributes are necessary so that when the Update Model Wizard is called, the Designer knows how to resolve the views properly. Because the EntitySet you are creating is virtual and it does not exist in the database, those attributes are not required.

  1. Within the DefiningQuery tags, enter the stored procedure listed in Example 13-8, with one exception. The XML will be confused by the <, so you will need to replace it with an HTML-encoded version, &lt;. That line should look like this:

    HAVING SUM(amount)&lt;min(TripCostUSD

    Next, you'll need to create the virtual table to which the UnpaidReservation entity will map.

  2. In the section where EntityType elements are defined within the SSDL, add the UnpaidReservation virtual table:

    <EntityType Name="UnpaidReservation" >
  <Key>
    <PropertyRef Name="ReservationID" />
  </Key>
  <Property Name="ReservationID" Type="int" Nullable="false" />
  <Property Name="PaymentTotal" Type="money" Nullable="false" />
  <Property Name="Cost" Type="money" Nullable="false" />
</EntityType>

  3. Save and close the model, and then open it in the Designer so that you can map the entity to the virtual table you just created. The mapping should look like Figure 13-4.

Figure 13-4. Mapping the new entity to the new virtual table

13.6.3.1. Creating an association from the new entity

Now that you have the entity in the model, you can create an association back to the Reservation entity and tie right into the model and all of the other relationships. This will be a great benefit because you will be able to provide additional details from queries against UnpaidReservation.

NOTE

Although we know the relationship to be a One to Zero or One relationship, where there may be an UnpaidReservation entity for a particular reservation but never more than one, you should not define the association as a 1:0..1. This "virtual" entity will create a problem when you attempt to delete a reservation entity. The model constraints will expect you to delete an UnpaidReservation as well. With entities that are mapped to database tables, this is not a problem. In this case you can avoid this problem by defining a One to Many relationship between Reservation and UnpaidReservation.

You can do this in a few ways; one is by right-clicking the UnpaidReservation entity, and then choosing Add and then Association from the context menu. Fill out the New Association form as shown in Figure 13-5.

Figure 13-5. Creating a 1:* association between the new entity (UnpaidReservation) and the Reservation entity

The last step is to map the new association. Create the mapping so that it looks like Figure 13-6.

Figure 13-6. The Mapping Details window for the new association

13.6.3.2. Testing the DefiningQuery

Now you can see the DefiningQuery and the association in action.

Add the method in Example 13-9 into the test module you created at the beginning of this chapter, and then run it to see how UnpaidReservation is plugged right into the model, leveraging its relationship to the Reservation entity.

Example 13-9. Code to test the new DefiningQuery

Private Sub DefiningQuery()
  Using context As New BAEntities
    Dim unpaidReservations =
        From ur In context.UnpaidReservations _
                          .Include("Reservation.Customer") _
        Select ur
    For Each unpaid In unpaidReservations
      Dim custOwes = Convert.ToDecimal(unpaid.Cost-unpaid.PaymentTotal)
      Dim custname As String
        With unpaid.Reservation.Customer
          custname = .LastName.Trim & ", " & .FirstName.Trim
        End With
        Console.WriteLine("Cust: {0} owes {1} for the {2} Res", _
                          custname, custOwes,
                          unpaid.Reservation.ReservationDate)
    Next
  End Using
End Sub

private static void DefiningQuery()
{
  using (BAEntities context = new BAEntities())
  {
    var unpaidReservations =
        from ur in context.UnpaidReservations
                          .Include("Reservation.Customer")
        select ur;
    foreach (var unpaid in unpaidReservations)
    {
      var custOwes = Convert.ToDecimal(unpaid.Cost - unpaid.PaymentTotal);
      string custname = null;
      custname = unpaid.Reservation.Customer.LastName.Trim() + ", " +
                 unpaid.Reservation.Customer.FirstName.Trim();
      Console.WriteLine("Cust: {0} owes {1} for the {2} Reservation",
                        custname, custOwes,
                        unpaid.Reservation.ReservationDate);
    }
  }
}

This example demonstrates that the entity created by DefiningQuery behaves like any other entity. The query uses Include to show eager loading through its association to Reservation and then Reservation's association to Customer. After the data is returned, you can traverse the relationships to get at the rest of the information that makes UnpaidReservation much more meaningful.

NOTE

As you learned earlier, the Designer's Update Model Wizard overwrites portions of the SSDL content of the .edmx file. However, what it overwrites are the nodes that represent objects in the database. Because the DefiningQuery is independent of the data store, the elements you have added to the SSDL for this mapping should not be affected if you update the model using the wizard.

13.6.4. Using DefiningQuery to Solve More Complex Problems

DefiningQuery also allows you to solve more complex problems. Here is another quote from Mike Pizzo, taken from the MSDN forums, describing the ability to create mappings that you cannot create with entities that map directly to tables:

…with DefiningQuery, you can map multiple entities to the same table outside of a type hierarchy, or a single entity to multiple rows within a single table (I did a demo at TechEd where I mapped an "Activity" Entity to a Sharepoint schema in which the properties of the Activity were actually mapped to different rows within a single "universal" table according to a row ordinal). The list goes on… In fact, every time I think I've found a mapping scenario that we don't support in Entity Framework 1.0, I find a way to do it using DefiningQuery.


13.7. Using Commands That Affect the Persisted Database

So far, this chapter has focused on retrieving data from the database. In addition to the many views and stored procedures for read operations that have been implemented for your databases, you also probably have many Database Manipulation Language (DML) procedures for performing updates, inserts, and deletes.

In Chapter 6, you learned how to use the simplest form of these in the model, by performing function mapping for the Insert, Update, and Delete functions of particular entities. You also did this in an earlier chapter with the Payment entity. Yet you can use DML procedures in many other scenarios. Leveraging them in your model and using them with the Entity Framework is possible, if not always pretty.

13.7.1. DML Functions That Return Entities

The BreakAway database has a stored procedure called CreateCustomerfromClient. This is an important function for BreakAway's business model. The company has many contacts who are potential customers, yet they are not customers until they book their first trip. That's when BreakAway begins to track more details regarding the customer. Sometimes the company needs to create a new Customer that does not already have a Contact in the database, and the inheritance in the model takes care of that.

But if the Contact record already exists and you want to create a new Customer record to tie back to that Contact, inserting a Customer entity won't work, because that will attempt to insert a new Contact as well.

CreateCustomerfromClient solves this problem, and not only extends the Contact to be a Customer but also passes back the newly created customer so that it can be used immediately. It takes a ContactID as a parameter, inserts a new row into the Customer table using that ContactID, and then returns a complete Customer. The results map directly back to a Customer entity. Here is the T-SQL for the procedure:

INSERT INTO customers (ContactID,customers.[InitialDate])
VALUES (@contactid,GETDATE())

INSERT INTO ContactPersonalInfo (ContactID) VALUES (@contactid)

SELECT Customers.*,
 Contact.FirstName, Contact.LastName, Contact.Title, Contact.AddDate,
 Contact.ModifiedDate, CPI.BirthDate, CPI.HeightInches,
 CPI.WeightPounds, CPI.DietaryRestrictions
FROM Customers INNER JOIN
  Contact ON Customers.ContactID = Contact.ContactID INNER JOIN
  ContactPersonalInfo CPI ON Customers.ContactID = CPI.ContactID
  WHERE customers.contactid

You can create a FunctionImport for this stored procedure and set its return type to Customer. Then you can call the function pretty easily in your code. Example 13-10 shows a method that calls the function and retrieves the newly created customer.

Before it executes the function, it first tests to ensure that the contact already exists in the database and that it is not already a customer.

Example 13-10. Using a function to turn an existing contact into a customer

Public Function ConvertContacttoCustomer(ByVal contactID As Integer)
 As Customer
  Using context As New BAEntities
    Dim contact = context.Contacts _
                         .Where(Function(c) c.ContactID = contactID) _
                         .FirstOrDefault
    If contact IsNot Nothing Then
      If Not TypeOf (contact) Is Customer Then
       'remove contact from the context and then from memory
        context.Detach(contact)
        contact = Nothing
       'call the function which returns a customer
        Return context.CreateCustomerfromContact(contactID).First
      End If
    End If
  End Using
  Return Nothing
End Function

public static BAGA.Customer ConvertContacttoCustomer(int contactID)
{

  using (BAEntities context = new BAEntities())
  {
    var contact = context.Contacts
                         .Where((c) => 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 of EntityCommand's Execute methods to call these functions. Like the other ADO.NET Command classes, you can call ExecuteNonQuery, ExecuteScalar, or ExecuteReader from EntityCommand. You already used ExecuteReader 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 the database, 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:

<Function Name="CancelTrip" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="eventID" Type="int" Mode="In" />
</Function>

You can call this function in the same way you called the ScalarFunction 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 the SELECT 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 to None. 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 <CommandText> element of <Function>. 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 the InsertPayment 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:

<Function Name="InsertContactVirtual" IsComposable="false">
   <CommandText>
     INSERT INTO Contact
      ([FirstName]
     ,[LastName]
     ,[Title]
     ,[AddDate]
     ,[ModifiedDate])
     VALUES
      (@FirstName,@LastName,@Title,GETDATE(),GETDATE())
     SELECT SCOPE_IDENTITY() as ContactID
   </CommandText>
   <Parameter Name="FirstName" Type="nchar" Mode="In"/>
   <Parameter Name="LastName" Type="nchar" Mode="In"/>
   <Parameter Name="Title" Type="nchar" Mode="In"/>
</Function>

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 existing InsertContact function:

<Function Name="InsertContact" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="FirstName" Type="nchar" Mode="In" />
  <Parameter Name="LastName" Type="nchar" Mode="In" />
  <Parameter Name="Title" Type="nchar" Mode="In" />
</Function>

As described in Chapter 6, the two required attributes for a function are Name 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 a Customer in the BreakAway model, it will create and execute three separate commands: one for the Contact table, one for the Customer 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 the Customer, Contact, and ContactPersonalInfo tables, and InsertContact, UpdateContact, and DeleteContact interact with only the Contact table.

When the Entity Framework uses these stored procedures it will not overlap them. When saving changes to Customers it will call only the Customer entity's functions. When saving changes to Contacts it will call only the Contact 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:

<Function Name="ufnLBtoKG" ReturnType="nvarchar" Aggregate="false"
          BuiltIn="false" NiladicFunction="false" IsComposable="true"
          ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="Pounds" Type="int" Mode="In" />
</Function>

Notice that the IsComposable attribute is true. This is different from the stored procedures whose IsComposable attribute must be false. You can use UDFs as parts of queries.

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 the TREAT 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<DbDataRecord>(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 from EntityConnection.

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 client application.

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, EntityCollections won't exist on the client, as they are a class in System.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<Reservations>.

Another example is that you don't automatically get two-way relationship navigation. If you add a Reservation to a Customer, you will find that Reservation 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 and System.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<anonymous type> 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 DbDataRecords. 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

<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<ShortContact> GetCustomers()
{
  using (BAEntities Context = new BAEntities())
  {
    var query = from cust in Context.Contacts.OfType<Customer>()
                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

<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<Customer>()
        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

<?xml version="1.0" encoding="utf-8"?>
<Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns="http://tempuri.org/">
  <EntityKey>
    <EntitySetName>Contacts</EntitySetName>
    <EntityContainerName>BAEntities</EntityContainerName>
    <EntityKeyValues>
      <EntityKeyMember>
        <Key>ContactID</Key>
        <Value xsi:type="xsd:int">582</Value>
      </EntityKeyMember>
    </EntityKeyValues>
  </EntityKey>
  <ContactID>582</ContactID>
  <FirstName>Catherine</FirstName>
  <LastName>Abel</LastName>
  <Title>Ms.</Title>
  <AddDate>2003-12-14T12:57:40.893</AddDate>
  <ModifiedDate>2008-08-07T08:27:07.033</ModifiedDate>
  <TimeStamp>AAAAAAAAF/8=</TimeStamp>
  <InitialDate>2007-06-29T05:56:51.593</InitialDate>
  <Notes>test43</Notes>
  <BirthDate>1994-01-05T00:00:00</BirthDate>
  <HeightInches>55</HeightInches>
  <WeightPounds>124</WeightPounds>
  <DietaryRestrictions>
</DietaryRestrictions>
  <CustomerTypeReference>
    <EntityKey>
      <EntitySetName>CustomerTypes</EntitySetName>
      <EntityContainerName>BAEntities</EntityContainerName>
      <EntityKeyValues>
        <EntityKeyMember>
          <Key>CustomerTypeID</Key>
          <Value xsi:type="xsd:int">3</Value>
        </EntityKeyMember>
      </EntityKeyValues>
    </EntityKey>
  </CustomerTypeReference>
  <PrimaryDestinationReference>
    <EntityKey>
      <EntitySetName>Destinations</EntitySetName>
      <EntityContainerName>BAEntities</EntityContainerName>
      <EntityKeyValues>
        <EntityKeyMember>
          <Key>DestinationID</Key>
          <Value xsi:type="xsd:int">49</Value>
        </EntityKeyMember>
      </EntityKeyValues>
    </EntityKey>
  </PrimaryDestinationReference>
  <SecondaryDestinationReference>
    <EntityKey>
      <EntitySetName>Destinations</EntitySetName>
      <EntityContainerName>BAEntities</EntityContainerName>
      <EntityKeyValues>
        <EntityKeyMember>
          <Key>DestinationID</Key>
          <Value xsi:type="xsd:int">52</Value>
        </EntityKeyMember>
      </EntityKeyValues>
    </EntityKey>
  </SecondaryDestinationReference>
  <PrimaryActivityReference>
    <EntityKey>
      <EntitySetName>Activities</EntitySetName>
      <EntityContainerName>BAEntities</EntityContainerName>
      <EntityKeyValues>
        <EntityKeyMember>
          <Key>ActivityID</Key>
          <Value xsi:type="xsd:int">16</Value>
        </EntityKeyMember>
      </EntityKeyValues>
    </EntityKey>
  </PrimaryActivityReference>
  <SecondaryActivityReference>
    <EntityKey>
      <EntitySetName>Activities</EntitySetName>
      <EntityContainerName>BAEntities</EntityContainerName>
      <EntityKeyValues>
        <EntityKeyMember>
          <Key>ActivityID</Key>
          <Value xsi:type="xsd:int">19</Value>
        </EntityKeyMember>
      </EntityKeyValues>
    </EntityKey>
  </SecondaryActivityReference>
</Customer>

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 for ShortContact, 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

<ShortContact>
 <Name>Abel, Catherine</Name>
 <ContactID>582</ContactID>
</ShortContact>

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

    <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

    <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<Customer>()
                          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 the ObjectContext. 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 ObjectContext nor the ObjectStateEntry is serialized. Therefore, when the customer is deserialized on the receiving end, it has only its current values. 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):

    1. The context inspects the EDM's metadata and learns that in the Contacts EntitySet, the CustomerID property is used for the EntityKey.

    2. Next, the context looks at custEdit and determines that the CustomerID value is 123.

    3. 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.

    4. 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 DataSources 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 GetCustomer method in the ComboBox's SelectIndexChanged event and bind the results to the _currentCustomer variable, which in turn you will bind to the 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 the Customer 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 DataContracts 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

    <OperationContract()> _
Function GetCustsandTrips() As String

<OperationContract()> _
Function GetCustomer(ByVal custID As Integer) As Customer

<OperationContract()> _
Function UpdateCustomer(ByVal cust As Customer) As String

<OperationContract()> _
Function InsertCustomer(ByVal cust As Customer) As String

<OperationContract()> _
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 DataContracts, 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

<DataContract() _
Public Class ShortContact
  Private _ContactID As Integer
  Private _Name As String

  <DataMember()> _
  Public Property Name() As String
    Get
      Return _Name
    End Get
    Set(ByVal value As String)
      _Name = value
    End Set
  End Property
  <DataMember()> _
  Public Property ContactID() As Integer
    Get
      Return _ContactID
    End Get
    Set(ByVal value As Integer)
      _ContactID = value
    End Set
  End Property
End Class

<DataContract()> _
Public Class CustsandTrips
  Private _custs As List(Of ShortContact)
  Private _trips As List(Of Trip)
  <DataMember()> _
  Public Property Custs() As List(Of ShortContact)
    Get
      Return _custs
    End Get
    Set(ByVal value As List(Of ShortContact))
      _custs = value
    End Set
  End Property
  <DataMember()> _
  Public Property Trips() As List(Of Trip)
    Get
      Return _trips
    End Get
    Set(ByVal value As List(Of Trip))
      _trips = value
    End Set
  End Property
End Class

<DataContract()> _
Public Class CusttoEdit
  Private _customer As Customer
  Private _reservationsToDelete As List(Of Integer)
  <DataMember()> _
  Public Property Customer() As BAGA.BreakAwayModel.Customer
    Get
      Return _customer
    End Get
    Set(ByVal value As BAGA.BreakAwayModel.Customer)
      _customer = value
    End Set
  End Property
  <DataMember()> _
  Public Property ReservationsToDelete() As List(Of Integer)
    Get
      Return _reservationsToDelete
    End Get
    Set(ByVal value As List(Of Integer))
      _reservationsToDelete = value
    End Set
  End Property
End Class

[DataContract]
public class ShortContact
{
  int _contactID;
  string _Name;
  [DataMember]
  public int ContactID
  {
    get { return _contactID; }
    set { _contactID = value; }
  }
  [DataMember]
  public string Name
  {
    get { return _Name; }
    set { _Name = value; }
  }
}

[DataContract()]
public class CustsandTrips
{
  private List<ShortContact> _custs;
  private List<Trip> _trips;

  [DataMember()]
  public List<ShortContact> Custs
  {
    get  {  return _custs;  }
    set  {  _custs = value; }
  }

  [DataMember()]
  public List<Trip> Trips
  {
    get  {  return _trips;  }
    set  {  _trips = value; }
  }
}

[DataContract()]
public class CusttoEdit
{
  private Customer _customer;
  private List<int> _reservationsToDelete;

  [DataMember()]
  public BAGA.BreakAwayModel.Customer Customer
  {
    get  {  return _customer;  }
    set  {  _customer = value; }
  }

  [DataMember()]
  public List<int> ReservationsToDelete
  {
    get {   return _reservationsToDelete;  }
    set {   _reservationsToDelete = value; }
  }
}

Modify the GetCustsandTrips operation so that it returns a CustsandTrips type, as shown in Example 14-18.

Example 14-18. Fixing the GetCustsandTrips operation signature

<OperationContract()> _
Function GetCustsandTrips() As CustsandTrips

[OperationContract]
CustsandTrips GetCustsandTrips();

Modify the UpdateCustomer operation so that it takes a CusttoEdit type, as shown in Example 14-19.

Example 14-19. Fixing the UpdateCustomer operation signature

<OperationContract()> _
  Function UpdateCustomer(ByVal cust As CusttoEdit) As String

[OperationContract]
string UpdateCustomer(CusttoEdit cust);

14.3.1.4. Enabling the model's partial class properties for Trip and Reservation to participate in the service

Although the code-generated entity classes and their properties are marked with DataContract and DataMember attributes by default, the custom properties that you have created are not. The client can benefit from the TripDetail property that you added to the Trip class in customizing_entities in Chapter 10. To make this property available as part of the WCF service payload, you'll need to add the DataMember attribute.

You have one more rule to satisfy. DataMembers must have both a getter and a setter to be serializable. Otherwise, you will get an error in the service that is using the class.

Open the Trip's partial class in the model project and modify the TripDetail property by adding the DataMember attribute and the SET clause, as shown in Example 14-20.

Example 14-20. Modifying the Trip in the partial class of the EDM

<System.Runtime.Serialization.DataMember()> _
Public Property TripDetails() As String
  Get
    ...existing code...
  End Get
  Set(ByVal value As String)
  End Set
End Property

[System.Runtime.Serialization.DataMember]
public string TripDetails
{
  get
  {
    //existing code
  }
  set{}
}

Do the same for the TripDetails property of the Reservation partial class so that you can use Reservation.TripDetails on the client side.

14.3.1.5. Implementing the service interface

Now it's time to add some logic to the operations that are based on the interface. You'll do this in the Service1 class.

  1. Remove the default methods.

  2. Implement the IService interface.

    You do this differently in VB and C#. In VB, place your cursor at the end of the Implements IService1 line of code and press the Enter key. In C#, right-click on IService1 in the class declaration, select Implement Interface from the context menu, and select Implement Interface from that context menu's submenu. All of the methods defined in the interface will be stubbed out for you.

  3. Import the BAGA namespace into the class with using or Imports.

  4. Add the code for GetCustsandTrips (see Example 14-21).

    This contains the same query as the GetCustomers method in the web service, but it also queries for trips with their location information. The results of both queries are packaged into a CustsandTrips object to return to the client.

    Example 14-21. Filling in the logic for the CustsandTrips method

    Public Function GetCustsandTrips() As CustsandTrips _
 Implements IService1.GetCustsandTrips
  Dim ct As New CustsandTrips
  Using Context As New BAEntities
    Dim custquery =
        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.Trim}
    ct.Custs = custquery.ToList
    Dim tripQuery = From trip In context.Trips.Include("Destination")
    ct.Trips = tripQuery.ToList
  End Using
  Return ct
End Function

    public CustsandTrips GetCustsandTrips()
{
  CustsandTrips ct = new CustsandTrips();
  using (var context = new BAEntities())
  {
    var custquery = from cust in context.Contacts.OfType<Customer>()
                    orderby cust.LastName, cust.FirstName
                    select new ShortContact
                    {
                      ContactID = cust.ContactID,
                      Name = cust.LastName.Trim()
                        + ", " + cust.FirstName.Trim()
                    };
    ct.Custs = custquery.ToList();
    var tripQuery = from trip in context.Trips.Include("Destination")
                    select trip;
    ct.Trips = tripQuery.ToList();
  }
  return ct;
}

  5. Add the code for GetCustomer (see Example 14-22).

    Here is where things start to differ from the web service. You will return a customer graph that includes the customer's reservations, the trip information for the reservations, and the location information for the trips. This way, you will be able to build a descriptive list of reservations for the customer on the client side.

    Example 14-22. Logic for the GetCustomer method

    Public Function GetCustomer(ByVal custid As Integer) As BAGA.Customer _
 Implements IService1.GetCustomer
  Using context As New BAEntities
    Dim cust = From c In context.Contacts.OfType(Of Customer). _
               Include("Reservations.Trip.Destination") _
               Where c.ContactID = custID _
               Select c
    Return cust.FirstOrDefault
  End Using
End Function

    public Customer GetCustomer(int custID)
{
  using (BAEntities context = new BAEntities())
  {
    var cust =
        from c in context.Contacts.OfType<Customer>()
          .Include("Reservations.Trip.Destination")
        where c.ContactID == custID
        select c;
    return cust.FirstOrDefault();
  }
}

  6. Add the code in Example 14-23 to the InsertCustomer method.

    Example 14-23. Code for the InsertCustomer method

    Public Function InsertCustomer(ByVal cust As BAGA.Customer) As String _
 Implements IService1.InsertCustomer

  Try
    Using context As New BAEntities
      RemoveTripsfromGraph(cust)
      context.AddToContacts(cust)
      context.SaveChanges()
    End Using
    Return cust.ContactID.ToString
  Catch ex As Exception
    Return "error: " & ex.Message
  End Try
End Function

    public string InsertCustomer(BAGA.Customer cust)
{
  try
  {
    using (BAEntities context = new BAEntities())
    {
      RemoveTripsfromGraph(cust);
      context.AddToContacts(cust);
      context.SaveChanges();
    }
    return cust.ContactID.ToString();
  }
  catch (Exception ex)
  {
    return "error: " + ex.Message;
  }
}

    This method is nearly the same as InsertCustomer in the web service. Thanks to the way relationship spanning works, by adding the new Customer to the context, you are also adding the reservations that are attached to it. You may have noticed a new method being called in Example 14-23: RemoveTripsfromGraph. This method is related to an issue with adding entity graphs to the context. We will discuss this method in more detail in the following section.

Testing the WCF Services and Operations

Although you can verify that the service runs by viewing it in the browser, you cannot test the operations as you can with an ASMX Web Service. A WCFTestClient utility is available, but it does not display responses that contain entities. Testing the WCF service to see the payload involves trace logging and a number of steps, the description of which is not within the scope of this book. Furthermore, in the end the response is so gnarly that it doesn't make sense to try to look at it here. This is a good reminder of why you should not return entity objects to non-.NET consumers. However, when you get to the client side of this walkthrough, you will be able to debug into the returned Customer and see that the Reservation, Trip, and Location objects have come over in their entirety as part of the customer graph.

14.3.1.6. Adding graphs to ObjectContext

The concept of relationship spanning and its rules may make it easy to add a graph to a context, yet they have a limitation. Because you are adding the new Customer, everything in the graph will be treated as something to be added. That's very handy for Reservations, but what about Reservation.Trip? Reservation.Trip will actually cause the Add to fail because the Trip entity came from the database and has an EntityKey. When the context attempts to add the trip, an exception will be thrown. The fact that it has an EntityKey tells the context that it is not a new Trip and therefore cannot be added.

How do you add some things from a graph and not others? You need to disassemble part of the graph before it is added to the context, which is not an obvious task.

The reservation will have a Trip entity as the value of Reservation.Trip, but Reservation.TripReference will not contain the trip information when a new customer with a new reservation comes in from the client. To add the graph to the context, you need this to be completely reversed. You cannot add the reservation to the context with the trip attached (SaveChanges will treat Trip as a new item), yet you need the TripReference populated for the foreign key (TripID) to be included in the reservation. The RemoveTripsfromGraph method shows how to solve this puzzle.

Add the RemoveTripsfromGraph method to the Service1 class, as shown in the following code:

Private Sub RemoveTripsfromGraph(ByRef cust As Customer)
  For Each r In cust.Reservations
    Dim tripEntityKey = r.Trip.EntityKey
    r.Trip = Nothing
    r.TripReference.EntityKey = tripEntityKey
  Next
End Sub

private void RemoveTripsfromGraph(Customer cust)
{
  foreach (var r in cust.Reservations)
  {
    var tripEntityKey = r.Trip.EntityKey;
    r.Trip = null;
    r.TripReference.EntityKey = tripEntityKey;
  }
}

What is going on in this little routine after a Customer is passed in? The code iterates through each reservation. It grabs an instance of the EntityKey for that Reservation's Trip, and then drops the Trip. The tripEntityKey retains its values even after the instance of the Trip is gone. Figure 14-8 shows that the value of the EntityKey is still intact even though r.Trip is null.

Figure 14-8. The EntityKey for the Trip, which will be used to replace the Trip entity with a TripReference.EntityKey

With the Trip removed, the context will not attempt to add it. However, the Reservation still needs to know which trip the customer is going on. The next line achieves that by setting the EntityKey of the TripReference. You saw this method used in previous examples, beginning with the first Windows Forms application you built in Chapter 8. Rather than having a Trip entity, having the EntityKey is enough. Figure 14-9 shows the Reference with the Trip attached, and then the Reference with the Trip removed but the EntityKey tied to the TripReference. This will provide enough information to populate the foreign key value in the database record when the insert is performed.

Figure 14-9. The RemoveTripsfromGraph method, which changes the Reservation's Trip from an entity to an EntityReference

14.3.1.7. Deleting objects

Deleting the Customer requires another involved piece of logic, as deleting the Customer means deleting the Customer's Reservations. A referential constraint in the BreakAway database says that every Reservation must be related to a Customer. If you attempt to delete a customer that has reservations, the database will throw an error because it won't allow orphaned reservations.

NOTE

It is possible in the database to enforce cascading deletes, which means that if the customer is deleted, the database will automatically delete any dependent reservations. In this case, you need to delete only the customer, and the database will handle the rest. You'll learn more about referential constraints and cascading deletes in Chapter 15.

In the meantime, the DeleteCustomer routine will need to explicitly delete all of the related Reservations for the Customer object. Don't forget that the Customer is derived from a contact. The Customer record is only an extension of a Contact record. Therefore, a business decision is involved here: will the Contact record be deleted? In the case of BreakAway Geek Adventures, the rule is not to delete customers and reservation history, but for the sake of your education, we have permission to enable it in this service. So, let's get coding.

To call the DeleteObject method of ObjectContext, the object to be deleted must be in the cache. Therefore, you'll first need to query for the customer using the custID that the client will pass in. Then you can delete the customer and its reservations. Delete the reservations first. Otherwise, when you delete the customer, its relationship to the reservations will be deleted and you won't be able to find the reservations as easily.

Add the code in Example 14-24 to the DeleteCustomer method.

Example 14-24. Code for the DeleteCustomer method

Public Function DeleteCustomer(ByVal custID As Integer) As String _
 Implements IService1.DeleteCustomer
  Using context As New BAEntities
    Try
      Dim custtoDelete = (From cust In context.Contacts _
                          .OfType(Of Customer).Include("Reservations") _
                           Where cust.ContactID = custID Select cust) _
                         .FirstOrDefault
      If Not custtoDelete Is Nothing Then
        For i As Integer = custtoDelete.Reservations.Count - 1 To 0 Step -1
         Dim res = custtoDelete.Reservations.ToArray()(i)
         context.DeleteObject(res)
        Next i
        context.DeleteObject(custtoDelete)
      End If
      context.SaveChanges()
    Catch ex As Exception
      Return ex.Message
    End Try
  End Using
End Function

public string DeleteCustomer(int custID)
{
  using (BAEntities context = new BAEntities())
  {
    try
    {
      var custtoDelete = (
              from cust in context.Contacts.OfType<Customer>()
                .Include("Reservations")
              where cust.ContactID == custID
              select cust).FirstOrDefault();
      if (custtoDelete != null)
      {
       for (int i = custtoDelete.Reservations.Count - 1; i >= 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 Reservations 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 Reservations 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.

Reservations 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<Customer>()
                                        .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 an InvalidOperationException 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 ReservationIDs 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 of ReservationIDs 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<int> 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 rightwards double arrow 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<int>();
    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 Reservations 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 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<Reservation>();
  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 cboTrips 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:

  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 EntityStates 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 an EntitySet is mapped using a QueryView, every other related entity also needs to be mapped using a QueryView. 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 same importance 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 EntityObjects. 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, Associations 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 an Address 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 when SaveChanges 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 a Customer 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 the ObjectStateEntry 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 the ObjectContext and you join it to an entity that is in the ObjectContext, 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 the ObjectContext 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 the ObjectContext. 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 correct Contact 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 to myAddress.Contact when myAddress and its Contact are not attached to the context will merely return the Contact 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 in Example 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 in Figure 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 first EntityEntry is an actual entity. It's the Reservation you queried for, as you can see in Figure 15-8.

Figure 15-8. The ObjectStateEntry for the queried Reservation

The last three EntityEntries are placeholders for the EntityKeys of the related data—for example, the related Contact 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, the ObjectContext 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 the Contact, one for the related Trip, and one for the UnpaidReservation entity (which you created in Chapter 13). If the true entity is brought into the context at some point, the Entity 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 an EntityCollection. 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 the EntityKey for ContactReference. Even if the contact does not exist in memory, the ContactReference property provides the minimal information about the Contact 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 the Reservation 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 EntityCollections in many of the examples in this book already. Although you'll probably work with EntityCollection most frequently through a navigation property, it is also possible to instantiate an EntityCollection 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 the ObjectContext 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 call SaveChanges.

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 until SaveChanges 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 a Contact 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 in Chapter 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 the Key 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 in Example 15-5.

Example 15-5. An Association's OnDelete action set to Cascade as defined in the CSDL

<Association Name="FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID">
  <End Role="SalesOrderHeader"
       Type="AdventureWorksLTModel.SalesOrderHeader" Multiplicity="1">
    <OnDelete Action="Cascade" />
  </End>
  <End Role="SalesOrderDetail"
       Type="AdventureWorksLTModel.SalesOrderDetail" Multiplicity="*"/>
  <ReferentialConstraint>
    <Principal Role="SalesOrderHeader">
      <PropertyRef Name="SalesOrderID" />
    </Principal>
    <Dependent Role="SalesOrderDetail">
      <PropertyRef Name="SalesOrderID" />
    </Dependent>
  </ReferentialConstraint>
</Association>

The following code spins up a new ObjectContext and queries for the first SalesOrderHeader along with its SalesOrderDetails. In this case, two SalesOrderDetails are returned along with the SalesOrderHeader. Then the SalesOrderHeader is deleted from the context, after which the ObjectStateManager is queried for all objects whose state is Deleted:

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: the Order entity and the two Detail 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 the SalesOrderHeader and the two SalesOrderDetails, 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 the EntityReference using only an EntityKey. This requires that you know the value of the EntityReference'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 default CustomerType 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 actual CustomerType object into memory. When the Customer is saved, the CustomerTypeReference is resolved, and in the database the Customer record has its CustomerTypeID foreign key value set to 1.

Synchronizing EntityReference.Value and Its EntityKey

The ObjectContext automatically synchronizes the value of the EntityReference (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 the Value.

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 can Load, 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 the Load method earlier in the book, here we will dig a little deeper.

You can call Load from entities that are attached to the ObjectContext. 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 call Load from detached entities: when the entity was created as a result of a NoTracking 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()  '<--succeeds

var query = context.Reservations;
query.MergeOption= System.Data.Objects.MergeOption.NoTracking;
var res = query.First();
res.CustomerReference.Load(); //<--succeeds

However, if you detach an entity from the context, calling Load will cause an exception to be thrown, as shown in Example 15-8.

Example 15-8. A failed attempt to call Load on a detached entity

Dim query = context.Reservations
Dim res = query.First
context.Detach(res)
res.CustomerReference.Load()  '<--InvalidOperationException

var query = context.Reservations;
var res = query.First();
context.Detach(res);
res.CustomerReference.Load();  //<--InvalidOperationException

15.3.4.1. Load provides behind-the-scenes query creation and execution

Calling the Load method on an EntityCollection or EntityReference will force the Entity Framework to create and execute a query against the data store to retrieve the related entities. This is why the code in Example 15-7 will fail. Load requires an ObjectContext to create and execute the query.

For example, Contact.Addresses.Load will cause a query to automatically run against the data store for all of the contact's addresses. The only overload for Load is to include MergeOptions, but you cannot provide a filter to control which of the contact's addresses are to be loaded from the database. If you have a conditional mapping defined for the Address entity, it will be honored.

Lazy Loading Versus Deferred Loading

Many data access frameworks, including LINQ to SQL, support implicit lazy loading by default. The Entity Framework does not. Instead, it supports explicit lazy loading. What's the difference? Implicit lazy loading does not require the developer to explicitly call a Load method. If the related data is not loaded at the time it is requested, it will be automatically retrieved for you. The Entity Framework, on the other hand, requires that you explicitly load the data. The thinking behind the Entity Framework's Load method requirement is the now familiar rule that the Entity Framework will not do something you did not ask it to do. Implicitly making connections and round trips to the database is costly. There seems to be a semantic difference in how some developers use the term lazy loading. You may hear people say that the Entity Framework supports lazy loading, but in that case they really mean explicit lazy loading, also known as deferred loading.

Although version 1 of the Entity Framework doesn't support implicit lazy loading, the ADO.NET team's MSDN Code Gallery project provides a code example that shows how you can implement lazy loading in the Entity Framework. Also, optional lazy loading is targeted for the next version of the Entity Framework. You can read more on Product Manager Tim Mallalieu's blog post from June 24, 2008, at http://blogs.msdn.com/timmall/.

15.3.4.2. Understanding why Load cannot be called on Added entities

Because of the way Load creates and executes queries, there are other scenarios where you cannot call Load besides not being able to call it from a Detached entity. One of these may not make sense, but with some detective work, you can come to understand the reason. The scenario is that you cannot call Load on objects whose EntityState is Added.

The short answer to "but why?" is that Load requires an EntityKey with an appropriate ID. The temporary EntityKey assigned to Added entities is insufficient. This makes sense if you want to load the children of an entity, but it does not seem to make sense if you wanted to load an EntityReference for a new entity. As an example, consider a new Reservation where you have identified the Reservation's Customer using CustomerReference.EntityKey. If you have the Customer's EntityKey, it seems very logical that you should be able to load the customer. Example 15-9 attempts to do this.

Example 15-9. A failed attempt to call Load on an entity whose EntityState is Added

Dim newAddress = Address.CreateAddress(0, "Home", Now())
context.AddToAddresses(newAddress)
newAddress.ContactReference.EntityKey = _
   New EntityKey("BAEntities.Contacts", "ContactID", 100, Nothing)
newAddress.ContactReference.Load()  '<--InvalidOperationException

var newAddress = Address.CreateAddress(0, "Home",Now);
context.AddToAddresses(newAddress);
newAddress.ContactReference.EntityKey =
 new EntityKey("BAEntities.Contacts", "ContactID", 100);
newAddress.ContactReference.Load(); //<--InvalidOperationException

If you receive an exception on the call to CreateAddress telling you it does not take three arguments, the problem is that Address.TimeStamp needs a default value in the model. Go back to the model and change the Default for Address.TimeStamp to 0x123. If you enter it in the Properties window, don't put any quotes around this value.

The call to Load will throw an exception explaining that you can't call Load on an Added entity (with some additional details). The reason is that the query to load the data is not constructed the way you might expect.

If you were writing the query, you would probably use SQL like this:

SELECT * from Contact WHERE ContactID=100

But the Entity Framework does not construct Load queries in this way.

The Entity Framework needs to have a single way to construct Load queries for both EntityReference and EntityCollection. The common denominator between loading EntityReferences and EntityCollections is the EntityKey of the calling entity; therefore, the calling entity needs to exist in the database as well. Look at the native T-SQL query constructed to load the reservation of a payment based on the code in Example 15-10.

Example 15-10. Using Load on an EntityReference

Dim firstPayment = context.Payments.First
firstPayment.ReservationReference.Load()

var firstPayment = context.Payments.First();
firstPayment.ReservationReference.Load();

Even though the Payment already has the EntityKey of the Reservation it needs, the native query shown in Example 15-11 is based on only the PaymentID value.

Example 15-11. The T-SQL that results from the Load method in Example 15-9

SELECT 1 AS [C1],
[Extent2].[ReservationID] AS [ReservationID],
[Extent2].[ReservationDate] AS [ReservationDate],
[Extent2].[ContactID] AS [ContactID],
[Extent2].[EventID] AS [EventID]
FROM  [dbo].[Payments] AS [Extent1]
INNER JOIN [dbo].[Reservations] AS [Extent2]
    ON [Extent1].[ReservationID] = [Extent2].[ReservationID]
WHERE ([Extent1].[ReservationID] IS NOT NULL)
   AND ([Extent1].[PaymentID] = @EntityKeyValue1)

Parameter: @EntityKeyValue1=1

The query is doing the following, in the order shown:

  1. Using the Payment's PaymentID value to locate the ReservationID

  2. Performing a JOIN on the Reservation table based on the common ReservationID

  3. Returning the appropriate fields from the Reservation table to map to the Reservation entity

What happens when you load children? In Example 15-12, Load is called on an existing parent (Reservation) in order to load its children (Payments).

Example 15-12. Using Load to load an EntityCollection

Dim firstRes = context.Reservations.First
firstRes.Payments.Load()

var firstRes = context.Reservations.First();
firstRes.Payments.Load();

When Load is called, you again can see, in the native query shown in Example 15-13, that the calling entity's key ID is used for the query. In this case, that is the ReservationID and its value is 1.

Example 15-13. The T-SQL that results from calling EntityCollection.Load

SELECT AS [C1],
[Extent1].[PaymentID] AS [PaymentID],
[Extent1].[PaymentDate] AS [PaymentDate],
[Extent1].[Amount] AS [Amount],
[Extent1].[ModifiedDate] AS [ModifiedDate],
[Extent1].[PaidinFull] AS [PaidinFull],
[Extent1].[ReservationID] AS [ReservationID]
FROM [dbo].[Payments] AS [Extent1]
WHERE ([Extent1].[ReservationID] IS NOT NULL)
AND ([Extent1].[ReservationID] = @EntityKeyValue1)

Parameter:
@EntityKeyValue1=1

Now this query seems more natural—it looks into the Payments table for all payments with ReservationID=1.

Because of the need to construct both queries (as a result of EntityReference.Load and EntityCollection.Load) using the ID of the calling entity, the calling entity needs to be in the database already. In the case of an Added entity, there's a very good chance that the calling entity does not yet exist in the data store. So, it's a reasonable rule to completely disallow Load on Added entities.

NOTE

In the course of deciphering this rule, you now have gained a lot of insight into the logic behind Load queries. For some of us, this is truly geeky fun. Others might want a short reprieve before continuing.

Now let's move on to other ways of defining relationships between entities.

15.3.5. EntityCollection.Add

Example:

Contact.Addresses.Add(myNewAddress)

You use Add to add items to an EntityCollection. You can use Add only on EntityCollection. It isn't valid with EntityReference.

Again, because of the varying states of an entity, you can use Add in a few different ways.

15.3.5.1. Adding new entities that are detached

You can use Add to add a new entity that is not yet attached to the ObjectContext. Add will first add the entity to the ObjectContext and will then add it to the EntityCollection of the calling entity. This requires that the entity have no EntityKey; otherwise, it will throw an exception. Any entity with an EntityKey is presumed to have come from the data store, and therefore it can't be treated as a new record.

For instance, the code in Example 15-14 queries for a Customer from the database, creates a new Address in memory, and then adds the Address to the Customer's Addresses collection.

Example 15-14. Adding a new, detached entity to another entity

Using context As New BAEntities
  Dim con = (From c In context.Contacts.Include("Addresses")).First
  Dim newAdd = Address.CreateAddress(0, "home", Now);)
   With newAdd
      .Street1 = "1 Main"
      .City = "Hamburg"
      .StateProvince = "NY"
    End With
  con.Addresses.Add(newAdd)
End Using

using (BAEntities context = new BAEntities())
{
  var con = (from c in context.Contacts.Include("Addresses") select c)
            .First();
  var newAdd = Address.CreateAddress(0, "home", Now);
  newAdd.Street1 = "1 Main";
  newAdd.City = "Hamburg";
  newAdd.StateProvince = "NY";
  con.Addresses.Add(newAdd);
}

When this is complete, newAdd.Contact returns the Contact entity and newAdd.ContactReference is populated with an EntityKey and the Value. The newAdd object instance is attached to the context with a temporary EntityKey and an EntityState of Added.

15.3.5.2. Adding new or existing entities that are attached

You can use Add to add entities that are already attached to the ObjectContext, regardless of whether or not they are new.

15.3.5.3. Adding entities to the EntityCollection of a detached object

If the calling entity is detached from the context, Add will be treated as a CLR method, as shown in Example 15-15.

Example 15-15. Adding to the EntityCollection of a detached entity

Dim firstRes = context.Reservations.First
context.Detach(firstRes)
Dim newPayment = New Payment
newPayment.Amount = 100
firstRes.Payments.Add(newPayment)

var firstRes = context.Reservations.First();
context.Detach(firstRes);
var newPayment = new Payment();
newPayment.Amount = 100;
firstRes.Payments.Add(newPayment);

In this case, the newPayment entity will be added to the Payments collection and you'll be able to navigate from the Payment to the Reservation (newPayment.Reservation). But because the ObjectContext is not involved, no Relationship object is created, and therefore newPayment.ReservationReference.EntityKey will be null, as shown in Figure 15-14.

Figure 15-14. A new entity that was added to the EntityCollection of a detached entity

15.3.6. Attach and Remove

Examples:

Contact.Addresses.Attach(myAddress)
Address.ContactReference.Attach(contact)

In addition to ObjectContext.Attach, an Attach method exists for IRelatedEnd, which you can use for EntityCollections and EntityReferences.

Using the Attach method, you can define relationships between entities that already exist in the ObjectContext but that have not been connected automatically.

EntityCollection.Attach is more commonly used than EntityReference.Attach. Although it is technically possible to use the latter, it is not generally necessary because it is so much easier to merely assign the EntityReference value.

EntityCollection.Remove is used to remove an entity from a collection. It will stay in the ObjectContext, but will no longer be related to a parent in that particular association. There is no Remove method for EntityReference.

NOTE

When removing entities from an EntityCollection, be careful not to orphan entities. If you leave a child entity without a required parent, you will get an EntityReferenceException when you call SaveChanges.

15.3.6.1. Use Attach only when a relationship exists in the data store

EntityCollection.Attach is necessary in only a few edge cases, which occur when you are attempting to reconstruct a graph from data that may have otherwise lost its EntityReference.EntityKey. This could happen, for example, if you are serializing groups of data to move across tiers. Although binary serialization and Windows Communication Foundation (WCF) serialization maintain graphs, XML serialization (used in ASMX Web Services) does not, and therefore it is necessary to transmit the data in a different format. If you have the Entity Framework in the client and service, chances are the EntityReference.EntityKey property is intact. But if your client application is not using the Entity Framework (as in the examples in this chapter), the EntityReferences won't exist. This is a scenario in which EntityCollection.Attach would help you rebuild the graph from preexisting data when it gets back to the service. An entity in this state—for example, an Address that has an EntityKey because it came from the data store, but whose Contact, ContactReference.EntityKey, and ContactReference.Value properties are null—is the correct target of Attach.

Other than this, you'll encounter a lot of rules if you try to use Attach where it doesn't belong. Here are a few places where you might think you want to use Attach, but you will run into trouble. Most of these problems occur when you are not following the main purpose of Attach, which is to connect entities that are already attached to the ObjectContext and are not new.

  • You cannot use Attach to attach another EntityCollection; however, it is possible to attach an IEnumerable (discussed shortly). Otherwise, you need to attach one entity at a time.

  • You cannot use Attach when the EntityState of either end is Detached. ObjectContext is required to work out the relationship. Do an ObjectContext.Attach first.

  • You cannot use Attach when the ends are in different contexts.

  • You cannot use Attach to attach entities whose EntityState is Added. You'll want to use Add in this case.

  • You cannot use Attach to attach entities whose EntityState is Deleted. It's not possible to create a RelationshipEntry for a deleted entity.

  • You cannot use Attach to reassign entities to another graph.

15.3.7. Attach Versus Add

Whether you are calling Attach or Add from ObjectContext or from IRelatedEnd, it's important to understand the difference between these two methods.

Add will create an ObjectStateEntry with EntityState=Added. If you are using the ObjectContext.Add method, the EntityState is impacted on the entity that you are adding. If you are calling the EntityCollection.Add method, it is the relationship whose EntityState is affected. When SaveChanges is called the entity that was added to the context will be inserted in the database and the relationship that was created will be persisted in the database.

Attach will create an ObjectStateEntry whose EntityState=Unchanged. If you are calling the ObjectContext.Attach method, the resulting ObjectStateEntry will have a clean slate. SaveChanges will do nothing with that entity. If you are calling EntityCollection.Attach, a RelationshipEntry will be created and it will have a clean slate. Nothing will happen with respect to this relationship when SaveChanges is called.

When you use the CLR properties to set the entity value, Add and, if necessary, Remove are called under the covers.

15.3.8. Moving an Entity to a New Graph

Often you will want to move an entity from one graph to another, perhaps for the simple reason that an end user applied a payment to the wrong reservation.

In such a case, you can simply reassign the Reservation property:

myPayment.Reservation=myReservation

or add the payment to the Payments EntityCollection of the new reservation:

myReservation.Payments.Add(myPayment)

The ObjectContext will resolve the existing relationship. If you were to inspect the relationships in the context after the change, you would find a RelationshipEntry linking the payment to the original reservation and another RelationshipEntry linking the payment to the new reservation. The EntityState of the first entry would be Deleted, while the EntityState of the second entry would be Added.

Remember that Attach will not work here. Attach is for creating a relationship in the context to reflect a relationship that already exists in the database. In the same way that ObjectContext.Attach renders an entity's EntityState as Unchanged, this Attach will render the relationship's EntityState as Unchanged. Therefore, the relationship modification you performed will not be applied during the call to SaveChanges.


15.4. Learning a Few Last Tricks to Make You a Relationship Pro

If you spend any amount of time in the MSDN Forums for the Entity Framework, you may recognize two questions that are asked frequently. The first is "How can I filter the children that are returned when I use Load?" and the second is "How can I get the foreign key value of a navigation property?"

Now that you have learned so much about relationships in the Entity Framework, you will be able to understand the solutions to both of these FAQs.

The first is solved with a little-known method called CreateSourceQuery. The second is solved by digging down into the EntityReference to get at its properties. Here is how to perform both tricks.

15.4.1. Using CreateSourceQuery to Enhance Deferred Loading

Not only can you attach an entity to an IRelatedEnd, but you can also attach an IEnumerable when you are calling EntityCollection.Attach. An EntityCollection is not an IEnumerable, which is why you can't just attach another EntityCollection. But if you want to attach a number of entities at once, you can wrap them in something as simple as a list or the results of another query.

Additionally, you can use this as an alternative to EntityCollection.Load because it will give you some flexibility regarding what you are loading. You can use CreateSourceQuery to create queries on the fly for an Attach method, though you'll use it a bit differently for EntityCollection.Attach and EntityReference.Attach.

The most efficient way to create an IEnumerable for attaching or loading entities into an EntityCollection is to use the CreateSourceQuery method. Like the Load method, CreateSourceQuery will take care of the query creation and execution for you, leveraging the existing ObjectContext.

As an example, if you have a customer in the ObjectContext and you want to get that customer's reservations, you could call the following:

myCust.Reservations.Load()

This would load all of the reservations for that customer.

However, if you want to filter those reservations, you can use CreateSourceQuery instead, as shown in the following code:

Dim cust=context.Contacts.OfType(Of Customer).First
Dim sq = cust.Reservations.CreateSourceQuery() _
 .Where(Function(res) res.ReservationDate > New Date(2008, 1, 1))
cust.Reservations.Attach(sq)

var cust=context.Contacts.OfType<Customer>().First();
var sq = cust.Reservations.CreateSourceQuery()
 .Where(r => r.ReservationDate > new DateTime(2008, 1, 1));
cust.Reservations.Attach(sq);

The query will execute when the CreateSourceQuery is called. Now only the subset of reservations for that customer will be loaded.

You can also use CreateSourceQuery to filter on types. In the following code, Attach is being used with an EntityReference, which will not take IQueryable. Instead, you need to pass in an object, which you can get using the FirstOrDefault query method. Since Attach will throw an exception if you attempt to pass in a null, you need to test for null before calling Attach:

For Each a In addresses
  Dim sq = a.ContactReference.CreateSourceQuery() _
            .OfType(Of Customer).FirstOrDefault
  If Not sq Is Nothing Then
    a.ContactReference.Attach(sq)
  End If
Next

var addresses = context.Addresses.Take(5);
foreach (var a in addresses)
{
  var sq = a.ContactReference.CreateSourceQuery()
            .OfType<Customer>().FirstOrDefault();
  if (sq != null)
    a.ContactReference.Attach(sq);
}

With this code, only customers will be loaded.

Watch those resources. Just like Load, the preceding query will be run whether or not the contact is a customer, so you may end up with a lot of wasted trips to the database. Consider your data, your resources, and your application's needs. In some scenarios, you may find yourself better off making one big query for customers and not doing this explicit lazy loading.

15.4.2. Getting a Foreign Key Value

Foreign keys do not exist in an EDM. Foreign keys from a database table are replaced with navigation properties when an entity is created. Because you can traverse relationships in the EDM without having to perform JOINs, the need for foreign keys is greatly reduced. However, at times you may want to have access to a foreign key value such as Address.ContactID.

Although the value is not exposed directly, if you have the entity or the EntityReference, you can get to that value. For example, if the Address.Contact property is populated, you can simply request Address.Contact.ContactID.

If the ContactReference property is populated, you could drill into the EntityKey and extract the value. Don't forget that an EntityKey is composed of a collection of key/value pairs. Although a Contact entity may have only one key property (ContactID), in plenty of cases multiple properties are combined to make an EntityKey, just as you can use multiple fields in a database table to create a primary key.

Example 15-16 shows how to retrieve the ContactID from an Address entity.

Example 15-16. Retrieving the ContactID from an Address entity

address.ContactReference.EntityKey.EntityKeyValues _
 .Where(Function(k) k.Key = "ContactID")

address.ContactReference.EntityKey.EntityKeyValues
 .Where(k=> k.Key == "ContactID")

An extension method that can return a foreign key value for any EntityReference would be handy here. A simple approach requires that the developer knows about the EntityReference, and this particular method will presumptuously return the value of the first key, as shown in Example 15-17.

Example 15-17. Returning the foreign key value from an EntityReference property

<Extension()> _
Private Function ForeignKey(ByVal entRef As EntityReference) As Int32
  Dim keyval As Int32
  If Int32.TryParse _
    (entRef.EntityKey.EntityKeyValues(0).Value.ToString, keyval) Then
      Return keyval
  Else
    Return Nothing
  End If
End Function

public static class extension
{
  public static int ForeignKey(this EntityReference entRef)
  {
    int keyval = 0;
    if (int.TryParse(entRef.EntityKey.EntityKeyValues[0]
                           .Value.ToString(),
                     out keyval))
      return keyval;
    else
      return 0;
  }
}

Now you can call the extension method like so:

address.ContactReference.ForeignKey()

To write a method that extends the entity and does not require that you first drill into the EntityReference calls for some tricky use of MetadataWorkspace, which you will learn about in Chapter 17.

NOTE

In Chapter 20, you will see examples of customizing entities with foreign key fields. For example, you will add a PrimaryActivityID property to the Customer entity to simplify the job of the UI developer.


15.5. Summary

As you can tell by now, relationships are central to the EDM and to how the Entity Framework functions. There are a lot of nuances and subtleties to understand, and some overall rules that are critical to have under your belt. And yet we've covered only the tip of the iceberg in this chapter. You will continue to encounter complexities in your code where relationships are involved, but having this deep understanding of how relationships work and how they relate to the rest of the Entity Framework means you have a lot of problem-solving tools at your disposal. We will continue to cover relationships, and how to solve other challenges that occur in various scenarios, throughout the remainder of the book.


Chapter 16. Making It Real: Connections, Transactions, Performance, and More

In previous chapters, you worked with bits and pieces of code and built small examples, but you did not build a real-world application. As such, you may be wondering how the Entity Framework addresses the everyday concerns of software developers. How do you control connections? Is there any connection pooling? Are database calls transactional? What about security? How's the performance? This chapter will address these and many of the additional questions developers ask after learning the basics of the Entity Framework.


16.1. EntityConnection and Database Connections in the Entity Framework

One of the benefits of using the Entity Framework is that it removes the need to write code to set up a database connection. Given that a connection string is available to the Entity Framework, most typically as part of the EntityConnectionString defined in a .config file, the Entity Framework will automatically set up, open, and close the database connection for you. Compared to typical ADO.NET code where you need to instantiate; define; and in many cases explicitly open a connection, instantiate and define a command, execute the command, and then explicitly close the connection, letting the ObjectContext handle all of this in the background as part of the query pipeline is certainly convenient. And this is the benefit you get in the default query scenarios. But oftentimes, you'll want more control over how and when connections are being made. To be able to do that, let's take a look at how the EntityConnection and DbConnection relate to each other. We'll also see how to programmatically force them to work the way you want if, in fact, the default behavior doesn't meet your needs.

16.1.1. EntityConnection Versus Database Connection

An EntityConnection is not a database connection. This can be a big point of confusion. Although you can open and close an EntityConnection, this does not mean you are opening and closing a connection to the database. Whether you use EntityClient directly or you let Object Services execute your commands and queries for you, the EntityConnection is just a path to the database connection.

An EntityConnection consists of four parts:

Metadata

The pointer to the metadata files (Conceptual Schema Definition Layer [CSDL], Mapping Schema Layer [MSL], and Store Schema Definition Layer [SSDL])

Provider connection

The database connection string

Provider name

The namespace of the database provider

Name

The name of the connection string

You can define the EntityConnection declaratively in the .config file. Example 16-1 lists the name of the connection string and then the EntityConnection string itself. Within the connection string, you can see the metadata parameter, the provider connection parameter, and the provider name. When the EDM Wizard builds this string for you, it replaces the quotes around the provider connection string with an escaped quote (&quot;), which is the XML encoding for a quote. For readability, you can replace the escaped quotes with single quotes, as in Example 16-1.

Example 16-1. The EntityConnection string in an app.config or web.config file

<connectionStrings>
<add
 name="BreakAwayEntities"
 connectionString=
   "metadata=res://*/BAModel.csdl|res://*/BAModel.ssdl|
            res://*/BAModel.msl;
    provider=System.Data.SqlClient;
    provider connection string='Data Source=myserver;
                               Initial Catalog=BreakAway;
                               Integrated Security=True;
                               MultipleActiveResultSets=True'"
    providerName="System.Data.EntityClient"
 />
</connectionStrings>

By default, an ObjectContext will use the connection string from the .config file that matches the EntityContainer name within your model. You have taken advantage of this in almost every code sample so far in the book, which is why you have not yet had to work explicitly with connection strings.

The database connection string that is embedded into the EntityConnection string is passed along to the database provider that eventually makes the actual connection to the database.

Development Database Versus Production Database

When you're moving from a development environment to a production environment, pointing to a new database can be as simple as changing the provider connection string parameter of the appropriate connection string in the .config file. You can also programmatically change the database connection string or modify connection strings on the fly.

16.1.2. Programming EntityConnection Strings

EntityConnection is a class within the EntityClient namespace. Earlier in the book, you worked directly with this class when using EntityClient for your queries. In the following code, the name of the connection string in the .config file is passed as a parameter (along with the parameter key name=) in the EntityConnection constructor. The connection in this case will be created from the details provided in the connection string:

Using conn As EntityConnection = New EntityConnection("name=BAEntities")

using (EntityConnecton conn = new EntityConnection("name=BAEntities");

You can use the preceding method to explicitly select a particular connection string from the .config file when instantiating an ObjectContext, as shown in the code that follows.

Dim context= New BAEntities("name=MyOtherConnectionString")

var context = new BAEntities("name=MyOtherConnectionString");

When you use this constructor for an EntityConnection or ObjectContext, the ConnectionString is not read in its entirety right away. In fact, if you inspect the EntityConnection in the debugger after it has been instantiated, you'll see that although the database connection object has been pulled into the StoreConnection property, the other parameters of the EntityConnectionString are nowhere to be found, as shown in Figure 16-1.

Figure 16-1. The EntityConnection object with no properties for the metadata or provider namespace attributes

The metadata and provider namespace parameters are not displayed as properties of the EntityConnection class, and they will be accessed at the point in the query pipeline where EntityClient needs to read the Entity Data Model (EDM), and then again to determine which provider (e.g., Data.Sql.SqlClient) to which to pass the request for further processing.

If you do want to read the full connection string from the configuration file, you can use one of the .NET methods for reading data from a configuration file, such as System.Configuration.ConfigurationManager.

16.1.3. Using the EntityConnectionStringBuilder Class

You can programmatically construct an EntityConnectionString with the EntityConnectionStringBuilder, which inherits from DbConnectionStringBuilder. For example, you may store the location of your metadata files (.csdl, .msl, .ssdl) in a resource file and wish to programmatically change the EntityConnectionString to point to this location. Or you may want to programmatically change the ADO.NET DataProvider (e.g., System.Data.SqlClient) on the fly.

The code in Example 16-2 reads the connection string from the configuration file into a string, creates an EntityConnectionStringBuilder from that string, modifies the Metadata property, and then instantiates an ObjectContext with the newly configured EntityConnectionString.

NOTE

For this example, a string for the path to the metadata files has been stored in the project's settings as MetadataFilePath. Its value is as follows:

C:\EFModels\Model.csdl|C:\EFModels\Model.ssdl|C:\EFModels\Model.msl

Example 16-2. Programmatically modifying an EntityConnectionString

Dim connstring = ConfigurationManager.ConnectionStrings.Item _
                 ("BAEntities").ConnectionString
Dim estringnew = New EntityConnectionStringBuilder(connstring)
With estringnew
  .Metadata = My.Settings.MetadataFilePath
  Dim context = New BAEntities(estringnew.ToString)
  Dim query = From con In context.Contacts _
              Where con.Addresses.Any(Function(a) a.City = "Seattle")
End With

var connstring = ConfigurationManager
                 .ConnectionStrings["BAEntities"].ConnectionString;
var estringnew = new EntityConnectionStringBuilder(connstring);
estringnew.Metadata = Properties.Settings.Default.MetadataFilePath;
var context = new BAEntities(estringnew.ToString());
var query =
    from con in context.Contacts
    where con.Addresses.Any((a) => 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 completely dynamic 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 an EntityConnection, 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 an EntityConnection before you can have your query executed.

When working with the ObjectContext directly or through LINQ to Entities, the default behavior is that the ObjectContext 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 calls Load 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 when cons.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<Customer>()
                      select con;
  var allcustList = allCustomers.ToList();
}

16.1.4.2.2. Forcing an explicit connection

To change the default behavior that happens in Example 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<Customer>()
                      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 in Figure 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 the StoreConnection.

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 or ObjectContext, 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, SaveChanges 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 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 IsolationLevels, 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 ObjectStateManager, which we will cover in detail in Chapter 17.

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 an EntityConnection; 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 also loaded 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 and ObjectQuery, other operations occur, such as the creation of ObjectStateEntries 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 the ObjectQuery 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<decimal> testresults = new List<decimal>();
  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<decimal> testresults = new List<decimal>();
  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<decimal> testresults = new List<decimal>();
  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<EFCustomer> customers =
                       AWL2E.CreateQuery<EFCustomer>(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<decimal> testresults = new List<decimal>();
  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<decimal> testresults = new List<decimal>();
  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 each EntitySet and association. For example, the Views class for the FullGeneration example in the preceding note will create a view for dbo.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 EntitySets 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 EntitySets 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 an ObjectQuery 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<Customer>()
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<Customer>()
  where cust.Reservations.FirstOrDefault().Trip.Destination.DestinationName
  == DestinationString select cust

This lambda expression is used as a parameter of CompiledQuery.Compile.

Example 16-19 shows the CompiledQuery, which will take a BreakAwayEntities object and a string when it's called, and will return an IQueryable(of Customer). Those are passed into the Compile 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<BreakAwayEntities, String,
 IQueryable<Customer>> (*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<Customer> 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<Customer> custs = compQuery.Invoke(context, destination);
  var custlist = custs.ToList();
}
using (BreakAwayEntities context = new BAEntities ())
{
  string destination = "Bulgaria";
  ObjectQuery<Customer> 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 for EntityClient. ObjectQuery.EnablePlanCaching is the property for enabling or disabling query plan caching for ObjectQuery 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 the EntityClient 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 the EntityDataReader 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 for DataSet. The first uses the default Update, 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 than DataAdapter and LINQ to SQL, as you can see in Table 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 ObjectContexts 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 use ObjectContext in separate threads.

16.5.1. Forcing an ObjectContext to Use Its Own Thread

Example 16-22 uses a separate class for managing the ObjectContext 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 the ObjectContext 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<Contact> contactList);
private static List<Contact> 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<Contact> 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 ObjectContexts 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 the ObjectContext. 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 <= Today.AddDays(6)) Then
        'new thread for very soon trip emails
        Dim workerThread As Threading.Thread =  _
         New Threading.Thread(AddressOf emailThread.NextWeek)
        workerThread.Start(cust)
      Else 'no future trips
        'new thread for no upcmoing trips emails
        Dim workerThread As Threading.Thread = _
         New Threading.Thread(AddressOf emailThread.ComeBackEmails)
        workerThread.Start(cust)
      End If
    Next
  End Using
End Sub

Public Class EmailThreadClass
  Public Sub UpcomingTripEmails(ByVal cust As Customer)
    Dim anytrip = cust.Reservations _
       .Where(Function(r) r.Trip.StartDate > Today.AddDays(6)) _
       .First.Trip
    Console.WriteLine("Thread " & Thread.CurrentThread.ManagedThreadId)
    Console.WriteLine("            Dear " & cust.FirstName.Trim & _
      ", Your trip to " & anytrip.Destination.DestinationName.Trim & _
      " begins on " & anytrip.Trip.StartDate & _
      ". We look forward to seeing you soon.")
    Console.WriteLine()
  End Sub

  Public Sub NextWeek(ByVal cust As Customer)
    Dim anytrip = cust.Reservations _
       .Where(Function(r) r.Trip.StartDate <= Today.AddDays(6)) _
       .First.Trip
    Console.WriteLine("Thread " & Thread.CurrentThread.ManagedThreadId)
    Console.WriteLine("            Dear " & cust.FirstName.Trim & _
      ",  Your trip to " & anytrip.Location.LocationName.Trim & _
      " begins in only a few days. Please let us know if " & _
      " you have any last minute questions.")
    Console.WriteLine()
  End Sub

  Public Sub ComeBackEmails(ByVal cust As Customer)
    Console.WriteLine("Thread " & Thread.CurrentThread.ManagedThreadId)
    Console.WriteLine("            Dear " & cust.FirstName.Trim & _
       ", We haven't seen you in a while. We hope you'll consider" &  _
       "  BreakAway Geek Adventures for your next vacation.")
    Console.WriteLine()
  End Sub
End Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BAGA;
using System.Threading;

namespace Chapter16CS
{
  public class EmailThreads
  {
    public static void Main()
    {
      var emailThread = new EmailThreadClass();
      using (BAEntities context = new BAEntities())
      {
        var custs =
            from cust in context.Contacts.OfType<Customer>()
             .Include("Reservations.Trip.Destination")
            select cust;
        foreach (var cust in custs)
        {
          if (cust.Reservations
              .Any((r) => r.Trip.StartDate > DateTime.Today.AddDays(6)))
          {
            //new thread for upcoming trip emails
            Thread workerThread =
               new Thread(emailThread.UpcomingTripEmails);
            workerThread.Start(cust);
          }
          else if (cust.Reservations
            .Any(r => r.Trip.StartDate > DateTime.Today
                 & r.Trip.StartDate <= DateTime.Today.AddDays(6)))
          {
            //new thread for very soon trip emails
            Thread workerThread = new Thread(emailThread.NextWeek);
            workerThread.Start(cust);
          }
          else //no future trips
          {
            //new thread for no upcmoing trips emails
            Thread workerThread =
               new Thread(emailThread.ComeBackEmails);
            workerThread.Start(cust);
          }
        }
      }
    }
  }

  public class EmailThreadClass
  {
    public void UpcomingTripEmails(object customer)
    {
      var cust = (Customer)customer;
      var anytrip = cust.Reservations
       .Where(r => r.Trip.StartDate > DateTime.Today.AddDays(6))
       .First().Trip;

Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("            Dear " + cust.FirstName.Trim() +
   ", Your trip to " + anytrip.Destination.DestinationName.Trim() +
 " begins on " + anytrip.StartDate +
  ". We look forward to seeing you soon.");
Console.WriteLine();
    }

    public void NextWeek(object customer)
    {
      var cust = (Customer)customer;
      var anytrip = cust.Reservations
       .Where((r) => r.Trip.StartDate <=DateTime.Today.AddDays(6))
      .First().Trip;

Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("            Dear " + cust.FirstName.Trim() +
  ",  Your trip to " + anytrip.Destination.DestinationName.Trim() +
  " begins in only a few days. Please let us know if " +
  " you have any last minute questions.");
Console.WriteLine();
    }

    public void ComeBackEmails(object customer)
    {
      var cust = (Customer)customer;

Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("            Dear " + cust.FirstName.Trim() +
  ", We haven't seen you in a while. We hope you'll consider" +
  "  BreakAway Geek Adventures for your next vacation.");
Console.WriteLine();
    }
  }
}


16.6. Summary

This chapter looked at several important concerns of application developers—connections, transactions, security, performance, and threading—and how they relate to the Entity Framework. You should now have a good understanding of not only how things work under the covers, but also how you can take advantage of the Entity Framework's flexibility to control how connections are made, control transactions, or impact performance. This will enable you to implement the functionality that allows you to write enterprise-level applications while benefitting from using an EDM.


Chapter 17. Controlling Objects with ObjectStateManager and MetadataWorkspace

It's time to delve deeper into the Entity Framework and work directly with its core components: the ObjectStateManager and MetadataWorkspace APIs.

Object Services uses the classes in these two APIs extensively to interact with objects at a granular level. Most of the classes and methods are public, so you can also take advantage of them to control and manipulate entity objects at a low level.

In this chapter, you will build some pretty intensive code samples that will give you great hands-on experience working at this level. You will also get some ideas of what you can achieve with ObjectStateManager and MetadataWorkspace. In fact, most of the object interaction under the covers occurs using ObjectStateEntries and MetadataWorkspace. You have direct access to the same capabilities.

Combined, these two classes not only allow you to manipulate objects, but also enable you to write generic methods that you can use on various Entity Framework object types, as well as dynamically create objects at runtime without depending on the generated entity classes.

There are many benefits to writing generic code for entity objects. It allows you to write reusable code that can handle any EntityObject, whether processing entities in SavingChanges, writing utilities, or even, as you'll see in this chapter, writing code that can dynamically create entities.


17.1. Managing ObjectStateEntry Objects with ObjectStateManager

In Chapter 9, you got an introduction to ObjectStateEntry objects, which contain the value and state information for every object in the cache. Objects enter the cache in two distinct ways: as the result of a query or as the result of an explicit code instruction to add or attach an entity that is already in memory.

Anytime an entity enters the cache, the ObjectStateManager creates an ObjectStateEntry for that object. The type of ObjectStateEntry for an entity is called an EntityEntry. When any new relationships are created between attached entities, either automatically by the context or explicitly by code, the ObjectStateManager creates an ObjectStateEntry of the type RelationshipEntry to represent the relationship object (see Figure 17-1).

Figure 17-1. ObjectStateEntries created by the ObjectContext for each entity and relationship it is managing

Anytime an entity leaves the cache, its ObjectStateEntry is automatically destroyed as well as any RelationshipEntry objects that are bound to that entity.

17.1.1. An ObjectStateEntry Refresher

Because ObjectStateEntry is critical to most of what you will be learning in this chapter, it might be helpful to have a quick refresher on this class.

The information that an entity's ObjectStateEntry exposes includes the following:

  • An array of original values (the values when the entity was attached to the context)

  • An array of current values

  • An array of names of properties that have been modified

  • A pointer back to the entity

  • EntityKey

  • EntityState

  • The EntitySet name

  • ObjectStateManager

ObjectStateEntry also has an IsRelationship property to determine whether it's an EntityEntry or a RelationshipEntry.

Most of the ObjectStateEntry properties are null when the entry is a RelationshipEntry. However, these properties are still relevant and will be populated:

  • EntityState

  • EntitySet

  • ObjectStateManager

The pointer back to the EntitySet is important because the EntitySet itself provides all of the information regarding the two ends of the relationship.

You may recall that the debugger doesn't show all of the ObjectStateEntry information. You can access some of it only at runtime by calling properties that are not exposed in the debugger.


17.2. Getting an ObjectStateManager and Its Entries

Every ObjectContext has its own ObjectStateManager, which you can access using the ObjectContext.ObjectStateManager property.

The ObjectStateManager itself doesn't have properties. It only manages the ObjectStateEntry objects; therefore, it has methods to return those entries.

17.2.1. Getting Groups of Entries with GetObjectStateEntries

ObjectStateManager.GetObjectStateEntries returns an IEnumerable collection of entries. As you learned in the brief introduction to GetObjectStateEntries in Chapter 10, you must specify one or more EntityState enums to determine which types of entities to return. It won't just return all entries by default.

For example, to get all Unchanged entries that are currently in the context, you can use the code in Example 17-1.

Example 17-1. Retrieving Unchanged ObjectStateEntry entries

context.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged)

context.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged)

You can specify multiple states by separating the enums with the Or operator, as shown in Example 17-2.

Example 17-2. Specifying more than one EntityState for GetObjectStateEntries

context.ObjectStateManager.GetObjectStateEntries _
(EntityState.Added Or EntityState.Unchanged)

context.ObjectStateManager.GetObjectStateEntries
 (EntityState.Added | EntityState.Unchanged).

17.2.1.1. Overloading GetObjectStateEntries using extension methods

If you use GetObjectStateEntries frequently, you are sure to find the use of the EntityState parameters annoying. Sometimes you'll want all of the entries and you'll need to type in each EntityState over and over. Other times you'll want to find entries of a certain type.

NOTE

.NET's extension methods allow you to add functionality to internal classes. You can take advantage of this in many places with the Entity Framework. If you want to learn more about extension methods, check the MSDN topics for C# (http://msdn.microsoft.com/en-us/library/bb383977.aspx/) and for Visual Basic (http://msdn.microsoft.com/en-us/library/bb384936.aspx/).

The following examples represent a set of three extension methods to make the use of GetObjectStateEntries more convenient. The extension method in Example 17-3 takes no parameters, and returns all of the entries regardless of EntityState.

Example 17-3. GetObjectStateEntries overload to return all entries regardless of their EntityState

<Extension()> _
Public Function GetObjectStateEntries _
     (ByVal osm As Objects.ObjectStateManager) _
      As IEnumerable(Of Objects.ObjectStateEntry)
  Dim typeEntries = From entry In osm.GetObjectStateEntries
                    (EntityState.Added Or EntityState.Deleted _
                     Or EntityState.Modified Or EntityState.Unchanged)
  Return typeEntries
End Function

public static IEnumerable<ObjectStateEntry>
 GetObjectStateEntries(this ObjectStateManager osm)
{
  var typeEntries = from entry in osm.GetObjectStateEntries
                     (EntityState.Added | EntityState.Deleted
                      | EntityState.Modified | EntityState.Unchanged)
                    select entry;
  return typeEntries;
}

This definitely beats having to specify four entity state enums in the frequent cases where you'll want to do that.

The extension method in Example 17-4 returns all objects of a particular type by taking advantage of generics.

Example 17-4. GetObjectStateEntries overload to return all entries of a particular entity type

<Extension()> _
Public Function GetObjectStateEntries(Of TEntity) _
   (ByVal osm As Objects.ObjectStateManager) _
    As IEnumerable(Of Objects.ObjectStateEntry)

 'this method takes advantage of the previous overload to return
 'all EntitytStates
  Dim typeEntries = From entry As Objects.ObjectStateEntry _
                    In osm.GetObjectStateEntries() _
                    Where entry.Entity Is TEntity
  Return typeEntries
End Function

public static IEnumerable<ObjectStateEntry>
 GetObjectStateEntries<TEntity>(this ObjectStateManager osm)
{
 //this method takes advantage of the previous overload to return
 // all EntityStates
  var typeEntries =
      from  entry in osm.GetObjectStateEntries()
      where entry.Entity is TEntity
      select entry;
  return typeEntries;
}

Now you can get all entities of a particular type without having to build a LINQ query. The following code demonstrates how to call the overload:

GetObjectStateEntries(Of Customer)

GetObjectStateEntries<Customer>

The extension method in Example 17-5 takes the EntityState parameters and filters on a particular type using generics again.

Example 17-5. GetObjectStateEntries overload to return all entries of a particular entity type and EntityState

<Extension()> _
Public Function GetObjectStateEntries(Of TEntity) _
   (ByVal osm As Objects.ObjectStateManager, _
    ByVal state As EntityState) _
    As IEnumerable(Of Objects.ObjectStateEntry)

  Dim typeEntries = From entry As Objects.ObjectStateEntry _
                    In osm.GetObjectStateEntries(state) _
                    Where entry.Entity Is TEntity
  Return typeEntries
End Function

public static IEnumerable<ObjectStateEntry>
  GetObjectStateEntries<TEntity>
 (this ObjectStateManager osm, EntityState state)
{
  var typeEntries =
      from  entry in osm.GetObjectStateEntries(state)
      where entry.Entity is TEntity
      select entry;
  return typeEntries;
}

The code in Example 17-6 calls each new GetObjectStateEntries overload.

Example 17-6. Calling all three GetObjectStateEntries overloads

'query for some contacts
Dim contacts = context.Contacts _
                      .Where(Function(c) c.Addresses _
                             .Any(Function(a) a.CountryRegion = "UK")) _
                      .ToList()
'Get all entries in the context, even relationships
Dim allOses=context.ObjectStateManager.GetObjectStateEntries()
'Get all Customer entries
Dim custOses=context.ObjectStateManager _
                           .GetObjectStateEntries(Of Customer)()
'Get only Modified Customer entries
Dim modifiedCustOses=context.ObjectStateManager _
         .GetObjectStateEntries(Of Customer)(EntityState.Modified)

//query for some contacts
 var contacts = context.Contacts
                       .Where(c => 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<Customer>();
//Get only Modified Customer entries
var modifiedCustOses=context.ObjectStateManager _
          .GetObjectStateEntries<Customer>(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 its EntityKey and use that to find the entry. Example 17-7 uses an entity to find its related ObjectStateEntry, 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), an InvalidOperationException 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 the GetObjectStateEntry 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 a FieldCount property. It does not include a DataRecordInfo object. Under the covers, this is an ObjectStateEntryDbDataRecord. 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, calling OriginalValues 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 as GetString 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 DbDataReaders, 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 the RelationshipEntries 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 in Chapter 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 method extension.

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

<Extension()> _
Public Module Visualizers

<ConditionalAttribute("DEBUG")> _
<Extension()> _
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 GetModifiedProperties 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.

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, ComplexTypeString, for that task.

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 EntityKeys 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 EntityStateObjects, but you should not rely on it for RelationshipEntries. Stick with the CurrentValues.

Because the RelationshipEntry describes a relationship between two entities, the EntityKeys 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 EntityKeys 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

<Extension()> _
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<int> 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 ApplyPropertyChanges 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 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 RelationshipEntries 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 an ObjectContext 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 the ObjectStateManager 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 is a RelationshipEntry, 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 EntityReferences 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 named ModifiedDate is not a DateTime field. Never assume!

The LINQ query in the example drills into the CurrentValues of each entry. Then, using the Any method, it looks at the names of each FieldMetaData item for that entry, picking up only those whose name is ModifiedDate. You saw code similar to this when building the visualizer earlier in this chapter. Next, the If statement verifies that the ModifiedDate property is a DateTime field; then it updates the field using CurrentValues.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 the PrimitiveTypeKind 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 of FieldMetadata 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 an EdmProperty 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 the EntityKey. The Members property lists all of the members.

As you begin to investigate the EntityType, 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 at Example 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 an EntityConnection 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 an EntityConnection 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. The MetadataWorkspace 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 the DataSpace. 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 the C-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 the DataSpace 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's Union 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 the PrimitiveTypes and FunctionTypes that the model needs to be aware of. PrimitiveTypes are .NET, EDM, and store types. FunctionTypes 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<EntityType>(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 the EntityKey.

  • Members returns all of the properties.

  • NavigationProperties returns a subset of members.

  • Properties returns only the scalar properties (including ComplexTypes).

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<EntityType>("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. In Example 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 calling GetItems<EdmFunction>. 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 in Example 17-36.

Example 17-36. The UpdateContact function listing in the SSDL

<Function Name="UpdateContact" Aggregate="false" BuiltIn="false"
          NiladicFunction="false" IsComposable="false"
         ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
   <Parameter Name="ContactID" Type="int" Mode="In" />
   <Parameter Name="FirstName" Type="nchar" Mode="In" />
   <Parameter Name="LastName" Type="nchar" Mode="In" />
   <Parameter Name="Title" Type="nchar" Mode="In" />
</Function>

Everything you see in the schema is available through the MetadataWorkspace.

17.6.6. Querying the Items

It's also possible to perform LINQ queries against item collections. As an example, the method query in Example 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<EdmType>(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's Union 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<EdmType>(DataSpace.CSpace)
 .Union(mdw.GetItems<EdmType>(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 property (EntityCollections 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 is Reservation, in the Payment entity the navigation property is Payment.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 that EntityCollection 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 the NamespaceName so that you can use the GetItem method. You can find the NamespaceName within various types, but not directly from the MetadataWorkspace, so the code in Example 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<EntityContainer>
   (DataSpace.CSpace).First().Name;
var namespacename =  mdw.GetItems<EntityType>
   (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<string> listOfNavs = new List<string>();
 var EntityInfo = mdw.GetItem<EntityType>
     (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 in Section 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 the EntityKey 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 string ReservationID.

Now you can finally build the string using the names in listofNavs 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 (see Example 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<DbDataRecord>(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 access ObjectStateEntries 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 a DataRecordInfo 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<Payment>

    • 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 the ObjectStateEntry 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 generic List<Payment>, 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<EntityObject>, but you cannot cast a generic List<T> to another generic List<T>. 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 in Example 17-46, which drills deep into the FieldMetadata 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 the ObjectStateManager 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 <= ct.FieldCount; cti++)
          Console.WriteLine("  " + ct.GetName(i) + ": " +
                            ct[cti].ToString());
        break;
    }
  }
  Console.WriteLine();
}

Example 17-48 shows the final output of the sample, using the dynamically built Entity SQL query from Example 17-44 and the dynamically evaluated results.

Example 17-48. The results of the generic query displayed using generic code

Reservation
 ReservationID: 90
 ReservationDate: 12/4/2005 12:00:00 AM
 TimeStamp: System.Byte[]

Customer
 ContactID: 569
 FirstName: Cecil
 LastName: Allison
 Title: Mr.
 AddDate: 1/10/2004 5:46:14 PM
 ModifiedDate: 8/7/2008 8:27:07 AM
 TimeStamp: System.Byte[]
 InitialDate: 10/21/2003 6:19:27 AM
 Notes:
 BirthDate: 4/13/1988 12:00:00 AM
 HeightInches: 63
 WeightPounds: 152
 DietaryRestrictions:

 CustTimeStamp: System.Byte[]

Trip
 TripID: 32
 StartDate: 3/4/2006 12:00:00 AM
 EndDate: 3/11/2006 12:00:00 AM
 TripCostUSD: 1500

Payment
 PaymentID: 8
 PaymentDate: 2/1/2006 12:00:00 AM
 Amount: 300.0000
 ModifiedDate: 1/1/1900 12:00:16 AM
 TimeStamp: System.Byte[]
 ContactID: 569

This demonstrates the power you can access by combining the MetadataWorkspace and the ObjectStateManager.

NOTE

Zlatko Michailov's blog post titled "How to Parse an EntityDataReader" will give you the tools you need to iterate through the shaped results of an EntityClient query, identifying whether the items contain scalar values, a single entity object, an EntityCollection, and more. The post is at http://blogs.msdn.com/esql/ in the November 2007 archive.

17.6.9. Dynamic Entity SQL and Generics for Reference Lists

Here's a very handy use for creating Entity SQL dynamically. Many applications use reference lists which are contained in separate tables. In the BreakAway app, Locations (aka Destinations), CustomerTypes, and Activities can be considered reference types. The entities follow a pattern as well. Each has an ID that is composed of the entity name and "ID," e.g., ActivityID, as well as a description that is composed of the entity name and the word "Name," e.g., ActivityName.

Rather than write individual queries to generate these lists, you could have a single, generic method that dynamically queries for these lists.

Look at the similarities between the Entity SQL expressions for querying these lists. I'll use refData as the control variable for the expression:

SELECT refData FROM BAEntities.Activities AS refData
SELECT refData FROM BAEntities.CustomerTypes AS refData
SELECT refData FROM BAEntities.Destinations AS refData

If you were to create separate methods in your business layer to return each of these lists, you would have a lot of redundant code. Yet all that really differentiates the queries are the EntitySet name and the actual type returned in the resulting list.

Using a dynamic query and .NET Generics, you can create a single method that would be able to query for and return any of these lists.

The trickiest thing you'll need to do in this method is to get the strongly typed name of the entity that you want to query. But you can pull this off with the MetadataWorkspace, which means that the method will need access to that. Therefore, the method will take a MetadataWorkspace as a parameter. If the calling code has an available context, then it can easily pass in the MetadataWorkspace property from the context. Otherwise, you can create a MetadataWorkspace object on the fly and pass that in.

Here is what the signature of such a method would look like. Note that I am using TEntity to refer to an entity type rather than the more common T, used in other generic methods:

Public IList(Of TEntity) GetReferenceList(Of TEntity)(mdw As MetadataWorkspace)

public IList<TEntity> GetReferenceList<TEntity>(MetadataWorkspace mdw)

The first bit of code in the method will use the MetadataWorkspace to get the strongly typed EntitySet name based on the name of the entity defined by TEntity. Rather than have this reusable function buried in the GetReferenceList method, I am encapsulating it in a separate extension method for the MetadataWorkspace class, which I am calling GetEntitySetName. This is shown in Example 17-49.

Example 17-49. The GetEntitySetFullName extension method for the MetadataWorkspace class

<Extension()> _
Public Function GetEntitySetFullName(ByVal mdw As MetadataWorkspace, _
 ByVal entityName As String) As String
  Dim entContainer = mdw.GetItems(Of EntityContainer)(DataSpace.CSpace).First
  Dim entsets = From eset In entContainer.BaseEntitySets _
                Where eset.ElementType.Name = entityName
  Dim containerName = entsets.First.EntityContainer.Name
  Return containerName & "." & entsets.First.Name
End Function

public static string GetEntitySetFullName(this MetadataWorkspace mdw,
 string entityName)
{
  var entContainer = mdw.GetItems<EntityContainer>(DataSpace.CSpace).First();
  var entsets =
        from eset in entContainer.BaseEntitySets
        where eset.ElementType.Name == entityName
        select eset;
  var containerName = entsets.First().EntityContainer.Name;
  return containerName + "." + entsets.First().Name;
}

Now you can build the GetReferenceList method, as shown in Example 17-50, which will use GetEntitySetFullName to supply the strongly typed EntitySet name.

Example 17-50. The GetReferenceList extension method for the ObjectContext class

Public Function GetReferenceList(Of TEntity)(ByVal context As ObjectContext) _
 As IList(Of TEntity)
  Dim entityName = GetType(TEntity).Name
  Dim eSetName = context.MetadataWorkspace.GetEntitySetFullName(entityName)
  Dim esql = "SELECT VALUE refData FROM " & eSetName & " AS refData"
  Dim objQuery = context.CreateQuery(Of TEntity)(esql)
  return objQuery.ToList
End

public IList<TEntity> GetReferenceList<TEntity>(ObjectContext context)
{
  var entityName = typeof(TEntity).Name;
  var eSetName = context.MetadataWorkspace.GetEntitySetFullName(entityName);
  var esql = "SELECT VALUE refData FROM " + eSetName + " AS refData";
  var objQuery = context.CreateQuery<TEntity>(esql);
  return objQuery.ToList();
}

Note that the queries are executed using the default MergeOption, which means they will be managed by the context. Although you won't likely need to change-track these objects, if you want them to engage in relationships to other entities, they need to be managed by the context. You could create an overload that allows you to pass in a MergeOption and thereby have more control over each list.

This is a great example of using the MetadataWorkspace to construct code that is not only reusable but easier to maintain. It also highlights very useful functionality that you can't easily achieve with LINQ to Entities.


17.7. Creating EntityObjects Without Entity Classes

Much of the metadata work done in the previous examples used metadata to inspect existing classes that were created through an ObjectContext. What if you want to create classes without the benefit of the generated entity classes? For example, you could have an application that works with any Entity Framework EDM passed to it that has no previous knowledge of the classes.

You can do this by combining the MetadataWorkspace API with System.Reflection.

Although many people are familiar with using System.Reflection for inspecting .NET objects, you also can use it to instantiate objects using type information. You can generate that type information through the MetadataWorkspace API and then let reflection create object instances for you.

For System.Reflection Newbies

Although System.Reflection sounds like a daunting topic (it certainly did to me at one point), once you've used it a few times it's not so scary. Types in .NET have classes that you can instantiate—for example, you just saw some code where an assembly object was instantiated using an assembly file.

In the same way you have been investigating the metadata with the MetadataWorkspace API, System.Reflection allows you to open assemblies and read information about modules, classes, parameters, events, and so forth. For example, Assembly has a method called GetTypes, which returns all of the types defined in the assembly.

Also, Assembly has a CreateInstance method, which allows you to create an instance of an object. Assembly.CreateInstance calls System.Activator.CreateInstance, which you can also use directly.

You can do a lot of amazing things with System.Reflection. Look for the MSDN documentation topic titled "Reflection Overview" for more information.

17.7.1. Creating a New Entity with CreateInstance

You can instantiate a new class using the CreateInstance method of either System.Activator or System.Reflection.Assembly. Using Assembly.CreateInstance requires that you have a type instance of an assembly. Internally, Assembly.CreateInstance will eventually call Activator.CreateInstance.

There are reasons for choosing one over the other, but Assembly.CreateInstance suits the needs of these examples since the sample will do some other things with the assembly.

ObjectStateManager and MetadataWorkspace Versus Reflection

You can see how similar reflection is to the MetadataWorkspace API by calling Assembly.GetTypes:

Dim someTypes = myAssembly.GetTypes _
.Where(Function(t) t.BaseType.Name = "EntityObject")

So, why use one over the other?

Each API offers functionality that is not available in the other. For example, reflection lets you create new instances of objects, whereas the ObjectStateManager doesn't. Furthermore, the ObjectStateManager has methods and properties that are specific to entities, such as GetKeyMembers. However, some of the functionality overlaps, most notably the ability to set values. Although doing this with reflection takes less steps, setting values on EntityObjects using the CurrentValueRecord.SetValue and related SetString, SetDateTime, and similar methods is much better, performs much better. So, it will not be uncommon to mix the two APIs in a solution.

17.7.1.1. Getting a reference to an assembly

You can load an assembly into an Assembly type in a number of ways. One way is to use an existing object that comes from that assembly.

For example, if you have created a Customer object from the BreakAway model's assembly, you can use that object to get a handle to the assembly using the static method Assembly.GetAssembly, as shown in Example 17-51.

Example 17-51. Loading an assembly programmatically

Dim cust=context.Customers.First
Dim BAAssembly=Assembly.GetAssembly(context.GetType)

var cust=context.Customers.First();
var BAAssembly=Assembly.GetAssembly(context.GetType);

17.7.1.2. Creating an entity from the assembly

Now you can use this assembly object to instantiate any class within the assembly. You can do this by passing in the strongly typed name of the class, as shown in Example 17-52.

Example 17-52. Instantiating a class in the assembly

Dim newObj=BAAssembly.CreateInstance("BAGA.Payment")

var newObj=BAAssembly.CreateInstance("BAGA.Payment");

This will create a new Payment object instance.

You can see how using reflection can let you build dynamic code. You can create objects just by passing in a string.

NOTE

Notice that the strong name of the type does not use the model name, as you are required to do when working with the metadata. Instead, it needs the strongly typed name of the class as it is known to the assembly, and in this example, the assembly's namespace is BAGA.

17.7.2. Using System.Type to Inspect the EntityType

The object in the preceding section is a Payment entity, which has only the methods and properties of a Payment entity. That hasn't gotten you very far with dynamic programming.

However, you can additionally create a Type object, either directly from the assembly or from the Payment instance. A Type object allows you to do the same type of detective work on the Payment type that you did on the metadata. (See Example 17-53.)

Example 17-53. Creating a Type object to be used with reflection

Dim objectType = currAssembly.GetType("BAGA.Payment")

var objectType=currAssembly.GetType("BAGA.Payment");

System.Type also lets you set values. Although setting scalar values using an Entity Framework CurrentValueRecord provides better performance, you cannot modify any other properties. System.Type will allow you to access and modify all of the properties of the object, including navigation properties. You'll do this in the next example.


17.8. Creating Entities and Graphs Dynamically

Now it's time to leverage what you just learned. The following example is a culmination of many of the techniques explained in this chapter, from using the MetadataWorkspace to dynamically creating relationships on the fly using the RelationshipManager. The code enables you to create entities, build a graph, and save the graph data—all with generic code that has no knowledge of the entity classes that it will work with. The example in this section will do the following:

  • Receive information about an existing parent entity and the child to be created dynamically. An array of KeyValuePairs will be used to provide field names and values for populating the new object.

  • Query the model to retrieve the parent entity.

  • Create a new instance of the child.

  • Populate the child with the data.

  • Attach the child to the parent using the RelationshipManager shown earlier in this chapter.

  • Save the new child to the database.

You will do all of this without any references to the actual entity types so that you can use any parent and child with the method.

The method shown in Example 17-54 takes advantage of a few custom functions and extension methods that are included in the downloads on the book's website. You'll see a frequently used custom method called GetEntitySetName. This method is a trimmed down version of GetEntitySetFullName, which does not bother with EntityContainer name. The extension methods are handy for a lot of MetadataWorkspace and ObjectStateManager scenarios.

Comments throughout the code explain what's happening in detail.

NOTE

Because of the length of this code sample, only the Visual Basic version will be displayed. The C# version will be available for download from the book's website.

Example 17-54. Building an entity graph dynamically with database interaction

Private Function AddChildtoParentObject _
              (ByRef ParentTypeName As String, _
               ByVal ParentID As KeyValuePair(Of String, Integer),_
               ByVal ChildTypeName As String, _
               ByVal ParamArray FieldValues() As _
               KeyValuePair(Of String, Object)) As Boolean

  'using a default connection string, but metadata and store connection
  'string can be passed in as well
   Using context As New ObjectContext( _
     New EntityClient.EntityConnection("name=BAEntities"))
     Dim mdw = context.MetadataWorkspace

     'will need namespace name further on
      Dim EDMNamespace = _
       mdw.GetItems(Of EntityType)(DataSpace.CSpace).First.NamespaceName

     'need assembly and its Namespace to create objects.
      Dim currAssembly = Assembly.GetAssembly(parentObject.GetType)

     'Assembly namespace is available in any of its types,
     'get one randomly
      Dim assemblyNS = _
         currAssembly.GetTypes.Where _
         (Function(t) t.BaseType.Name = "EntityObject").First.Namespace

      'query the EDM/db for the parent object
      Dim esql = _
      "SELECT VALUE ent FROM " & _
      mdw.GetEntityFullSetName(ParentTypeName) & " AS ent " & _
      "WHERE ent. " & ParentID.Key & "=" & ParentID.Value.ToString

      Dim parentObject = context.CreateQuery _
       (Of EntityObject)(esql).FirstOrDefault
      If parentObject Is Nothing Then
        Return False  'need logic to deal with this in calling method
      End If

     'create a new System.Type of the child entity
     'this will be used for type inspection
      Dim childAsType As Type = _
       currAssembly.GetType(assemblyNS & "." & ChildTypeName)

     'instantiate an actual entity
      Dim childAsEntity =  _
       CType(Activator.CreateInstance(childAsType), EntityObject)

     'get the relationshipmanager so you can add a relationship
      Dim childRelationshipManager = _
       CType(childAsEntity, IEntityWithRelationships) _
       .RelationshipManager

     'need association name in order to get the related end
     'GetEntitySetName and GetAssociationName are custom methods
      Dim associationName = _
       mdw.GetAssociationName _
       (mdw.GetEntitySetName(ChildTypeName), _
        mdw.GetEntitySetName(ParentTypeName))

     'now use AssociationName and the EntitySet of the parent
     'to get the related end
      Dim ParentRelatedEnd = _
       childRelationshipManager.GetRelatedEnd _
       (associationName, mdw.GetEntitySetName(ParentTypeName))

     'finally, add the parent object to the relationship of the child
      ParentRelatedEnd.Add(parentObject)

      'modify properties through ObjectStateEntry, _
      'provides better performance in this case than with reflection
      Dim childEntry = context.ObjectStateManager.GetObjectStateEntry _
       (childAsEntity.EntityKey)

      'iterate through FieldValues passed in to assign the properties
      For Each item In FieldValues
        Try
          childEntry.CurrentValues.SetValue _
           (childEntry.GetOrdinalforProperty(item.Key), item.Value)
        Catch ex as exception
          'if value types are incorrect, setvalue will fail
        End try
      Next

      context.SaveChanges()
    End Using
    Return True
     'wrap the entire method in a Try/Catch and add exception handling
  End Function

A lot is going on in this example, but you learned most of it earlier in the chapter. The example serves two purposes. First, it demonstrates how to use reflection to create entities dynamically, as well as how to build graphs dynamically. Second, it demonstrates the combined power of the ObjectStateManager, the MetadataWorkspace, and reflection to build dynamic code, whether it is an entire application that is purely dynamic or part of an application that needs to be dynamic.

17.8.1. Calling the AddChildtoParentObject Method

The AddChildtoParentObject method requires a number of values to be passed in as parameters, as listed here and as shown in the following code:

  • The name of the parent entity.

  • The key field name and value for the parent, which are bundled in a KeyValuePair. This allows the method to query the database for the parent record. The method assumes that only a single value is required for the key, which will suffice for most cases.

  • The name of the child entity that will be added.

  • An array of KeyValuePairs, which take a string and an object. Each key/value pair represents the field name and value of the fields that will be populated for the new child entity.

AddChildtoParentObject _
  ("Reservation", _
   New KeyValuePair(Of String, Integer)("ReservationID", 10), _
   "Payment", _
   New KeyValuePair(Of String, Object)("PaymentDate", Now), _
   New KeyValuePair(Of String, Object)("Amount", CType(400, Decimal)))

AddChildtoParentObject
 ("Reservation",
  new KeyValuePair<string, int>("ReservationID", 10), "Payment",
  new KeyValuePair<string, object>("PaymentDate", System.DateTime.Now),
  new KeyValuePair<string, object>("Amount", System.Convert.ToDecimal(400)));

The method has some checks and balances built in to protect you from invalid property names and incorrect data types.

NOTE

Although an Entity SQL query is built into the method, the only provided parameter is the parent entity name, which your application, not the user, should supply, so you shouldn't have to worry about an injection attack here.

Out of context, this looks like a lot of work—even more work than just using classic ADO.NET. But if you need to create dynamic functionality in your applications that can handle any entity types you throw at it, this is definitely the way to go.


17.9. Summary

In this chapter, you investigated ObjectStateManager and MetadataWorkspace—the two APIs that provide most of the internal functionality of the Entity Framework. Using the same classes and features that Object Services and EntityClient use to parse queries and materialize objects, you learned how to create a variety of dynamic functionality and explored some useful scenarios for taking advantage of these features.

Although you might not be able to overcome some of the more sophisticated challenges in your applications with a simple method that is already available in the Entity Framework, access to these low-level tools enables you to build your own tools and functionality.


Chapter 18. Handling Entity Framework Exceptions

Things can go awry in many ways when you're querying or updating entities. The Entity Data Model (EDM), for example, could have a problem that the validator fails to detect but that makes it difficult for a query to be compiled. You might attempt to save entities to the database that are missing related data—a reservation without a trip, perhaps, or a reservation without a customer. The database might have a constraint that is not reflected in the model; if a user saves data that breaks the rule of the constraint, the database will throw an error. Or someone may have modified or even deleted a record while another user was editing it.

The Entity Framework includes a specialized set of exceptions for capturing problems like these that arise during query and command execution.

In your application, you should embed each query execution or call you make to SaveChanges in some sort of mechanism for catching these exceptions, most likely a Try/Catch block. When one of these exceptions is raised, it is up to your code to handle it.

In this chapter, we'll look at exception handling that is unique to the Entity Framework. Some exceptions are Entity Framework-specific exceptions, and others are .NET exceptions caused by faulty operations in the Entity Framework. You'll also spend a good deal of time focusing on concurrency exceptions, which are raised by a database conflict is caused when multiple users edit the same data concurrently.

NOTE

You should strongly consider exception handling for any application you write. If this is a new topic for you, plenty of resources are available on the Web, in the MSDN documentation, and in a variety of books to teach you accepted patterns and practices for implementing exception handling in .NET.


18.1. Preparing for Exceptions in Entity Framework Code

In Object Services, you can get exceptions from the moment you try to instantiate an ObjectContext to the time you call SaveChanges. Use Try/Catch blocks around this functionality to capture exceptions. For instance, you can dispose the context in the Finally clause in case it was instantiated before the error occurred, as shown in Example 18-1.

Example 18-1. Catching an exception and disposing the ObjectContext in Finally

Dim context As BAEntities
Try
  context = New BAEntities
  Dim res = context.Reservations.First
  Return res
Catch ex As Exception
   Throw   'Throw or handle exception
Finally
  context = Nothing
End Try

BAEntities context = null;
try
{
  context = new BAEntities();
  var res = context.Reservations.First();
  return res;
}
catch (Exception ex)
{
  throw;  //throw or handle exception}
finally
{
  context = null;
}

When you employ the using block in the context's instantiation, as shown in Example 18-2, the context and any resources that it controls will be disposed at the end of the block.

Example 18-2. Catching an exception when ObjectContext is automatically disposed

Try
  Using context As New BAEntities
    Dim res = context.Reservations.First
  End Using
Catch ex As Exception
   Throw   'Throw or handle exception
End Try

try
{
  using (BAEntities context = new BAEntities())
  {
    var res = context.Reservations.First;
  }
}
catch (Exception ex)
{
   throw; //Throw or handle exception
}

Exceptions can occur when you're creating connections and executing commands with EntityClient. It's equally important to capture those exceptions and be sure the connections and DataReaders are properly disposed. Example 18-3 shows the code for catching an exception when using EntityClient.

Example 18-3. Catching an exception when using EntityClient

Dim esql = "SELECT VALUE r FROM BAEntities.Reservations AS r"
Try
  Using econn As New EntityConnection("Name = BAEntities")
    Dim eComm = econn.CreateCommand
    Dim eReader As EntityClient.EntityDataReader
    With eComm
      .CommandText = esql
      econn.Open()
      eReader = .ExecuteReader(CommandBehavior.SequentialAccess)
      econn.Close()
      Do While eReader.Read
        'process results
      Loop
    End With
  End Using
Catch ex As Exception
   Throw   'Throw or handle exception
End Try

var esql = "SELECT VALUE r FROM BAEntities.Reservations AS r";
try
{
  using (EntityConnection econn =  new EntityConnection("Name = BAEntities"))
  {
    var eComm = econn.CreateCommand();
    EntityClient.EntityDataReader eReader = null;
    eComm.CommandText = esql;
    econn.Open();
    eReader = eComm.ExecuteReader(CommandBehavior.SequentialAccess);
    econn.Close();
    while (eReader.Read())
    {
      //process results
    }
  }
}
catch (Exception ex)
{
   throw; //Throw or handle exception
}


18.2. EntityConnectionString Exceptions

A number of problems are the result of a missing, misinformed, or even malformed EntityConnectionString.

18.2.1. Connection String Can't Be Found or Is Improperly Configured: System.ArgumentException

The first step in most activities involving entities is to create an ObjectContext. As you've learned, this requires an EntityConnection, which in turn depends on the EntityConnectionString.

If the default or specified connection string cannot be found either in the application's .config file or in other locations that you've designated, a System.Argument Exception will be thrown that reads as follows:

The specified named connection is either not found in the configuration,
 not intended to be used with the EntityClient provider, or not valid.

You will get this error if you are creating the EntityConnection directly and are passing in a connection string name with the same problem.

This is easier to deal with during debug mode than at runtime. In debug mode, check your entity connection string. If you are relying on the default in the .config file, make sure its name matches the EntityContainer name of your model. In our examples, the EntityContainer is BAEntities and the connection string should have the same name:

<connectionStrings>
<add name="BAEntities"  .... />

If that's not the problem, check that the string doesn't contain some type of invalid formatting.

18.2.2. Metadata Files Cannot Be Found: System.Data.MetadataException

The metadata attribute of the connection string has the names and paths of the model files (e.g., BAModel.csdl, BAModel.msl, BAModel.ssdl) hardcoded into it. The path could be a file path:

res:C:/BAModel.csdl|C:/BAModel.ssdl|C:/BAModel.msl

or a notation that indicates the files are embedded in an assembly:

res://*/BAModel.csdl|res://*/BAModel.ssdl|res://*/BAModel.msl

Many times in the ObjectContext life cycle these files need to be read so that when you instantiate the context, it looks for the files. If the files in the metadata tag cannot be found in the designated file path or in one of the referenced assemblies, a System.Data.MetadataException will be thrown.

If you have changed the name of the EDMX file in your solution, this can cause problems with the metadata attribute.

NOTE

In many of the following examples, the exact code for dealing with a problem may not be specified. If you put no code in a Catch statement, the exception will be caught but will not be thrown to the calling code, thereby going completely undetected. Minimally, you should call Throw to cause the exception to be raised to the calling code.

18.2.3. Handling Connection String Exceptions

Example 18-4 shows how to prepare for possible exceptions the aforementioned problems can throw. It looks for the message related to a missing or invalid ConnectionString as well as the MetadataException. You can handle the errors right in the code or throw them to the calling code.

Example 18-4. Catching a connection string problem in an ArgumentException

Catch ex As ArgumentException
  If ex.Message.Contains("specified named connection is either not found") Then
    Throw     'replace with handling code
  Else
    Throw     'handle or Throw the exception
  End If
Catch ex As MetadataException
    Throw     'replace with handling code
Catch ex As Exception
  Throw
End Try

catch (ArgumentException ex)
{
  if (ex.Message.Contains("specified named connection is either not found"))
    throw; //replace with handling code
  else
    throw; //handle or Throw the exception
}
catch (MetadataException ex)
{
    throw; // replace with handling code
}
catch (Exception ex)
{
  throw;
}

It's not easy to resolve these types of problems in your code, and your best resolution in your exception handler is to exit out of the method elegantly, provide the end user with some information, and log the error in such a way that an administrator or support person can assist with the issue. The System.Data.MetadataException or System.ArgumentException contains no special information other than the message itself. You will benefit by using standard exception-handling methods, such as reporting the message along with the connection string and where the message came from.


18.3. Query Compilation Exceptions

If the connection succeeds, the next thing you will probably do that involves an Entity Framework-related exception is to create and execute a query.

18.3.1. Invalid LINQ to Entities Query Expressions: System.NotSupportedException

LINQ's syntax has the benefit of IntelliSense and compile-time checking, so it is less prone to runtime errors. However, certain syntax will be valid to the compiler, but not when it comes time to process the query.

A good example of this is the use of the .NET method ToShortDateString(). The following LINQ query passes through the compiler's checks because ToShortDateString is a valid method for a Date type:

From r In context.Reservations Select r.ReservationDate.ToShortDateString()

But at runtime, when it attempts to compile the query into a native store command, it will discover that ToShortDateString has no direct mapping to any function in the store. Thus, the store command cannot be created, which results in a System.NotSupportedException with the following message:

LINQ to Entities does not recognize the method 'System.String ToShortDateString()'
method, and this method cannot be translated into a store expression.

You should be able to catch these during debug mode.

18.3.2. Invalid Entity SQL Query Expressions: EntitySQLException

EntitySQLException will probably be the most common exception you will encounter as you are learning Entity SQL and debugging your applications. So, you might as well make friends with it straight away. Also, remember that the query builder methods can help you with a good portion of your Entity SQL queries, even though they provide only a subset of the operators and functions you can use when you write Entity SQL directly.

EntitySQLException is thrown when your Entity SQL expression cannot be parsed or processed. You should be testing every one of the Entity SQL expressions you write so that you don't have any runtime surprises. However, if you are building dynamic queries, chances are greater that bad syntax will sneak in. And there are always the "what if" scenarios that you can't even imagine until they occur, but that you might lie awake worrying about at night. So, rather than lose sleep, you can hedge your bets by making sure you catch any of the exceptions properly.

NOTE

Don't forget about eSqlBlast, the tool for testing Entity SQL queries that I mentioned in Chapter 3. It's a great help for testing Entity SQL expressions without having to constantly debug your code to do so.

Here's an example of a malformed expression where the AS operator is missing; a common mistake. The expression should be using AS con after contacts; but with that missing, the variable con used elsewhere in the expression has no meaning.

SELECT VALUE con FROM BAEntities.contacts WHERE left(con.Lastname,1)='S'

The exception passes back some very helpful information contained in its properties.

The exception details are as follows:

Column= 61

In the preceding code, column 61 is where con.Lastname begins.

ErrorContext= multipart identifier

multipart identifier refers to the fact that multiple items (con and Lastname) exist and the parser has an issue with one (or more) of them. If the expression had selected a single value (SELECT VALUE con), the ErrorContext would be a singlepart identifier.

ErrorDescription = "'con.Lastname' 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."

Again, this is saying that the parser just can't figure out what con.Lastname is, and is listing all of the possible causes.

Another example occurs when you use incorrect functions or operators. Even the provider-specific functions and operators will be checked here. For instance, the following expression will throw an error because it incorrectly uses SqlServer.AVERAGE instead of the correct function, SqlServer.AVG:

SELECT VALUE SQLServer.AVERAGE(p.amount)
FROM BAEntities.Payments AS p

The exception's message will read as follows:

'SqlServer.AVERAGE' cannot be resolved into a valid type constructor or function,
near function, method or type constructor, line 1, column 30."

The line break is not accounted for in the message. In addition, the parsing occurs long before any attempts to touch the database are made.

Example 18-5 shows the Catch block for a method that creates an EntityConnection and attempts to execute it.

Example 18-5. Checking for a connection string problem in an ArgumentException

Catch ex As EntitySqlException
  Throw 'TODO: Replace with handling code
Catch ex As ArgumentException
  If ex.Message.Contains("specified named connection is either not found") Then
    Throw 'TODO: Replace with handling code
  Else
    Throw  'handle or Throw the exception
  End If
Catch ex As MetadataException
  Throw  'handle or Throw the exception
Catch ex As Exception
  Throw  'handle or Throw the exception
End Try

catch (EntitySqlException ex)
{
    throw; //TODO: Replace with handling code
}
catch (ArgumentException ex)
{
  if (ex.Message.Contains("specified named connection is either not found"))
  {
    throw; //TODO: Replace with handling code
  }
  else
  {
    throw; //handle or Throw the exception
  }
}
catch (MetadataException ex)
{
  throw; //handle or Throw the exception
}
catch (Exception ex)
{
  throw; //handle or Throw the exception
}

18.3.3. Store Provider Issues: EntityCommandCompilationException

Command compilation occurs when the Entity Framework creates the command tree to represent a store query. It's possible that the provider compiling the query is causing a problem. In this case, an EntityCommandCompilationException will be thrown with the following message, and no additional details:

An error occurred while preparing the command definition.

This is another tricky one to solve, although you won't be able to solve it in your code. The best you can do is to log the exception, inform the user if necessary, and gracefully exit the method.


18.4. Creating a Common Wrapper to Handle Query Execution Exceptions

If you have a defined system for handling or perhaps logging errors, you wouldn't want to rewrite that handling code for every query. Instead, you could build a set of wrappers to execute queries and handle particular exceptions. Each wrapper method would take either an ObjectQuery or a LINQ to Entities query as an argument and return either a single object or some type of enumerable collection of objects. If the query execution fails, the method could provide code to handle the different types of exceptions.

Here are the signatures of two methods, each with the two overloads for ObjectQuery or LINQ to Entities queries. The first method returns a single entity and the second returns a List of entities:

Public Function ExecuteFirstorDefault(Of TEntity) _
 (ByVal objectQuery As ObjectQuery(Of TEntity)) As TEntity
Public Function ExecuteFirstorDefault(Of TEntity) _
 (ByVal L2EQuery As IQueryable(Of TEntity)) As TEntity

Public Function ExecuteList(Of TEntity) _
 (ByVal objectQuery As ObjectQuery(Of TEntity)) As List(Of TEntity)
Public Function ExecuteList (Of TEntity) _
 (ByVal objectQuery As ObjectQuery(Of TEntity)) As List(Of TEntity)

public TEntity ExecuteFirstorDefault<TEntity>(ObjectQuery<TEntity> objectQuery)
public TEntity ExecuteFirstorDefault<TEntity>(IQueryable<TEntity> L2EQuery)

public List<TEntity> ExecuteList<TEntity>(ObjectQuery<TEntity> objectQuery)
public List<TEntity> ExecuteList<TEntity>( IQueryable <TEntity> L2EQuery)

Each method executes the given query and returns the requested result. For example, the first method would call return objectQuery.FirstOrDefault() or return L2EQuery.FirstOrDefault() to perform the execution. The ObjectQuery methods can take an ObjectQuery whether it was created using context.CreateQuery, new ObjectQuery, or a QueryBuilder method. Just be sure not to do anything to execute that query in the calling code. The methods that accept LINQ to Entities queries can take straight LINQ to Entities queries or those that were created by invoking a CompiledQuery.

Example 18-6 shows one of the methods with all of its Exceptions stubbed out. Remember that the ObjectQuery queries can throw EntitySQLExceptions, while the LINQ to Entities queries can throw InvalidOperationExceptions.

Example 18-6. The ExecuteFirstorDefault wrapper method for executing ObjectQuery queries

Public Function ExecuteFirstorDefault(Of TEntity) _
(ByVal objectQuery As ObjectQuery(Of TEntity)) As TEntity
  Try
    Return objectQuery.FirstOrDefault()
  Catch ex As EntitySqlException
    Throw ex 'TODO: Replace with handling code
  Catch ex As ArgumentException
    If ex.Message.Contains("specified named connection is either not found") Then
      Throw 'TODO: Replace with handling code
    Else
      Throw 'handle or Throw the exception
    End If
  Catch ex As MetadataException
    Throw 'handle or Throw the exception
  Catch ex As Exception
    Throw 'handle or Throw the exception
  End Try
End Function

public TEntity ExecuteFirstorDefault<TEntity>(ObjectQuery<TEntity> objectQuery)
{
  try
  {
    return objectQuery.FirstOrDefault();
  }
  catch (EntitySqlException ex)
  {
    throw ex; //TODO: Replace with handling code
  }
  catch (ArgumentException ex)
  {
    if (ex.Message.Contains("specified named connection is either not found"))
      throw; //TODO: Replace with handling code
    else
      throw; //handle or Throw the exception
  }
   catch (MetadataException ex)
  {
    throw ; //handle or Throw the exception
  }
  catch (Exception ex)
  {
    throw; //handle or Throw the exception
  }
}

Example 18-7 shows code that calls the ExecuteList method overload to execute a LINQQuery.

Example 18-7. Executing a LINQ to Entities query with the ExecuteList method

Dim linqquery = context.Contacts.OfType(Of Customer) _
                       .Include("Reservations") _
                       .Where(Function(c) c.Reservations.Any())
Dim custlist = dal.ExecuteList(Of Customer)(linqquery)

var linqquery = context.Contacts.OfType<Customer>()
                      .Include("Reservations")
                      .Where(c => c.Reservations.Any());
var custlist = dal.ExecuteList<Customer>(linqquery );

You will still need some error handling in the calling code to handle particular scenarios, but the Execution query helper methods allow you to avoid repeating exception handling code that you may want to repeat for every query. Note that you don't necessarily need to return EntityObjects from the queries. You could return a single or set of DbDataRecords or any other predefined class. The only thing that you can't return would be an anonymous type that results from a LINQ to Entities projection. You'll see these query execution methods used in later chapters.


18.5. SaveChanges Command Execution Exceptions

When it's time to save changes back to the database you have another set of problems to be aware of. The connection issues raised earlier in this chapter will come into play if you are establishing a new ObjectContext or EntityConnection to perform the update. But the data itself causes other problems. The Entity Framework will catch some of the problems and prevent the data from going to the database. The database will detect others and will return an error to the application.

18.5.1. Model and Mapping Constraints Are Broken: UpdateException

If you have done something such as violated a constraint built into the model, an UpdateException will be thrown.

Relationships are constraints. If an association defines a 1:* (One to Many) relationship between two entities, any child in that relationship that is being saved needs to have some evidence of a parent. Even if the parent entity is not attached to the child, the EntityReference must have an EntityKey. Example 18-8 shows a new reservation being created in memory and added to the context, which then calls SaveChanges. But no Customer is associated with the context, not even an EntityKey for the CustomerReference. As a result, this call to SaveChanges will fail.

Example 18-8. A SaveChanges call that will fail because the new reservation has no Customer identified

Dim res = New Reservation
res.TripReference.EntityKey = trip.EntityKey
res.ReservationDate = Today
Using context As New BAEntities
  context.AddToReservations(res)
  context.SaveChanges()
End Using

var res = new Reservation();
res.TripReference.EntityKey = trip.EntityKey;
res.ReservationDate = DateTime.Today;
using (BAEntities context = new BAEntities())
{
  context.AddToReservations(res);
  context.SaveChanges();
}

NOTE

This code assigns the trip's EntityKey to TripReference, rather than just stating res.Trip=trip. Remember that when the reservation is added to the context, the context will also try to add anything attached to that reservation. If the trip, which came from the database, is attached to the reservation, you'll get an error as the context attempts to assign a new temporary EntityKey to that trip.

SaveChanges throws an UpdateException with the following message:

Entities in 'BAEntities.Reservations' participate in the FK_Reservations_Customers'
relationship.
0 related 'Customers' were found. 1 'Customers' is expected.

The ObjectContext doesn't do this type of validation when you add the reservation to the context, because you might attach a Customer or its EntityKey later. Therefore, the only time it's confident that you have no intention of adding a Customer is when you are calling SavingChanges, and that's when it does its check.

NOTE

You should detect this type of problem before your code goes into production. You can also employ your own business rules to perform these types of checks before it's time to call SaveChanges.

For example, you could have specific rules in the Reservation class that test to see whether a Customer is defined by checking for the presence of the CustomerReference.EntityKey or by checking that the Customer property is not null. The code behind SaveChanges uses the MetadataWorkspace to read the model, identify the constraints, and then check the entities in the cache to see whether they pass or fail the constraints. You could write similar generic code to perform this type of function as well, if it makes sense for you to do so.

18.5.2. Exceptions Thrown by Broken Constraints in the Database

Other constraints may not be defined in the model or handled by any business logic. Such is the case with the dependency that a Payment must contain a valid ReservationID. The database does not define a cascading delete to delete any payments related to a reservation if that reservation is being deleted. Therefore, if an attempt is made to delete a reservation that would leave orphaned Payment records, the database will throw an error and will not execute the delete command. That error is passed back to the client. If the client is the Entity Framework and the error was a result of a SaveChanges call, an UpdateException will be thrown with the following message:

The DELETE statement conflicted with the REFERENCE constraint
"FK_Payments_Reservations".

The conflict occurred in database "BreakAway", table "dbo.Payments",
column 'ReservationID'.

18.5.3. Automatically Rolling Back SaveChanges When an UpdateException Occurs

In Chapter 16, you learned that SaveChanges is wrapped in an implicit transaction. If an UpdateException is thrown during the call to SaveChanges, this halts the entire SaveChanges method and causes any previously executed commands to be rolled back.


18.6. ObjectStateEntries Returned by Object Services Exceptions

UpdateException is part of the System.Data.Entity API and is an Object Services exception. It inherits from .NET's DataException class and adds to it a valuable piece of information: the ObjectStateEntry of the entity being processed when the error occurred.

Figure 18-1 shows the UpdateException thrown by SaveChanges in Example 18-2, where the Reservation has no Customer.

Figure 18-1. Exceptions from Object Services containing an ObjectStateEntry

In the exception, you can see the StateEntries property. Multiple entries can appear in this property; for example, if you have relationships to other entities being managed by the context, the RelationshipEntry objects will be in this collection. But only the primary entry will be displayed in the debug window. And in this window you can see the ObjectStateEntry that is related to the Reservation, and that the entry has a pointer back to the entity.

If you want to you can log this information or present it to the user.

Remember that the context might be tracking a number of entities, so it's not always going to be obvious which entity caused the problem. By having this information returned in the exception, you can handle the exception intelligently.

18.6.1. General Entity Exceptions That May Occur When Executing Queries or Commands

A number of other exceptions derive from the generic System.Data.EntityException. Although some of these exceptions are internal, some may be raised simply as an EntityException.

For example, if there is a problem with the database server during command execution, an EntityException with the following message could be thrown:

"An error occurred while starting a transaction on the provider connection.
See the inner exception for details."

The InnerException will contain the actual error from the database, such as the following error in which, for dramatic effect, the SQL Server service was paused on the server:

"SQL Server service has been paused. No new connections will be allowed.
To resume the service, use SQL Computer Manager or the Services application
in Control Panel. Login failed for user 'domain\julie'. A severe error occurred
on the current command.  The results, if any, should be discarded."

NOTE

Although these are the exceptions you will most likely encounter, check the documentation of the EntityException base class to learn about some of the other exceptions that can occur during query and command execution.


18.7. InvalidOperationExceptions

Not all entity-related exceptions are query and execution related. One example you may encounter is the System.InvalidOperationException. This will be thrown when, for instance, you try to add an object to a context that already has an EntityKey. ObjectContext.Add is used to add new entities, and part of its job is to create an EntityKey. This is the type of exception you can avoid through testing and debugging. But you may certainly encounter it during testing and debugging.

In Example 18-9, the code that calls AddToContacts will throw an InvalidOperationException. The error results from the fact that the entity already has an EntityKey. The appropriate method to use is Attach.

Example 18-9. Trying to add an entity that already has an EntityKey

Dim con = context.Contacts.First
context.Detach(con)
context.AddToContacts(con)

var con=context.Contacts.First();
context.Detach(con);
context.AddToContacts(con);

InvalidOperationException is another exception you may want to plan for when working with entities.


18.8. Exceptions When Multiple Parties Edit Data Concurrently

Concurrency issues are the bane of data access developers. In any sizable organization, it is not uncommon for multiple users or services to be processing the same sets of information. Not infrequently, different users may be updating the same piece of data concurrently, and a conflict occurs. For instance, a salesperson could be modifying a payment at the same time an accounting system is processing it. Or a scheduler might delete a calendar item while another person in a different department was in the middle of editing the same item.

These are two very different types of concurrency problems. In the first problem, you need to consider whose changes are saved. Does the accounting system rule over the salesperson, or vice versa? Do you just take the last changes that were sent to the database, overriding the changes that were just saved? In some organizations the focus is on a single record, whereas other organizations might get as granular as worrying about which fields in the record were updated by whom.

In the second example, the problem occurs when the second user tries to save her changes but the record no longer exists in the database. What do you do then? You can't assume that her changes are unimportant just because the record was deleted. You might at least want to inform the user about the problem and give her an opportunity to take further action, if she has the proper permissions.

It is a tangled web of conundrums and decision making on the part of the application designer. Once your organization has devised the rules, it is up to the developer to implement them. But how?

18.8.1. Handling Concurrency Conflicts

In the database world, there are two ways to deal with concurrency conflicts. One involves pessimistic concurrency, where you expect the worst; the other is optimistic concurrency, where you hope for the best.

With pessimistic concurrency the database row is locked when a user retrieves that data and is then released when the user is finished working with that row. Nobody else can touch that data until the row is unlocked. It greatly reduces the potential of conflicts, but it comes at a price. Pessimistic concurrency is not scalable, because it maintains open connections and it can cause deadlocks.

A number of data access technologies do not support pessimistic concurrency because of the overhead involved. ADO.NET does not support it, nor does the ADO.NET Entity Framework. Therefore, this chapter will not cover pessimistic concurrency, but will focus instead on optimistic concurrency.

Optimistic concurrency does not lock the database rows, and relies on you, the developer, to provide logic in your application to handle potential update conflicts.

Concurrency is an age-old problem for anybody who designs line-of-business applications, and there is no silver bullet solution. You need to understand your business rules, be aware that these scenarios will need to be considered, and build your business logic to follow the rules you desire.

The Entity Framework does not magically solve the problem for you, either; however, it does provide tools for you to implement your business logic.


18.9. Understanding Optimistic Concurrency Options in the Entity Framework

Given that pessimistic concurrency is not an option for the Entity Framework, what are your options when multiple people (or processes) are concurrently editing data? First we'll survey the lay of the land, and then we'll dig into implementation.

A few solutions are commonly used; however, a narrower field of applications will process concurrency conflicts in a very granular way, which is not as common.

We'll look at these options as they are generally used in software and then focus on how the Entity Framework addresses them.

18.9.1. Ignoring Concurrency Conflicts

Many small systems don't even worry about these conflicts. When a user saves her changes, they are written to the database regardless of what anybody else is doing or has done.

The Entity Framework's default commands play an interesting role here. Because an Entity Framework update will update only the fields the user edited, it's possible that concurrent editing won't even cause a problem. Imagine that User A retrieves a Customer record, and while she is editing that record User B edits the same Customer, changing the BirthDate property. User B saves his changes. User A modifies the Customer's Notes field and saves. The Entity Framework will write a command to update the Notes field for that Customer. It won't touch the BirthDate, so all of the edits by both users are safe.

When using stored procedures to update, however, this scenario won't be so rosy. The procedure will most likely update every field regardless of its status. So, in that case, the original BirthDate value will be saved back to the database, and User B's changes will disappear.

18.9.2. Forcing the User's Data to the Server (ClientWins)

In a system designed to alert you of conflicts, the system would alert you when User A attempts to save her data, indicating that the record has been modified since the time she initially retrieved it. In the Entity Framework, one option in this case is to force the current user's updates to the database. This is also referred to as ClientWins. It's different from ignoring the conflict, however, because it will update all of the values in the entity, even those that have not been edited. The impact in the scenario described in the preceding section is that the BirthDate field and every other field in the database record will be changed to reflect the user's version of the data. It would have the same effect as the stored procedure.

18.9.3. Refreshing the User's Data with Server Data (StoreWins)

In this resolution, when the conflict is detected the user's data is refreshed from the server. The entity that she has just modified in her application will be updated to reflect the server's current version of the data. Any edits she made will be lost. That may sound malicious, but if this is the expected behavior of the application, it shouldn't be a problem. The application can alert the user and she can apply her edits to the Notes field again (or the application can do that for her if, for example, the changes still exist in memory, or even in the Text value of a user control), and then she can save again. If a process that doesn't involve a user is making the updates, you should apply logic that doesn't require user interface.

18.9.4. Determining the Scope of Changes

You can discover whether data has changed on the server while a user is in the process of editing the same data in a number of ways:

Check for any change at all to the record

To do this, you would need to compare every field in the property to see whether that row was edited. The Entity Framework supports this using its ConcurrencyMode property. Another mechanism that developers use is a database function called checksum that computes a hash value from all of the data in a particular row. The Entity Framework doesn't have direct support for checksum, but you can access it using Entity SQL, because of the way Entity SQL allows you to use database functions.

Check for particular field changes

Here you need to focus on only one or more specific fields that would indicate a change has been made. Database rowversion fields are great for this. But you may really be interested in a few specific properties. For example, with an employee record, you may determine that the only piece of data in which a conflict would create a problem is the Social Security number. Rather than using rowversion, which indicates that something changed with no regard to which field that may have been, you could specifically watch only the Social Security number field. If it was updated during a concurrent operation, it's time to raise a flag.

Check to see whether the fields you are updating have changed

The Entity Framework does not support this directly. You would have to do additional queries to check in this way.

NOTE

SQL Server's timestamp type is the same as the ISO standard rowversion type and its name is a bit confusing. Microsoft will be changing the name of this type to rowversion in a future version of SQL Server.

"The timestamp syntax is deprecated. This feature will be removed in a future version of Microsoft SQL Server. Avoid using this feature in new development work, and plan to modify applications that currently use this feature."

18.9.5. Using rowversion for Concurrency Checks

The simplest mechanism for detecting that anything in a database row has changed is to use a rowversion field. A rowversion is a binary field that is automatically updated whenever changes are made to the row. Many databases have a specific field type that is used for this. As noted earlier, SQL Server's timestamp is a rowversion field and will eventually be renamed to rowversion in a future version. With databases that do not have an explicit rowversion type, patterns are available for creating triggers to update fields in your database.

If you use rowversion fields in your database and they are surfaced as properties in your entities, the Entity Framework will need to check only that single field to detect whether a change was made to the data in the data store.

NOTE

Because the BreakAway database is a SQL Server database and its field type is timestamp, I will use that term frequently in this discussion.

A number of entities in the BreakAway model have TimeStamp properties, which map to timestamp fields in the database.

In the EDM, the TimeStamp property is a non-nullable binary field. In the store schema in the EDMX, the field is also non-nullable (Nullable=false) and its StoreGeneratedPattern is Computed, so the Entity Framework does not need to worry about managing this field:

CSDL
<Property Name="TimeStamp" Type="Binary" Nullable="false" MaxLength="8"
          FixedLength="true" />
SSDL
<Property Name="TimeStamp" Type="timestamp" Nullable="false"
          StoreGeneratedPattern="Computed" />

When working with disconnected data, a rowversion field—whether your database inherently supports it or you have to use a binary field with triggers—is one of the most important tools you have in your arsenal for dealing with concurrent systems. Otherwise, if you want to identify that a change has been made to a table row, you may have to consider a less efficient method of concurrency checking, such as checking every single field in the row.


18.10. Implementing Optimistic Concurrency with the Entity Framework

In the Entity Framework, enabling optimistic concurrency checks requires two steps:

  1. Define which property or properties will be used to perform the concurrency check.

  2. Handle the OptimisticConcurrencyException that is thrown when the check fails.

First we'll look at the various methods and effects of identifying the properties for the concurrency checks, and after that we'll dig into the exception handling.

18.10.1. Flagging a Property for Concurrency Checking

Because concurrency is defined on a property-by-property basis in the Entity Framework, the first step is to identify the property or properties that you will use for concurrency checking. We'll use the Contact entity's TimeStamp field.

The ConcurrencyMode property of an entity is used to flag a property for concurrency checking and can be found right in the Properties window. Its options are None, which is the default, and Fixed, as shown in Figure 18-2.

Figure 18-2. Setting a property's concurrency mode

By setting Concurrency Mode to Fixed, you ensure that the property is validated when an entity that was retrieved from the database is updated during a call to SaveChanges.

18.10.2. How the Entity Framework Uses the ConcurrencyMode Property

When Object Services prepares an Update or Delete command, it uses any properties marked for concurrency checking as a filter in the command along with the identity key.

With the ConcurrencyMode of Contact.TimeStamp set to Fixed, anytime a Contact is updated the Update command will look for the Contact using its EntityKey and its TimeStamp property.

For example, if Charles Petzold is knighted, his Title property will change from Mr. to Sir, as shown in Example 18-10.

Example 18-10. Changing a property of an entity with a concurrency checking property

Using context As New BAEntities
  Dim con = context.Contacts _
           .Where(Function(c) c.LastName = "Petzold" And c.FirstName = "Charles") _
           .FirstOrDefault
  con.Title = "Sir"
  context.SaveChanges()
End Using

using (BAEntities context = new BAEntities())
{
  var con = context.Contacts
            .Where(c => c.LastName == "Petzold" && c.FirstName == "Charles")
            .FirstOrDefault();
  con.Title = "Sir";
  context.SaveChanges();
}

When SaveChanges is called, the command shown in Example 18-11 will be sent to the database.

Example 18-11. The T-SQL Update command when using a TimeStamp field for optimistic concurrency checking

exec sp_executesql N'update [dbo].[Contact]
set [Title] = @0
where (([ContactID] = @1) and ([TimeStamp] = @2))
select [TimeStamp]
from [dbo].[Contact]
where @@ROWCOUNT > 0 and [ContactID] = @1',N'@0 nchar(3),@1 int,@2
binary(8)',@0=N'Sir',@1=850,@2=0x000000000000791A

That last value in Example 18-11 is the binary TimeStamp field. The command is attempting to update a Contact record where ContactID=1 and TimeStamp=0x000000000000791A, the original value of TimeStamp when the contact was first retrieved.

If that TimeStamp value had changed since the first query, due to someone else editing that record, the Update command will not find a matching record and will throw an error back to the client. The Entity Framework will report this as an OptimisticConcurrencyException.

This same type of concurrency check will also happen if the user is attempting to delete the contact. It will add the TimeStamp to the WHERE clause of the command. Every property that is marked as ConcurrencyMode=Fixed in your entity will be incorporated into the WHERE clause of Update and Delete commands in this way.

You'll read more about OptimisticConcurrencyException after reviewing some other options.

18.10.3. Concurrency Checking Without a rowversion Field

Although the rowversion concurrency checks are the most common methods developers use, you may not have that field available as an option.

In that case, you can use the ModifiedDate DateTime fields, but you need to be sure they are being updated. Although the SavingChanges example in Chapter 10 ensures that a particular model's ObjectContext always updates the ModifiedDate fields, this does not give you full coverage. You need to be sure that any application, process, or even user accessing the database directly updates that field every time, or the concurrency check will not detect a change.

Another method is to mark every property in the entity as FIXED. Although this does the trick, it makes your commands less efficient because all of the properties' original values will become part of every WHERE clause.

18.10.4. Concurrency Checking on a Checksum in the Data Store

With the assumption that you are thinking about CheckSum because you are unable to modify the database to use timestamp fields, a last resort is to use a QueryView or a Store Schema Definition Layer (SSDL) function, and to write store function queries (store queries written directly in the SSDL, as you saw in Chapter 13) directly into your model.

If you have checksum functions in the data store or you are implementing them in the SSDL, you still need to consider the actual act of performing the update so that you can get a concurrency check. If the CheckSum value is directly in the data table, it is represented in your entity as a binary property. It can be marked as a Fixed field and used for concurrency checks in the same way you use the TimeStamp or any other property in an entity.

If you have used QueryView or a store function to query data that includes a checksum value, you will need to use stored procedures for the update and delete operations, and these stored procedures will need to perform the concurrency checking.

SqlServer.CheckSum Won't Cut It for Concurrency Checks

SQL Server's CheckSum function is included in the SqlClient and you can use it in Entity SQL. However, because you can use only provider-specific functions with Entity SQL, you cannot use this, or the checksum functions that other providers offer, with LINQ. If you were to use CheckSum directly in T-SQL, you could write CheckSum(*), but the Entity SQL version requires that the individual expressions be listed—in this case, properties of an entity.

SqlServer.CheckSum has a few limitations that prevent it from being useful in Entity SQL as a way to do a concurrency check when you don't have access to the server. The first limitation is that it will take up to only three parameters, so you can't pass in an entire list of properties. The second is that all of the values must be of the same type.

18.10.5. Concurrency Checks for EntityReference Navigation Properties

If an entity has navigation properties that point to a parent or "One" side of a relationship, the concurrency check will still take place if that relationship changes. In the database, a foreign key value represents the relationship. So, as long as something changes that value and causes the TimeStamp field to change, when your application attempts to update the same row the change will be detected.

18.10.6. Concurrency Checks and Inherited Types

Version 1 of the Entity Framework does not support concurrency checks in derived types, period. You will see this quickly if you attempt to change the ConcurrencyMode property of any property in a derived type.

With inherited types, however, you may only use properties from the base type for concurrency checks. If you set the ConcurrencyMode of any property in a derived type to Fixed, you will get a validation error on the model that says that new concurrency requirements are not allowed for subtypes of base EntitySet types.

Given that you can't perform concurrency checks on derived types, it's important to see what behavior you can expect if the base type has any concurrency properties.

When you edit an inherited type that has a concurrency check in its base type, concurrency checking will happen, but only on the base type itself. Let's take a closer look at this.

In the BreakAway model, Customer inherits from Contact and Contact has a TimeStamp field that is now being used for concurrency checks. What happens when a Customer entity is being edited and a field that is specific to Customer has been modified? (See Example 18-12.)

Example 18-12. Modifying a derived entity whose base entity has a concurrency checking property

Dim cust = context.Contacts.OfType(Of Customer).First
cust.InitialDate = cust.InitialDate.Value.AddDays(1)
context.SaveChanges()

var cust = context.Contacts.OfType<Customer>.First();
cust.InitialDate = cust.InitialDate.Value.AddDays(1);
context.SaveChanges();

In this case, two commands will be sent to the database. The first will test to see whether anything in the Contact has changed. It is an update command that first declares a new variable (@p) and then attempts to update it using the ContactID and TimeStamp filter, as shown in Example 18-13.

Example 18-13. T-SQL checking for a change in the table related to the base entity before updating the derived entity

exec sp_executesql N'declare @p int
update [dbo].[Contact]
set @p = 0
where (([ContactID] = @0) and ([TimeStamp] = @1))
select [TimeStamp]
from [dbo].[Contact]
where @@ROWCOUNT > 0 and [ContactID] = @0',N'@0 int,@1
binary(8)',@0=1,@1=0x00000000000016B9

If a contact with a matching ContactID and TimeStamp is found, the context will send the next command, which is the one to update the InitialDate field in the Customer table, to be executed in the database. If the contact is not found, the command will throw an error back to the ObjectContext, which will in turn throw an OptimisticConcurrencyException.

There is a problem that you need to keep an eye on here. This mechanism assumes that all updates are being made through this model and that anytime something in the Customer table is changed, the TimeStamp field of the Contact table will be modified.

However, other applications may be using the same data, or even other EDMs that map to this data where the Customer is not a derived type. If one of the Customer table fields in the database is modified, the concurrency check will not detect it.

In this case, if you do need the check to be performed, you may want to rely on stored procedures for your DML commands.

18.10.7. Concurrency Checks and Stored Procedures

If you have mapped stored procedures to the insert, update, and delete functions of an entity, any properties marked as Fixed in that entity will not automatically be used in concurrency checks. However, you can define concurrency checking in the function mappings; the stored procedure that the functions are based on needs to be designed correctly.

18.10.7.1. Defining a stored procedure to perform concurrency checking

If your stored procedure has one or more parameters that take in values to be used for concurrency checking, when mapping to these parameters you can force the original value to be sent to that parameter with the Use Original Value checkbox.

There is one other important requirement for the stored procedure and the mapping. The database needs to return the new timestamp value to the entity. That way, if you need to use the original value again, it will be the correct version.

Adding an additional SELECT statement after the UPDATE command will impact the procedure's ability to return an error, because the SELECT statement will most likely succeed. Therefore, between the UPDATE command and the SELECT command, you will need to test to see whether the update was successful. If it was, continue with the SELECT; otherwise, the procedure will be finished and the error will be returned to your application.

The UpdatePayment stored procedure you already mapped to the Payment entity is written for you to use in this way. Example 18-14 displays the stored procedure.

Example 18-14. The UpdatePayment stored procedure

ALTER PROCEDURE [dbo].[UpdatePayment]
@PaymentID INT,
@date DATETIME,
@reservationID INT,
@amount MONEY,
@modifiedDate DATETIME,
@timestamp timestamp

AS

UPDATE payments
SET paymentdate=@date,reservationID=@reservationID,amount=@amount
WHERE
paymentid=@paymentid AND TIMESTAMP=@timestamp

IF @@ROWCOUNT>0
     SELECT timestamp AS newTimeStamp
     FROM payments WHERE paymentid=@paymentid

In Chapter 7, when you mapped the UpdatePayment function, you selected Use Original Value next to the TimeStamp property and you defined the function to capture the timestamp value that the stored procedure returned.

If you want to see this in action, now you can test the concurrency checking with Example 18-15, a short routine you can use to test the UpdatePayment procedure.

Example 18-15. Testing for update conflicts with function mappings

Using context As New BAEntities
  Dim pmt = context.Payments.First
  pmt.PaymentDate = pmt.PaymentDate.Value.AddDays(1)
  Dim origTS = pmt.TimeStamp
  Try
    context.SaveChanges()
    Dim newTS = pmt.TimeStamp
    If newTS Is origTS Then
      Console.WriteLine("timestamp not updated")
    Else
      Console.WriteLine("timestamp updated")
    End If
  Catch ex As OptimisticConcurrencyException
    Console.WriteLine("Concurrency Exception was thrown")
  End Try
End Using

using (BAEntities context = new BAEntities())
{
  var pmt = context.Payments.First;
  pmt.PaymentDate = pmt.PaymentDate.Value.AddDays(1);
  var origTS = pmt.TimeStamp;
  try
  {
    context.SaveChanges();
    var newTS = pmt.TimeStamp;
    if (newTS == origTS)
      Console.WriteLine("timestamp not updated");
    else
      Console.WriteLine("timestamp updated");
  }
  catch (OptimisticConcurrencyException ex)
  {
    Console.WriteLine("Concurrency Exception was thrown");
  }
}

You'll see that the Payment entity's TimeStamp property is updated when the update is successful. If you test the collision by editing the database manually at the suggested breakpoint, an OptimisticConcurrencyException will be thrown.


18.11. Handling OptimisticConcurrencyExceptions

Now it's time to look at the other piece of the concurrency puzzle: handling the exception that is thrown when a concurrency check fails.

When a check fails and a System.Data.OptimisticConcurrencyException is thrown, this is where you can inject your business logic to determine how to deal with the issue.

The most common resolutions, as described earlier, are to force the client-side data to the server, or to pull the server-side data to the client and lose the client's edits. You can perform either of these actions using ObjectContext.Refresh. While many applications handle concurrency conflicts, it is more common to have a sweeping rule rather than to have logic handle very narrow cases. We'll look at both scenarios, but we'll spend more time on the more commonly used patterns.

18.11.1. Using ObjectContext.Refresh

ObjectContext.Refresh allows you to refresh entities in the context from the database. You can also use it in other places in your application. Here we'll focus on using it to handle OptimisticConcurrencyExceptions.

You can use Refresh to force either a ClientWins scenario or a StoreWins scenario with your updates.

Refresh takes two parameters. The first is RefreshMode, which has the options RefreshMode.ClientWins and RefreshMode.StoreWins. The second parameter is either a single entity or an IEnumerable of entities. The IEnumerable can be something such as a List or an Array, or even an IQueryable (LINQ to Entities query) or ObjectQuery:

context.Refresh(RefreshMode.ClientWins, aTrip)

If the RefreshMode is ClientWins, a query will be executed against the database to get the current server values for the entity. Then it will push those values into the original values of the entity. That will make the entity think it started out with those server values and it will build the update commands accordingly when SaveChanges is called again.

StoreWins will replace all of the current and original values of the entity with the data from the server. The user will lose her edits, and instead the cached data will be in sync with the database. Entities that are refreshed with StoreWins will be ignored by SaveChanges until they are edited again.

To get your first look at this, let's focus on a single entity and see what Refresh looks like in a basic scenario.

18.11.2. Using ClientWins Refresh

In Example 18-16, a simple query returns a single entity. If a conflict arises during a call to SaveChanges, that same entity is passed into the Refresh method.

Example 18-16. A ClientWins refresh on a single entity

Using context As New BAEntities
  Dim con = context.Contacts _
           .Where(Function(c) c.LastName = "Petzold" And c.FirstName = "Charles") _
           .FirstOrDefault
  con.Title = "Sir"
Try
  context.SaveChanges()
Catch ex As OptimisticConcurrencyException
 'Refresh the contact entity,using ClientWins
  context.Refresh(RefreshMode.ClientWins, aTrip)
 'SaveChanges again
  context.SaveChanges()
End Using

using (BAEntities context = new BAEntities())
{
  var con = context.Contacts
                   .Where(c => c.LastName == "Petzold" && c.FirstName == "Charles")
                   .FirstOrDefault();
  con.Title = "Sir";
  try
  {
    context.SaveChanges();
  }
  catch (OptimisticConcurrencyException)
  {
    //Refresh the contact entity,using ClientWins;
    context.Refresh(RefreshMode.ClientWins, con);
    //SaveChanges again;
    context.SaveChanges();
  }
}

Before SaveChanges is called, if another user has edited the same contact, causing the timestamp field to be updated, an OptimisticConcurrencyException will be thrown when SaveChanges is called.

NOTE

To test the OptimisticConcurrencyException, you'll need to emulate an edit being made by another user or process. To do this, place a breakpoint on context.SaveChanges. When the breakpoint is hit, open the Contact table in the Solution Explorer and edit the matching record.

Figure 18-3 shows the state of that contact using the ObjectStateEntry visualizer (which you built in Chapter 17) before the Refresh is executed.

Figure 18-3. The state of the Contact entity before calling ObjectContext.Refresh

The only changed field is Title, with the current value of Sir and original value of Mr..

Next, Refresh is called, which executes a query to retrieve the current values of this entity in the database, including the new timestamp. Figure 18-4 shows the contact after Refresh has been called.

Figure 18-4. ObjectContext.Refresh with ClientWins refreshing all of the original values of the designated entities, even those that have not changed, leaving every property modified

All of the original fields have been updated to reflect the latest server values, and you can see that on the server side some naughty person changed Mr. Petzold's first name to Chuck, causing the TimeStamp field to be updated.

Because every property was modified in this entity, each property's EntityState was changed to Modified. That means when SaveChanges is called again, every value will be sent to the server for updating. This time the record will be found because you have the new value of the TimeStamp for the WHERE clause. The update succeeds and we now have Sir Charles Petzold, which has quite a nice ring to it.

18.11.3. Using StoreWins Refresh

Let's take the same scenario and see what happens when you choose the StoreWins option.

Figure 18-5 shows the state of the Contact entity after Refresh(StoreWins, con) has been called. The entity's state is Unchanged and the Current and Original values have been replaced with the server-side values. The local entity has lost its nice title of Sir and has acquired the nickname Chuck. When SaveChanges is called again, it will do nothing because this entity is now Unchanged.

Figure 18-5. ObjectContext.Refresh with StoreWins refreshing the entities by completely synchronizing them with the database

In this case there is no need to call SaveChanges again, because you have done a StoreWins refresh on the only entity in the ObjectContext. However, if you are building a generic routine, it's safer to call SaveChanges anyway, as you may have other entities in the ObjectContext that you need to deal with. It doesn't waste any resources if there is nothing to change.

18.11.4. Refreshing Collections of Entities

You also can use Refresh with a collection of entities. The easiest scenario with which to use this overload is when you already have a set of entities encapsulated in a collection. For example, if you are working with a list of Contact entities, you can refresh the entire list at once. This makes an assumption that your business rules don't require any granular decision making to determine whether this type of update is appropriate for every entity in that collection.

Example 18-17 shows a simple query that changes any contact with a FirstName of Chuck to Charles. Then, if there is a concurrency exception, it uses the brute force of a ClientWins refresh to ensure that this change is made to the database.

Example 18-17. Doing a ClientWins Refresh on a set of entities

Using context As New BAEntities
  Dim cons = context.Contacts.Where("(it.FirstName)='Chuck'").ToList()
  For Each con In cons
    con.FirstName = "Charles"
  Next
  Try
    context.SaveChanges()'breakpoint here to modify server data
  Catch ex As OptimisticConcurrencyException
    context.Refresh(RefreshMode.ClientWins, cons)
    context.SaveChanges()
  End Try
End Using

using (BAEntities context = new BAEntities())
{
  var cons = context.Contacts.Where("(it.FirstName)='Chuck'").ToList();
  foreach (var con in cons)
    con.FirstName = "Charles";
  try
  {
    context.SaveChanges();
  }
  catch (OptimisticConcurrencyException ex)
  {
    context.Refresh(RefreshMode.ClientWins, cons);
    context.SaveChanges();
  }
}

In this case, we are passing the entire list of Contact entities to the Refresh method.

NOTE

As I explained earlier in the book, I recommend that you not work directly with the query unless you want to execute it again and instead that you create a set of results, such as a List. This is to avoid accidental query execution. However, I did test to see what would happen if I passed an ObjectQuery and a LINQ IQueryable directly into a Refresh command. Did it wreak havoc? No. It made no attempt to execute the query again. The behavior was no different from passing in the List, as in Example 18-17.

Refresh builds a query to retrieve the current store contact data by placing the EntityKeys into one big WHERE clause so that it is a single query:

SELECT
1 AS [C1],
CASE WHEN (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL))
THEN '0X0X' ELSE '0X1X' END AS [C2],
[Extent1].[ContactID] AS [ContactID],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName], etc........
WHERE ([Extent1].[ContactID] = 468) OR ([Extent1].[ContactID] = 471) OR
([Extent1].[ContactID] = 474) OR ([Extent1].[ContactID] = 480) OR
([Extent1].[ContactID] = 489) OR ([Extent1].[ContactID] = 492) OR
([Extent1].[ContactID] = 498) OR ([Extent1].[ContactID] = 506) OR
([Extent1].[ContactID] = 578) OR ([Extent1].[ContactID] = 581) OR
([Extent1].[ContactID] = 587) OR ([Extent1].[ContactID] = 596) OR
([Extent1].[ContactID] = 805)

This way, it is able to refresh all of the items in the collection at once.

NOTE

Remember that this means any other fields of any of these records that were modified in the meantime will be overwritten by the values in the entities in the context. However, only the originally modified entities will be updated when SaveChanges is called again. Any Unchanged entities that were refreshed will still be Unchanged after the refresh and therefore overlooked by SaveChanges.

18.11.5. Refreshing Related Entities in a Graph

If a modified entity is within a graph and it causes a concurrency exception, be cautious about which entities you pass into the Refresh method. Refresh will only refresh the parent node of a graph and will not impact any related entities in the graph.

Beware! Refresh does not impact graphs.

For instance, in Example 18-18, addressGraph is a graph whose main entity is an address that contains a contact. If the contact's update throws a concurrency exception when SaveChanges is called, you might want to solve that by calling Refresh on the addressGraph.

Example 18-18. Refreshing a graph—not the results you might expect

Dim addressGraph = context.Addresses.Include("Contact").First
add.Contact.FirstName =  _
   New String(add.Contact.FirstName.Trim.Reverse.ToArray)
Try
  context.SaveChanges()
Catch ex As OptimisticConcurrencyException
  context.Refresh(RefreshMode.StoreWins, add)
  context.SaveChanges()
End Try

var addressGraph = context.Addresses.Include("Contact").First();
addressGraph.Contact.Title = "Dr.";
try
{ context.SaveAllChanges(); }
catch (OptimisticConcurrencyException)
{
  context.Refresh(RefreshMode.StoreWins, addressGraph);
  context.SaveAllChanges();
}

But only the parent entity of the graph, the Address entity, will be refreshed. The contact will continue to cause the exception every time you save changes.

But there is a way to attack this problem. Remember that the exception returns ObjectStateEntry objects for the entity that was causing the problem.

This means that in the exception, you will have the ObjectStateEntry for the contact, which contains a reference to the entity. You can extract that entry's entity and call Refresh on the contact, and then call SaveChanges again if necessary. Example 18-19 shows the code for this.

Example 18-19. Getting a graph child to refresh

Catch ex As OptimisticConcurrencyException
  Dim contact=ex.StateEntries(0).Entity
  context.Refresh(RefreshMode.ClientWins, contact)
  context.SaveChanges()
End Try

catch (OptimisticConcurrencyException ex)
{
  var contact = ex.StateEntries[0].Entity;
  context.Refresh(RefreshMode.ClientWins, contact);
  context.SaveAllChanges();
}

18.11.6. Rewinding and Starting Again, and Maybe Again After That

It's important to realize that when handling these exceptions, SaveChanges won't just continue on its merry way, updating the next entity in the context. If you hit the exception, the SaveChanges method rolls back whatever it has already done and then halts.

In the exception handler you can call SaveChanges again. However, if that call fails, you need to catch it again. If you are pushing a lot of changes in one SaveChanges call and a number of exceptions are in there, each time you call SaveChanges you may have fixed the last problem but you will then hit the next one.

So again, you need to trap that error, handle it, and call SaveChanges again. You will end up with code that looks like the VB code in Example 18-20 (the C# version would require quite a lot of braces).

Example 18-20. Catching a number of concurrency exceptions

Try
Context.SaveChanges
Catch ex As OptimisticConcurrencyException
 'do some work, then try again
  Try
    context.SaveChanges()
  Catch ex As OptimisticConcurrencyException
   'do some work, then try again
    Try
      context.SaveChanges()
      Catch ex As OptimisticConcurrencyException
       'do some work, then try again
        Try
          context.SaveChanges()
        Catch ex As OptimisticConcurrencyException
          'and so on and so forth....
        End Try
    End Try
  End Try
End Try

You will be better off having your own method that calls SaveChanges and contains the exception handler. Then you can call the method recursively as needed. Example 18-21 shows a custom method, SaveMyChanges. Further on, you'll see a twist on this, which is a method added to the partial class for BAEntities, allowing you to call context.SaveMyChanges without having to pass the context in as a parameter.

Example 18-21. Handling concurrency exceptions recursively

Private Sub SaveMyChanges(ByVal context As Objects.ObjectContext)
  Try
    context.SaveChanges()
  Catch ex As OptimisticConcurrencyException
    'handle concurrency exception here
    'then try again
    SaveMyChanges(context)
  Catch ex As Exception
    'handle other exceptions
  End Try
End Sub

private void SaveMyChanges(Objects.ObjectContext context)
{
  try
  {
    context.SaveChanges();
  }
  catch (OptimisticConcurrencyException ex)
  {
    //handle concurrency exception here
    //then try again
    SaveMyChanges(context);
  }
  catch (Exception ex)
  {
    //handle other exceptions
  }
}

NOTE

Although I prefer the separate and reusable method, SaveMyChanges, you can find other ways to recursively call SaveChanges in the MSDN documentation. One example, which is combined with a System.Transaction.TransactionScope (more on transactions and exceptions later in this chapter), is in the topic titled "How to: Manage Object Services Transactions (Entity Framework)."

18.11.7. What If a Relationship, But No Scalar Properties, Changes?

The code in Example 18-22 reassigns a payment to a different reservation.

Example 18-22. Reassigning a payment's ReservationReference

Dim pmt = context.Payments.FirstOrDefault
pmt.ReservationReference.EntityKey = _
  New EntityKey("BAEntities.Reservations", "ReservationID", 13)
SaveMyChanges(context)

var pmt = context.Payments.FirstOrDefault();
pmt.ReservationReference.EntityKey =
  new EntityKey("BAEntities.Reservations", "ReservationID", 13);
SaveMyChanges(context);

Figure 18-6 shows the ObjectStateEntries before calling SaveChanges. The Payment itself is Unchanged. There is a RelationshipEntry for the original relationship to Reservation 10. That relationship was deleted when the new relationship was added.

Figure 18-6. State entries when moving a payment from one reservation to another

The Entity Framework uses the added relationship and the entity that it belongs to (Payment) to build an update command, changing the RelationshipID property in the Payment record.

If a conflict occurs, the exception that results contains these same two entries that were used to build the command, as shown in Figure 18-7.

Figure 18-7. StateEntries returned by an OptimisticConcurrencyException when editing a relationship

In a simple Refresh, you only need to refresh the payment; therefore, the existing code that refreshes StateEntries(0) works perfectly.

However, if you need more granular logic, there's great information to be found in the entries returned by the exception.

Relationship Updates in SaveChanges

You know the ObjectStateManager keeps track of relationships. And if the only thing you modify in an entity is a navigation property, you will find added and deleted RelationshipEntries in ObjectStateManager. The entry itself will be unchanged. The documentation for OptimisticConcurrencyException.StateEntries says, "A collection of ObjectStateEntry objects is comprised of either a single entity and 0 or more relationships, or 0 entities and 1 or more relationships." However, I have not come across (by accident or on purpose) an exception that contains only RelationshipEntries in its StateEntries property. Unless there is some confirmation somewhere, maybe even a modification to the documentation that you won't come across a relationship in the exception, make sure your code is prepared for such an exception if you are digging into those entries.

18.11.8. Reporting an Exception

Using the details from the exception, you can create a log error or even a message to a user that describes the conflict in detail. The client-side data is readily available to create this report. If you need even more details from the server, you'll have to hit the server to get details about what actually changed there, although this is not a common scenario.

Reporting the exception could be as simple as alerting the user that there was a conflict when updating this payment.

The EntryDetails extension method, which you can find in the downloads on the book's website, creates a string of property names and values from a given ObjectStateEntry. The particular method is designed to supply information to an end user, so it doesn't include EntityKey values or any binary data.

For example, calling this extension method on the Payment entry from the exception shown in Figure 18-7 would return the following:

PaymentDate: 4/6/2008 12:00:00 AM
Amount: 800.0000
ModifiedDate: 5/1/2008 08:23:16 AM

The method is generalized, but you can enhance it even further to fine-tune the details.

If a user modified a variety of data, knowing which specific piece of data was causing the problem could be useful in letting the user decide whether her edits should be sent to the server or whether she would rather have the latest data from the server.

The level of information to access is up to you. Do you want the exception handler to retrieve the current store values as well? Should the user know who made that last change and when? These are common decisions that have to be made for handling concurrent data access, and again, they are not new to the Entity Framework.


18.12. Handling Concurrency Exceptions at a Lower Level

Although the generic ClientWins and StoreWins will suffice for many applications, in some applications more granular exception handling is defined. It's definitely difficult to come up with rules for automating intricate exception handling, but because of the information in the exception, if you do need to go to this level, a lot of possibilities are open to you. The rest of this chapter will explore some more heavy-duty exception handling.

18.12.1. Handling Granular Exceptions Without User Intervention

You may have your own concurrency rules that don't require a user to get involved. Perhaps for Payment entities, your rule is that if the client is editing the amount, the client's data should win; otherwise, refresh the payment information from the server. You may decide that the client should update all contact data. You may not even place a concurrency check on the contact for this reason, but you may have a best practice that requires concurrency checks on every entity.

Because the rule in this case is that all contacts should get a ClientWins, it doesn't make sense to hit them one at a time. So, on the first occurrence of a conflict with a contact, the code will refresh all contacts in the ObjectContext. This will require a little trickery that involves digging through the ObjectStateManager to get a collection of the contacts.

Let's see what the exception code looks like for these scenarios. First, you can separate the logic for the various types into their own methods, as shown in Example 18-23.

Example 18-23. Subroutines for handling exceptions differently for payments than for contacts

Private Sub PaymentRefresh _
   (ByRef entry As ObjectStateEntry, ByRef context As ObjectContext)
 'rule - if amount was changed locally,
 ' then clientwins, otherwise, storewins
  If entry.GetModifiedProperties.Contains("Amount") Then
    context.Refresh(RefreshMode.ClientWins, entry.Entity)
  Else
    context.Refresh(RefreshMode.StoreWins, entry.Entity)
  End If
End Sub

Private Sub ContactRefresh _
   (ByRef entry As ObjectStateEntry, ByRef context As ObjectContext)
 'Contacts will always have a ClientWins refresh
 'Refresh all of the contacts when the first Contact conflict occurs
  Dim contactsinContext = context.EntitiesFromContext(Of Contact)()
  context.Refresh(RefreshMode.ClientWins, contactsinContext)
End Sub

private void PaymentRefresh(ref ObjectStateEntry entry, ref ObjectContext context)
{
 //rule - if amount was changed locally,
 //then clientwins, otherwise, storewins;
  if (entry.GetModifiedProperties().Contains("Amount"))
    context.Refresh(RefreshMode.ClientWins, entry.Entity);
  else
    context.Refresh(RefreshMode.StoreWins, entry.Entity);
}

private void ContactRefresh(ref ObjectStateEntry entry, ref ObjectContext context)
{
 //Contacts will always have a ClientWins refresh
 //Refresh all of the contacts when the first Contact conflict occurs
  var contactsinContext = context.EntitiesFromContext<Contact>();
  context.Refresh(RefreshMode.ClientWins, contactsinContext);
}

EntitiesFromContext is another custom extension method that creates a list of entities of a particular type by pulling the entries for that type out of the ObjectStateManager and then placing the entity object for each ObjectStateEntry into a List. This way, you'll have the whole set of contacts that exist in the context. Example 18-24 shows this method, which extends ObjectContext. If during the course of the call to SaveChanges another concurrency conflict arises with a contact, all of the contacts will be refreshed again.

Example 18-24. Extracting entities from a context using an extension method

<Extension()> _
Public Function EntitiesFromContext(Of TEntity)(ByVal context As ObjectContext) _
 As List(Of TEntity)
  Dim entries = context.ObjectStateManager.GetObjectStateEntries(Of TEntity)()
  Dim list As New List(Of TEntity)
  For Each e In entries
    list.Add(CType(e.Entity, TEntity))
  Next
  Return list
End Function

public List<TEntity> EntitiesFromContext<TEntity>(ObjectContext context)
{
  var entries = context.ObjectStateManager.GetObjectStateEntries<TEntity>();
  List<TEntity> list = new List<TEntity>();
  foreach (var e in entries)
  {
    list.Add((TEntity)e.Entity);
  }
  return list;
}

The updated exception code inside SaveMyChanges now farms out the Refresh call to the appropriate method after it tests to be sure the entry is not a relationship, as shown in Example 18-25.

Example 18-25. Updated exception handling for calling subroutines

Dim conflictEntry = ex.StateEntries(0)
If Not conflictEntry.IsRelationship Then
  Dim entryEntityType = conflictEntry.Entity.GetType.Name
  Select Case entryEntityType
    Case GetType(Contact).Name 'this should refresh customers, too
      ContactRefresh(conflictEntry, context)
    Case GetType(Payment).Name
      PaymentRefresh(conflictEntry, context)
    Case Else 'business rule is to ClientWins refresh anything else
       context.Refresh(RefreshMode.ClientWins, conflictEntry)
  End Select
End If
SaveMyChanges(context)

var conflictEntry = ex.StateEntries[0];
if (! conflictEntry.IsRelationship)
{
  var entryEntityType = conflictEntry.Entity.GetType().Name;
  if (entryEntityType == typeof(Contact).Name) //this should refresh customers, too
      ContactRefresh(conflictEntry, context);
  else if (entryEntityType == typeof(Payment).Name)
      PaymentRefresh(conflictEntry, context);
  else
     context.Refresh(RefreshMode.ClientWins, conflictEntry);
}

You can use a lot of variations of this once you've gotten into the exception and you know how to drill into the details and make some decisions based on what you've found.

18.12.2. Handling Multiple Conflicts

The default method of conflict resolution in the Entity Framework has a few downsides. The first is that none of the data in the context will be saved until every conflict has been resolved. If you are updating a lot of records in a highly concurrent system, your SaveChanges operation may go through many loops before all of the commands execute successfully. The user may or may not notice the delay, but the delay could cause other conflicts.

Another downside is that you can't easily gather a list of all of the conflicts to present to the user at a later time for resolution. You might want to give the user a list of the conflicts, rather than giving the user one conflict at a time, with no indication of how many more there might be. This is because you have to resolve the first conflict encountered before you can get a report of the next conflict; otherwise, that first conflict will keep coming back.

18.12.2.1. Separating the good from the bad

One way to set the conflicting entities aside is to remove them from the context and save all of the entities that don't pose any conflicts. Then, as soon as the save is complete, pull them back into the context in such a way that you can reconstruct their state. This is not a simple task, but you have already learned the necessary steps to pull it off.

When all of the conflicting entries have been removed from the context, SaveChanges will succeed and the other data changes will be applied to the data store. On the book's website, you can find a PersistedStateEntry class in both VB and C# that achieves this pattern. This class takes advantage of many of the things you learned regarding MetadataWorkspace and ObjectStateManager. It also uses reflection because the ObjectContext (and therefore the ObjectStateManager) are not available for setting properties.

There are two principal functions. The first is to store the state entry information. This is done by storing the main ObjectStateEntry's EntityKey, original values, and entity in the constructor and then adding the information for each RelationshipEntry that also came back in the StateEntries. The second main function is performed by the NewEntityfromOrig method which reconstructs the object with its state and the EntityReferences that were defined by the RelationshipEntries in the exception's StateEntries.

Along with the code for the PersistedEntry class on the book's website, you will find an example of a Save routine that uses the class. Essentially, the routine instantiates a list of PersistedEntry objects, and any time an OptimisticConcurrencyException is encountered, a new PersistedEntry is created from the ObjectStateEntry that caused the problem. Its Entity is detached from the context and added into the list. The method repeats this process until all of the exceptions are encountered, and then on a final call to SaveChanges, the valid updates are persisted to the database. The entities that were persisted are reconstructed and reattached to the context along with their relationship information. All of the information about the entity along with the exception's message and any inner exception information are available from the PersistedEntry class for building an informational UI for the end user, for logging the problems, or any other task you may want to perform as part of your exception handling.

This is one pattern for separating conflicting data from good data that not only allows you to get the good data into the database more quickly, but also provides you an opportunity to present all of the conflicts to a user at once.


18.13. Handling Exceptions When Transactions Are Your Own

When you allow the Entity Framework to provide its default transactions, rollbacks and commits will occur automatically. In addition, ObjectContext.AcceptChanges will be called at the end of a successful SaveChanges so that the state of the entities becomes Unchanged.

If, however, you are using your own transactions as described in Chapter 16, you will need to roll back and commit the transactions yourself depending on the success or failure of the call to SaveChanges.

Depending on your application architecture and business rules, you may even choose to commit changes that have already been sent to the database, rather than rolling them back. You will also need to call AcceptAllChanges manually when the commands are completed successfully. Example 18-26 shows a basic pattern for using your own transaction with an OptimisticConcurrencyException.

Example 18-26. Handling an exception in a manual transaction

Using tran As New System.Transactions.TransactionScope
  Try
    context.SaveChanges()
    tran.Complete()
    context.AcceptAllChanges()
  Catch ex As OptimisticConcurrencyException
    'TODO: add code for handling exception
    context.SaveChanges()
  End Try
End Using

using (var tran = new System.Transactions.TransactionScope())
{
  try
  {
    context.SaveChanges();
    tran.Complete();
    context.AcceptAllChanges();
  }
  catch (OptimisticConcurrencyException ex)
  {
    //TODO: add code for handling exception
    context.SaveChanges();
  }
}

Although Complete and AcceptAllChanges won't be executed anytime an exception is thrown, you still may want to separate those calls from the SaveChanges loop.

Example 18-27 shows a pattern that allows you to shift the location of some of the logic.

Example 18-27. Moving the transaction completion into a Finally clause

Dim ChangesSaved as Boolean
Using tran As New System.Transactions.TransactionScope
  Try
    context.SaveChanges()
    ChangesSaved=True
  Catch ex As OptimisticConcurrencyException
    'add code for handling exception
    context.SaveChanges()
  Finally
    If ChangesSaved Then
      tran.Complete()
      context.AcceptAllChanges()
    End If
  End Try
 End Using

bool ChangesSaved = false;
using (var tran = new System.Transactions.TransactionScope())
{
  try
  {
    context.SaveChanges();
    ChangesSaved = true;
  }
  catch (OptimisticConcurrencyException ex)
  {
    //TODO: add code for handling exception
    context.SaveChanges();
  }
  finally
  {
    if (ChangesSaved)
    {
      tran.Complete();
      context.AcceptAllChanges();
    }
  }
}


18.14. Summary

In this chapter, you saw that there are many opportunities for exceptions to be thrown when querying or updating entities. You'll need to catch these exceptions and do something about them, or you will have some very unhappy end users.

The patterns in the chapter concentrated on handling the exceptions within the code where they occurred. Another common pattern is to raise exceptions to a common ExceptionHandler that you can use throughout your application. This is not specific to the Entity Framework, and you can find plenty of guidance on .NET exception handling in the documentation, articles, and other books that focus on handling exceptions.

Rules for handling concurrency problems vary among enterprises and applications. Because it is difficult to even come up with rules for resolving these issues at a granular level, you'll find that most commonly, the three sweeping solutions—client always wins with no concurrency checks, client wins with a complete replacement of the server data, and server wins with a complete replacement of the client data—are the ones chosen.

But you do have some options for handling exceptions in a more detailed way, and hopefully you'll find the patterns that I laid out in the final pages of the chapter both interesting and useful.


Chapter 19. Using Your Own Custom Classes

In Chapter 10, you learned how to extend code-generated entity classes using partial classes and methods. However, this still may not provide enough extensibility for you to insert your own business logic. You may have an existing application with existing classes in which you would like to take advantage of the Entity Data Model (EDM) and the Entity Framework APIs. You may feel too limited by the classes that the EDM Designer generates from the model, and prefer to write your own classes to have true business objects that can integrate into the framework. You may even want to have classes that leverage the Entity Framework, but are not bound to it.

You can create custom classes in two ways. The first is to have your objects inherit from EntityObject. This brings along everything you need to have your class integrate with Object Services. The second method is to implement some key interfaces that will allow your objects to take advantage of the state management and relationship management features of Object Services.

In this chapter, you're going to get a little break from the BreakAway model. The examples here will be against a modified version of Microsoft's sample database, AdventureWorksLT, which is based on the imaginary AdventureWorks Cycles company. The database contains information for tracking customers, orders, and products. The SQL script for creating this modified version of the database, AdventureWorksSuperLT, is available on the book's website.

Be aware that in version 1 of the Entity Framework, you cannot completely separate entity classes from the API. You'll minimally need to implement some interfaces for your classes to work with Object Services. This will be the focus of the second part of this chapter.


19.1. Mapping Classes to the Entity Data Model

Regardless of which method you choose to implement custom classes, the classes must fit into the EDM that they support, or vice versa. The EDM Designer's code generator creates one class for each entity in the model. If you don't use the generated code, you must supply all of the classes the model expects, including the EntityContainer class, which returns the EntitySets.

If you have preexisting classes, you'll need to modify the model to make it line up with your classes. Not only should the entity names match up with the class names, but also the properties and their attributes need to be the same as those in your classes.

19.1.1. Mapping Rules for Custom Classes and Their Properties

Each class must do the following:

  • Apply the EdmEntityTypeAttribute attribute to map to the entities in the model. The entity name and namespace must be identified in the attribute.

  • Contain a property to match every scalar and navigation property in the entity.

Each property of each class must use the EdmScalarPropertyAttribute attribute. Use the attributes parameters to note EntityKeys and non-Nullable values.

19.1.2. Inheriting from EntityObject

As stated earlier, one of the ways to create custom classes that will interact with Object Services is to inherit from EntityObject.

The classes that the EDM Wizard generates inherit from EntityObject. By looking closely at the generated code, you can learn what is required when building your own custom classes in this way. As you read through this chapter, you might find it helpful to reference an open file that was generated from an EDM.

Figure 19-1 shows a simple model created from the Customer, SalesOrderHeader, and SalesOrderDetail tables in the AdventureWorksSuperLT database. In the modified database, I removed many of the non-Nullable fields to simplify the example.

Figure 19-1. A simple model with Customer, Order, and Detail entities

A section of the Order class follows, which will allow you to see how the critical pieces are constructed.

To keep you focused on the changes you need to make to your classes, the only business logic implemented here is to update the Order class's TotalDue property anytime the SubTotal, Freight, or TaxAmt is changed. TotalDue becomes a read-only property in the class, and in the model the TotalDue property's Setter property is Internal. Look for the UpdateTotalDue method at the end of the sample.

You'll want to import these three namespaces to simplify your coding:

  • System.Data

  • System.Data.Objects.DataClasses

  • System.Data.Metadata.Edm

19.1.3. Metadata Attributes

Attributes from the System.Data.Metadata.Edm namespace are used to provide the mapping between your class and the entity in the model. Two attributes that you will need to use throughout your classes:

  • EdmRelationshipAttribute

  • EdmEntityTypeAttribute

EdmRelationshipAttribute is required for each association. The wizard combines all of the EdmRelationshipAttributes at the top of the generated code file. You can put them all together in one class or module as well. Visual Studio creates an AssemblyInfo file for every project. In VB, it's inside the default My Project folder. In C#, it's in the Properties folder. This file is a standard place for assembly attributes.

You could also distribute the EdmRelationshipAttributes among their relevant classes, putting them in the file where the class is defined. However, if you go this route, you have a choice to make. Each relationship involves two classes, but you can't duplicate an assembly attribute in a project. Therefore, you'll have to pick which of the two classes will contain the attribute. As long as it's somewhere in the project, the compiler will find it when the project is built.

The EdmRelationshipAttribute attribute shown in Example 19-1 describes the association that exists between Order and Detail.

Example 19-1. Attributes defining model associations

<Assembly: EdmRelationshipAttribute("AWModel", _
    "FK_OrderDetail_Order_SalesOrderID", "Order", _
    RelationshipMultiplicity.One, GetType(Order), "Detail", _
    RelationshipMultiplicity.Many, GetType(Detail))>

[assembly: EdmRelationshipAttribute("AWModel",
  "FK_OrderDetail_Order_SalesOrderID", "Order",
   RelationshipMultiplicity.One, typeof(Order), "Detail",
   RelationshipMultiplicity.Many, typeof(Detail))]

19.1.4. Entity Classes

As shown in Example 19-2, each entity class needs to have the EdmEntityTypeAttribute with the model's namespace and the name of the entity in the model to which it binds. It also needs to inherit from EntityObject.

Example 19-2. A class inheriting from EntityObject and using an attribute to identify which entity in the model it maps to

<EdmEntityTypeAttribute(NamespaceName:="AWModel", Name:="Order")> _
Public Class Order
  Inherits EntityObject

[EdmEntityTypeAttribute(NamespaceName = "AWModel", Name = "Order")]
public class Order : EntityObject

Following that is a list of the local variables. You may have additional variables that are not in the Order entity in the model, but minimally, you need one for each property of the entity—both scalar and navigation properties (see Example 19-3).

Example 19-3. Creating a local variable for each property in the entity

Private _SalesOrderID As Integer
Private _orderDate As Date
Private _shipDate As Date
Private _subTotal As Decimal
Private _taxAmt As Decimal
Private _freight As Decimal
Private _totalDue As Decimal
Private _modifiedDate As Date
Private _customer As Customer
Private _details As Detail

private int _SalesOrderID;
private DateTime _orderDate;
private DateTime _shipDate;
private decimal _subTotal;
private decimal _taxAmt;
private decimal _freight;
private decimal _totalDue;
private DateTime _modifiedDate;
private Customer _customer;
private Detail _details;

19.1.5. Scalar Properties

As you define the properties of your class, the EdmScalarProperty attribute points the property to a property of the entity in the model (see Example 19-4). EdmScalarProperty takes two parameters: EntityKey and IsNullable. The parameters are not required. Because EntityKey defaults to False and IsNullable to True, if your scalar property is Nullable and is not the EntityKey, you can skip this attribute.

In the Order entity, SalesOrderID is an EntityKey and is not Nullable, so those attributes need to be specified, and therefore the property needs this attribute.

Example 19-4. Using the EdmScalarProperty for scalar properties

  <EdmScalarProperty(EntityKeyProperty:=True, IsNullable:=False)> _
   Public Property SalesOrderID() As Integer
    Get
      Return _SalesOrderID
    End Get
    Set(ByVal value As Integer)
      ReportPropertyChanging("SalesOrderID")
      _SalesOrderID = value
      ReportPropertyChanged("SalesOrderID")
    End Set
  End Property

[EdmScalarProperty(EntityKeyProperty=true, IsNullable=false)]
public int SalesOrderID
{
  get
  {
    return _SalesOrderID;
  }
  set
  {
    ReportPropertyChanging("SalesOrderID");
    _SalesOrderID = value;
    ReportPropertyChanged("SalesOrderID");
  }
}

For C#, you should not use auto-implemented properties. For the entity to be change-tracked, you'll need to report property changes to the object. EntityObject has built-in functionality for that, with the ReportPropertyChanging and ReportPropertyChanged methods. If you want to take advantage of those, you'll need the set clause, which you won't have with an auto-implemented property. If, however, you plan to report changes using a different mechanism, the auto-implemented property will suffice, and you won't need the local variable for the property either.

TaxAmt is a non-Nullable field, so you need to include the IsNullable parameter in the attribute to indicate that the value is false. It also calls the UpdateTotalDue method, which is the only way to set the TotalDue property.

TotalDue is a ReadOnly property. In the database, it's a calculated field, so the application doesn't update it. However, in the application, you certainly don't want to go to the database to have the calculation performed. Each time one of the three properties—TaxAmt, SubTotal, or Freight—changes, UpdateTotalDue will be called and the _totalDue variable will be recalculated. Even though it's read-only, you'll need to have the setter for the C# code. See Example 19-5.

Example 19-5. Calling a method in one property that updates a ReadOnly property

<EdmScalarProperty(IsNullable:=False)> _
Public Property TaxAmt() As Decimal
  Get
    Return _taxAmt 'could be calculated also
  End Get
  Set(ByVal value As Decimal)
    _taxAmt = value
    UpdateTotalDue()
  End Set
End Property

<EdmScalarProperty(IsNullable:=False)> _
  Public ReadOnly Property TotalDue() As Decimal
  Get
    Return _totalDue
  End Get
End Property

Private Sub UpdateTotalDue()
  _totalDue = _subTotal + _taxAmt + _freight
End Sub

[EdmScalarProperty(IsNullable=false)]
public decimal TaxAmt
{
  get
  {
    return _taxAmt; //could be calculated also;
  }
  set
  {
    _taxAmt = value;
    UpdateTotalDue();
  }
}

[EdmScalarProperty(IsNullable=false)]
public decimal TotalDue
{
  get
  {  return _totalDue;
  }
  Set
  {
    _totalDue = value;
  }
}
private void UpdateTotalDue()
{
  _totalDue = _subTotal + _taxAmt + _freight;
}

The other scalar properties for Order are not listed, as they don't demonstrate anything new.

19.1.6. Navigation Properties

For navigation properties to take advantage of the Entity Framework, you'll want to emulate the way the framework's code generator builds them. Let's first take a look at the code-generated properties and then you'll see how to create your own.

Navigation properties get a different attribute:

EdmRelationshipNavigationPropertyAttribute

This attribute needs to know the name of the association as well as the name of the role on the other end. This comes from the properties of the association, which you can see right in the Designer, shown in Figure 19-2.

Figure 19-2. Finding the name of the association in its Properties window

19.1.6.1. EntityCollection navigation properties

Details is an EntityCollection. To modify the collection you need to call Add, Attach, and Remove, but you never set the property directly. Therefore, the Details property must be Read-Only. Notice that this property does not return a variable, but creates the EntityCollection on the fly using the entity's RelationshipManager. You may recall working directly with the RelationshipManager in Chapter 15.

Example 19-6 demonstrates how to define the Details navigation property for the Order class.

Example 19-6. Defining a navigation property for an EntityCollection

<EdmRelationshipNavigationPropertyAttribute("AWModel", _
   "FK_OrderDetail_Order_SalesOrderID", "Detail")> _
Public ReadOnly Property Details() As EntityCollection(Of Detail)
  Get
    Return CType(Me, IEntityWithRelationships).RelationshipManager _
                 .GetRelatedCollection(Of Detail) _
           ("FK_OrderDetail_Order_SalesOrderID", "Detail")
  End Get
End Property

[EdmRelationshipNavigationPropertyAttribute("AWModel",
   "FK_OrderDetail_Order_SalesOrderID", "Detail")]
public EntityCollection<Detail> Details
{
  get
  {
    return ((IEntityWithRelationships)this).RelationshipManager
            .GetRelatedCollection<Detail>
           ("FK_OrderDetail_Order_SalesOrderID", "Detail");
  }
}

19.1.6.2. EntityReference navigation properties

If you want to take full advantage of Object Services, you will want not just the navigation property (Customer in this case), but also an EntityReference. Here's how to construct both of them.

NOTE

I cheated a little here and stole code from a generated file so that you can see how the navigation properties work by default in the Entity Framework. Your custom classes may already have navigation logic that you don't want or need to replace.

Customer is a property in the model, so to map back to the model, it uses the EdmRelationshipNavigationPropertyAttribute.

If you recall the Value property of EntityReference, you will recognize it here. And here is where you can truly see how the EntityReference uses its Value property to supply the value object to the navigation property. In other words, Order.Contact is fulfilled by Order.ContactReference.Value. As you saw in Chapter 17, when you call ContactReference, Object Services uses the RelationshipEntry to find the correct Customer entity to return.

Example 19-7 shows how you can create an EntityReference navigation property in your custom class.

Example 19-7. Defining a navigation property for the value of an EntityReference

<EdmRelationshipNavigationPropertyAttribute("AWModel", _
         "FK_Order_Customer_CustomerID", "Customer")> _
Public Property Customer() As Customer
  Get
    Return CType(Me, IEntityWithRelationships) _
           .RelationshipManager.GetRelatedReference(Of Customer) _
           ("FK_Order_Customer_CustomerID", "Customer").Value
    End Get
  Set(ByVal value As Customer)
    CType(Me, IEntityWithRelationships) _
    .RelationshipManager.GetRelatedReference(Of Customer) _
    ("FK_Order_Customer_CustomerID", "Customer").Value = value
  End Set
End Property

[EdmRelationshipNavigationPropertyAttribute("AWModel",
 "FK_SalesOrder_Customer_CustomerID", "Customer")]
public Customer Customer
{
  get
  {
    return ((IEntityWithRelationships)(this))
            .RelationshipManager.GetRelatedReference<Customer>
            ("AWModel.FK_SalesOrder_Customer_CustomerID", "Customer")
            .Value;
  }
  set
  {
    ((IEntityWithRelationships)(this))
      .RelationshipManager.GetRelatedReference<Customer>
     ("AWModel.FK_SalesOrder_Customer_CustomerID", "Customer").Value
       = value;
  }
}

The CustomerReference EntityReference does not map back to the model; therefore, it does not need an Edm property attribute. This property gets and sets the EntityReference using the GetRelatedReference and InitializeRelatedReference methods of the RelationshipManager (see Example 19-8).

Example 19-8. Defining a navigation property for an EntityCollection

Public Property CustomerReference() As EntityReference(Of Customer)
  Get
    Return CType(Me, IEntityWithRelationships) _
           .RelationshipManager.GetRelatedReference(Of Customer) _
           ("FK_Order_Customer_CustomerID", "Customer")
  End Get
  Set(ByVal value As EntityReference(Of Customer))
    If (Not (value) Is Nothing) Then
      CType(Me, IEntityWithRelationships) _
         .RelationshipManager.InitializeRelatedReference(Of Customer) _
         ("FK_Order_Customer_CustomerID", "Customer", value)
    End If
  End Set
End Property

public EntityReference<Customer> CustomerReference
{
  get
  {
      return ((IEntityWithRelationships)(this))
              .RelationshipManager.GetRelatedReference<Customer>
              ("AWModel.FK_SalesOrder_Customer_CustomerID", "Customer");
  }
  set
  {
    if ((value != null))
       ((IEntityWithRelationships)(this)).
        RelationshipManager.InitializeRelatedReference<Customer>
       ("AWModel.FK_SalesOrder_Customer_CustomerID", "Customer", value);
   }
}
 Custom Classes and Graph Serialization

The default code generator adds serialization attributes to the entity classes and their properties. If you are planning to use these objects in services or in other scenarios where serialization is required, you need these attributes in your custom classes.

The first serialization attribute ensures that a class will be included if it is part of a graph that is being serialized with WCF:

System.Runtime.Serialization.DataContractAttribute(IsReference:=true)

The second serialization attribute allows the class to be either XML or binary serialized:

System.Serializable()

Example 19-9 shows what these look like in the class declaration.

Example 19-9. Making entities serializable for web and WCF services

<DataContractAttribute(IsReference = true), _
 Serializable()> _
Public Partial Class Order
 Inherits EntityObject

[DataContractAttribute(IsReference = true)]
[Serializable()]
 public partial class Order : EntityObject

As you learned in Chapter 14 \when building WCF services, if you want any of the properties to be part of the contract you need to mark them as DataMembers, as shown in Example 19-10.

Example 19-10. Marking entity properties as DataMembers

<EdmScalarProperty(IsNullable:=False), _
 DataMemberAttribute()>  _
Public ReadOnly Property TotalDue() As Decimal

[EdmScalarProperty(IsNullable = false)]
[DataMemberAttribute()]
public decimal TotalDue

19.1.7. Mapping the Remaining Entities in the Model

You'll find that mapping is an all-or-nothing affair. If you have relationships in the model, you will get errors when trying to query the model until you have all of the related entities mapped to classes.

19.1.8. Mapping the EntityContainer

You'll need to have some way to refer to the EntityContainer and the EntitySets within it so that you can query and update. If you don't already have a mechanism to do this, the simplest solution is to let the Entity Framework design tools generate the EntityContainer class and then grab that code. You can do this in the Designer or by using the EDM Generator command-line tool.

19.1.9. Resolving Conflicts Between Custom Classes and Designer-Generated Classes

If your custom classes have the same names as any of the entities, they will also have the same names as the default classes the Designer creates. The simplest solution here is to prevent the Designer from generating any classes. You can do this by removing the model's Custom Tool property, which by default is EntityModelCodeGenerator. This is available in the Properties window when you select the model in your solution.

19.1.10. Querying Using the Custom Classes

Once you have the classes mapped to the model you should be able to perform LINQ to Entities and ObjectQuery queries against the model. You may find that until you have everything properly in place, the debugger will help you find where you need to apply more mappings. Be patient and read the error messages carefully. They generally include all the information you'll need to fix each problem.


19.2. Implementing the IPOCO Interfaces

You may not even want your existing or new classes to inherit from EntityObject. You can still take advantage of the change-tracking and relationship management capabilities in Object Services by applying some key interfaces.

IPOCO

POCO stands for Plain Old CLR Objects, which are objects that do no more than what is defined by their type. An object that inherits from other objects is tightly bound and dependent on those objects.

Microsoft refers to the use of these interfaces as IPOCO—the first step toward enabling objects to be used in the Entity Framework without having to inherit from EntityObject. But still, the objects that implement the interfaces are not POCO objects. Look for the ability to use completely unbound objects in the Entity Framework in the next version (in .NET 4.0/Visual Studio 2010). In addition, an advisory board comprising some of the most important thinkers in domain-driven design is working with the Entity Framework team on planning future versions.

Even with the interfaces, you still need to use the model-mapping attributes, as described in Section 19.1.2.

The three interfaces are as follows:

  • IEntityWithKey

  • IEntityWithChangeTracker

  • IEntityWithRelationships

As the interface names indicate, each gives your custom class the capabilities of the EntityKey, change tracking within the ObjectContext, and relationship management with the ObjectContext.

We'll use an Order class again as an example.

19.2.1. The IEntityWithKey Interface

IEntityWithKey is an optional interface that allows the ObjectContext to easily identify the EntityKey of your class. Without it, you will have to explicitly provide the EntityKey property for various functions.

To implement this interface you need to do three things. First, implement it in the class declaration, as shown in Example 19-11.

Example 19-11. Implementing the interface in the class declaration

<EdmEntityTypeAttribute(NamespaceName:="AWModel", Name:="Order")> _
Public Class Order
  Implements IEntityWithKey

[EdmEntityTypeAttribute(NamespaceName="AWModel", Name="Order")]
public class Order : IEntityWithKey

Next, you need to implement the interface's EntityKey property. The EntityKey property is not the same as the properties for OrderID or CustomerID in the classes. The value of this property is an actual EntityKey object with one or more KeyValuePairs identifying the name and value of each property that makes up the EntityKey.

This property depends on a class-level variable, _entityKey, which needs to be declared, as shown in Example 19-12.

Example 19-12. Declaring the _entityKey class-level variable

Private _entityKey As EntityKey

Property EntityKey() As EntityKey Implements IEntityWithKey.EntityKey
  Get
    Return _entityKey
  End Get
  Set(ByVal value As EntityKey)
    _entityKey = value
  End Set
End Property

public EntityKey EntityKey
{
  get
  {
    return _entityKey;
  }
  set
  {
    _entityKey = value;
  }
}

NOTE

If you don't use EntityKey in your class, any of the work you do with change tracking and relationships will require that you explicitly provide an entity key. The rest of the explanation of the interfaces assumes you are using the IEntityWithKey interface.

19.2.2. The IEntityWithChangeTracker Interface

The IEntityWithChangeTracker interface allows your class to interact with the ObjectStateManager so that you can get change tracking on your object. Implement it in the class declaration along with IEntityWithKey, as shown in Example 19-13.

Example 19-13. Implementing the IEntityWithChangeTracker interface

<EdmEntityTypeAttribute(NamespaceName:="AWModel", Name:="Order")> _
Public Class Order
Implements IEntityWithKey,IEntityWithChangeTracker

[EdmEntityTypeAttribute(NamespaceName="AWModel", Name="Order")]
public class Order : IEntityWithKey, IEntityWithChangeTracker

You'll need to implement this interface's SetChangeTracker method so that Object Services can associate your objects with the proper ObjectContext, as it is possible to have multiple contexts in memory at the same time (see Example 19-14).

Example 19-14. Implementing the private _changeTracker as IEntityWithChangeTracker

Public Sub SetChangeTracker(ByVal changeTracker _
    As IEntityChangeTracker) _
    Implements IEntityWithChangeTracker.SetChangeTracker
  _changeTracker = changeTracker
End Sub

public void SetChangeTracker(IEntityChangeTracker changeTracker)
{
  _changeTracker = changeTracker;
}

NOTE

If you have any properties that are complex types in the entity, you will need to set the change tracker for those as well. See the Entity Framework documentation for an example.

With the change tracker in place, you'll need to be sure that it is aware of changes to any properties, including the EntityKey. You can do this by calling the change tracker's EntityMemberChanged and Changing methods before and after the value is being changed.

Example 19-15 shows the EntityKey property after you have implemented these changes. The code needs to check that the change tracker has been set before calling these methods.

Example 19-15. The EntityKey property after implementing the EntityMemberChanged and Changing methods

Property EntityKey() As EntityKey _
   Implements IEntityWithKey.EntityKey
  Get
    Return _entityKey
  End Get
  Set(ByVal value As EntityKey)
    If Not _changeTracker Is Nothing Then
      _changeTracker.EntityMemberChanging _
          (StructuralObject.EntityKeyPropertyName)
      _entityKey = value
      _changeTracker.EntityMemberChanged _
          (StructuralObject.EntityKeyPropertyName)
    Else
      _entityKey = value
    End If
  End Set
End Property

public EntityKey EntityKey
{
  get
  {
    return _entityKey;
  }
  set
  {
    // Set the EntityKey property, if it is not set.
    // Report the change if the change tracker exists.
    if (_changeTracker != null)
    {
       _changeTracker.EntityMemberChanging
        (StructuralObject.EntityKeyPropertyName);
       _entityKey = value;
       _changeTracker.EntityMemberChanged
        (StructuralObject.EntityKeyPropertyName);
     }
     else
       _entityKey = value;
  }
}

The scalar properties in your IPOCO class also need to implement the change tracker. Example 19-16 shows OrderDate as an example of how the scalar properties should look.

Example 19-16. OrderDate implementing the change tracker

<EdmScalarPropertyAttribute(IsNullable:=False)> _
Public Property OrderDate() As Date
  Get
    Return _orderDate
  End Get
  Set(ByVal value As DateTime)
    If Not _changeTracker Is Nothing Then
      _changeTracker.EntityMemberChanging("OrderDate")
      _orderDate = value
      _changeTracker.EntityMemberChanged("OrderDate")
    Else
      _orderDate = value
    End If
  End Set
End Property

[EdmScalarPropertyAttribute(IsNullable = false)]
public System.DateTime OrderDate
{
  get
  {
    return _orderDate;
  }
  set
  {
    if (_changeTracker != null)
    {
      _changeTracker.EntityMemberChanging("OrderDate");
      _orderDate = value;
      _changeTracker.EntityMemberChanged("OrderDate");
    }
    else
      _orderDate = value;
  }
}

19.2.2.1. An EntityState property

Note that because these objects do not inherit from EntityObject, the EntityState property is not readily exposed. A quick peek at the EntityObject.EntityState property in Reflector shows you how to get that into your class. It's exposed through the change tracker. You can create your own EntityState property on your object in the same way:

Public ReadOnly Property EntityState() As EntityState
  Get
    Return _changeTracker.EntityState
  End Get
End Property

public EntityState EntityState
{
  get
  {
    return _changeTracker.EntityState;
  }
}

19.2.3. The IEntityWithRelationships Interface

The IEntityWithRelationships interface is necessary only for classes that have any relationships involved. It lets the ObjectContext create and maintain relationships when you have graphs in its cache. The IEntityWithRelationships interface provides the RelationshipManager, which can do its work silently in the background or, as you saw in Chapter 15, can be explicitly coded against.

Add the IEntityWithRelationships interface to the other interfaces in the class declaration, as shown in Example 19-17.

Example 19-17. Implementing the IEntityWithRelationships interface

<EdmEntityTypeAttribute(NamespaceName:="AWModel", Name:="Order")> _
Public Class Order
  Implements IEntityWithKey, IEntityWithChangeTracker, _
             IEntityWithRelationships

[EdmEntityTypeAttribute(NamespaceName = "AWModel", Name = "Order")]
  public class Order : IEntityWithKey, IEntityWithChangeTracker,
                       IEntityWithRelationships

Then implement its RelationshipManager property, which is ReadOnly (see Example 19-18).

Example 19-18. Enabling the RelationshipManager

Dim _relManager As RelationshipManager

ReadOnly Property RelationshipManager() As RelationshipManager _
    Implements IEntityWithRelationships.RelationshipManager
  Get
    If _relManager Is Nothing Then
      _relManager = RelationshipManager.Create(Me)
    End If
    Return _relManager
  End Get
End Property

public RelationshipManager RelationshipManager
{
  get
  {
    if (_relManager == null)
      _relManager = RelationshipManager.Create(this);
    return _relManager;
  }
}

19.2.4. Working with the IPOCO-Enabled Objects

With the interfaces in place, querying data should work no differently than with classes that inherit from EntityObject. The code in Example 19-19 calls the custom EntityState property.

Example 19-19. Querying for orders

Using context = New IPOCO_Objects.AWEntities
  Dim ords = context.Orders
    For Each o In ords
      Console.WriteLine("{0}: {1}", o.OrderDate, o.EntityState)
    Next
End Using

using (var context = new IPOCO_Objects.AWEntities())
{
  var orders = context.Orders;
  foreach (var o in orders)
    Console.WriteLine("{0}: {1}", o.OrderDate, o.EntityState);
}

Deferred loading also functions correctly because of the IEntityWithRelationships interface, as shown in Example 19-20.

Example 19-20. Navigating relationships and loading related data

Using context = New IPOCO_Objects.AWEntities
  Dim custs = context.Customers _
     .Where(Function(c) c.Orders.Any().ToList
  For Each c In custs
    c.Orders.Load()
    If c.Orders.Count > 0 Then
      Console.WriteLine(c.Orders.Count)
    End If
  Next
End Using

using (var context = new IPOCO_Objects.AWEntities())
{
  var custs = context.Customers.Where(c => c.Orders.Any()).ToList();
  foreach (var c in custs)
  {
    c.Orders.Load();
    if (c.Orders.Count > 0)
      Console.WriteLine(c.Orders.Count);
  }
}

Thanks to the IEntityWithChangeTracker and IEntityWithRelationships interfaces, not only do you get the benefit of change tracking and SaveChanges on these custom objects, but also the RelationshipManager is able to automatically build the graphs for you just as it does with POEOs, that is, Plain Old Entity Objects (which is not an official term but one I invented for your entertainment value).


19.3. Custom Class Assemblies and Entity Data Model Files

Most likely, you'll compile your custom classes into their own assembly or assemblies. What happens to the model in this case?

By default, the model files (the .csdl, .msl, and .ssdl files) are embedded into the assembly of the project where the EDMX is contained. And by default, the assembly would normally contain the code-generated classes as well. Even if you have an assembly referenced in a project, .NET will only discover assemblies that your code leads to. Because your application will be using classes in the model's assembly, System.Reflection can find the assembly and, therefore, the embedded resources as well.

However, when you're using custom classes, where is your model? If you keep your model in its own project and use the default build method of embedding it into an assembly, the embedded files will not be found. Why? Without an actual class to call in the assembly, the assembly will never be found through the code path, and therefore it will not be considered a referenced assembly. Even if you explicitly loaded the assembly, it still won't be a referenced assembly.

19.3.1. Accessing the Model Files When They Are Not in a Referenced Assembly

Remember that embedding the model files into their project assembly is an option, but not a requirement. If you change the model's Metadata Artifact Processing property to Copy to Output Directory, you will have access to the actual files. Now you can place them anywhere in the filesystem, and have the metadata attribute of the connection string point directly to the file path (see Example 19-21).

Example 19-21. A ConnectionString to find the EDM files in the file path

connectionString=
"metadata=C:\EDMFiles\AWModel.csdl|C:\EDMFiles\AWModel.ssdl|
          C:\EDMFiles\AWModel.msl;
 provider=System.Data.SqlClient;
 provider connection string = 'Data Source=MyServer;
                               Initial Catalog=AdventureWorksLT;
                               Integrated Security=True;
                               MultipleActiveResultSets=True'"


19.4. Summary

Although the default code generation creates entity classes with no business logic, you do have a lot of opportunities to customize entities and add that logic. If you are happy to use the generated classes, you can take advantage of partial classes, partial methods, and a handful of public events to insert your own business logic.

If you need more independence from the generated classes but still want your objects to interact directly with the ObjectContext, you still have a number of options: you can build your own code generator, inherit EntityObject in your own custom classes, or implement the IPOCO interfaces on your objects. The last two options also allow you to use existing classes; though you need to be sure you follow all of the rules for mapping them properly to your model.


Chapter 20. Using the Entity Framework in n-Tier Client-Side Applications

Many applications implement their user interface, business logic, and data access layer completely on the client side. This is typical in scenarios where the application is completely self-contained on the end-user's machine or users are on a corporate intranet.

Even with these smaller applications, however, you should do your best to separate the UI from business logic and data access logic.

This is not a big challenge for the Entity Framework, because it is reasonable to work with a long-running ObjectContext and take advantage of the Entity Framework's change tracking from the time the data is queried to the time that changes are persisted to the database.

The Entity Framework handles all of the interaction with the database and can be thought of as a data access layer. Through the use of partial classes or your own custom classes, a business layer can provide some of the logic that pertains directly to data, such as the validations I demonstrated earlier in the book. But the tasks of interacting with the data store and validating data are not responsibilities of the user interface. So in this chapter, we will focus on separating the logic of interacting with the user from that of interacting with objects and data.

This chapter will work through many of the important tasks of building a layered, client-side application. Although it will use a specific application as its focus, you will not be building the application step by step, but rather investigating critical elements. The complete source code for this application is available on the book's website.


20.1. Thinking in Layers

Figure 20-1 portrays a basic architecture for client applications using the Entity Framework. However, the separation isn't really quite as tidy as the figure depicts. For instance, the entity classes contain business logic, and some might consider the act of asking the ObjectContext to get data to be the work of a data layer. But for pragmatic reasons, the examples in this chapter are based on the simple three-layer architecture shown in Figure 20-1.

Figure 20-1. A basic architecture for client applications using the Entity Framework

In Figure 20-1, the user interface (a Windows form) will not make calls to the data access layer directly. Instead, the Windows form will stay focused on its task of interacting with the user. In between the Windows form and the model's assembly is the business layer. The Windows form communicates with the business layer and the business layer communicates with the data layer. The Windows form might leverage other classes in the business layer that provide logic for business rules or other functionality that your business domain requires.

The ObjectContext acts as a bridge between the Entity Framework and the entity classes that you'll use in your applications. In the Windows Forms applications you built in Chapter 8, the UI itself instantiated the ObjectContext and queried and saved changes to the objects directly. These are not tasks for the UI. But where do they belong? Some will argue that these are data access layer tasks. Others will argue that because these actions do not interact directly with the database they are not data access tasks but business tasks.

What I propose is a layer or class that bridges the UI, the business logic, and the data access logic, as shown in Figure 20-2. I've chosen to call this a DataBridge.

Figure 20-2. A DataBridge that bridges the UI, the business logic, and the data access logic

The DataBridge owns the ObjectContext. It will instantiate it, make Entity Data Model (EDM) queries with it, call SaveChanges with it, and perform any interactions with the objects it is managing. The DataBridge will rely on a generic class that will execute any queries and commands using exception handling. For example, this generic class called CommandExecutor will contain the GetReferenceList, ExecuteList, and ExecuteFirstorDefault methods described in Chapter 18, along with some other methods. These methods are put into their own class, so they can be reused throughout the application.

20.1.1. Organizing Your Layers

Although it's not a requirement, using separate projects and assemblies for the different types of logic is the preferred way to keep things organized. A logical layer won't necessarily align with a physical layer. You will find that some elements that fit in a particular logical layer will need to physically exist in a project with elements from another layer.

The project that contains the EDM, its entity objects, and the extended logic for those objects is a good example of this. Here you will find some elements that would fit into the scope of business logic, and others that might be defined as data access logic, yet they all need to be in the same assembly.

Is It Business Logic or Data Access Logic?

Your own definition of what belongs in a data access layer and what belongs in a business layer may be very different from that of another developer. To some, only code that interacts directly with the data store belongs in a data access layer. To others, code that is responsible for querying and returning data belongs in the data access layer. When you write queries against the EDM, you are not interacting directly with the database; the Entity Framework does that for you. Expect to find different opinions on exactly where the query logic belongs. In the meantime, for the sake of this chapter, we won't worry too much about the semantics, and instead we'll get on with the business of ensuring that whatever you want to call this logic, it is not implemented in the user interface.

In this chapter, you will create a single class that acts as a bridge between the UI and the Entity Framework. The class will be in its own project. It will be called by the user interface and in turn it will work with the EDM and classes in the BreakAway assembly as well as leverage the Entity Framework directly. We'll place the CommandExecutor class into this project for the sake of simplifying the sample.

This DataBridge will focus on the needs of a Windows form that uses the entities in data-bound controls. Throughout the chapter, I will highlight some of the things you'll need to consider that are specific to using entity objects in a Windows form and explain why to choose certain options over others.

The Entity Framework on the Client

Windows Forms and WPF applications are perfect candidates for using the Entity Framework directly, since they have full access to the .NET Framework. Although Silverlight applications are also client-side applications, the Silverlight runtime does not include the Entity Framework or ADO.NET, and is therefore dependent on web services to provide its data.


20.2. Finding Your Motivation: A Master/Detail Data Entry Form That Will Use the DataBridge Class

Before getting into the details of what might be in the DataBridge class, it is helpful to have some knowledge of the needs of its consumer, which is to be a Windows Forms data entry form that allows a user to manage customers and their reservations. The form uses only the business layer as a conduit for retrieving and returning all of the data that is needed in the user interface.

Master/detail data entry is pretty standard for business applications. The DataBridge class will need to perform some typical tasks: providing selection lists, retrieving a specific entity graph from the data store to pass to the calling class (in this case, the UI), persisting changes back to the database, and more.

By looking at the form, which appears in Figure 20-3, you'll get an idea of what you will need in the new class.

Figure 20-3. A typical master/detail data entry form

The form provides a filtered list of customers or potential customers from which the user can select. Then the user can edit any of the customer information as well as view or edit the customers' reservations. This form works with a lot of relationships, which the Entity Framework handles with ease. If there are any unsaved changes, the form will prompt the user to save or roll back edits when he attempts to move to another customer. If you are used to working with DataSets, you'll see that the rollback feature definitely requires more effort with the Entity Framework.

Because we are using the Entity Framework, we will need to address some specific challenges that exist because the consumer of the DataBridge class happens to be a Windows form.


20.3. Preventing Non-UI Logic from Leaking into the UI

When writing applications it is helpful to constantly ask yourself, "Whose job is it to perform this task?" Unless you are writing Rapid Application Development (RAD) applications, as we did in Chapter 8, interacting with the database is not the job of the user interface. The UI's job is to interact with the user. When you need to add some new logic, such as performing a calculation based on an object's values or determining whether an object's properties are valid for updates, ask yourself whether that is within the scope of interacting with the user. If not, which layer should be performing that task?

This also means that the UI developer might have a need that is not met by one of the other layers it is using. Writing a layer that will meet the needs of any UI developer who uses it will require plenty of tweaks to accommodate not only the UI but also the entities. Some of the special requirements aren't even specific to the Entity Framework. For example, a Windows form GridViewComboBox does not have a SelectedItem property that will cause classes to display their class names (e.g., BAGA.Trip). The known workaround for this is to provide a Self property in the object that is being bound to the ComboBox. This is unlikely to preexist in the business layer, but if the UI developer encounters this problem, the Self property is going to need to be added somewhere.

Although it's difficult to predict all of these types of issues, it's important to pay attention to any instances where you might find yourself putting business logic into the UI layer to solve these problems.

Some business logic is specific to the model and some is specific to your business domain. Whether you can prepare for tweaks in advance or you encounter them when you are working on the UI layer, you'll also need to consider whether the logic belongs in the partial classes of the entities or in the business layer that sits between your UI and your entities and model.


20.4. Implementing Logic That Fits Best in the Entity Partial Classes

The Self property mentioned earlier solves a specific UI problem, but the logical place for it is directly in the Trip class. This means adding the property into the Trip entity's partial class. As you work through building the DataBridge and then trying to implement it in a UI, you may not want to just throw every new piece of logic into the DataBridge. Like the Self property, it may make more sense to add it into the entities' partial classes or even the partial class of the ObjectContext.

Example 20-1 shows the new properties for the Trip class. You might want to add a similar property to any entity class that might be used in data binding.

Example 20-1. Adding a Self property to the partial class

Public ReadOnly Property Self() As Trip
  Get
    Return Me
  End Get
End Property

public Trip Self
{
  get { return this; }
}

You might find that the UI requires other types of information. Another property that the entity classes would benefit from is a BalanceDue property for Reservation, as shown in Example 20-2. Again, this property is specific to an entity class, Reservation, and therefore the entity's partial class is a logical place to implement it, although you may choose to organize your business layer classes differently.

NOTE

Remember that these custom properties are not available for queries, since they aren't in the model itself.

Example 20-2. The BalanceDue property

Public ReadOnly Property BalanceDue() As String
  Get
    If Me.UnpaidReservations.Count = 1 Then
      Dim res = Me.UnpaidReservations.ToList()(0)
      Return (res.Cost - res.PaymentTotal).ToString()
    Else
      Return "n/a"
    End If
  End Get
End Property

public string BalanceDue {
get
{
   if (this.UnpaidReservations.Count == 1)
   {
     var res = this.UnpaidReservations.ToList()[0];
     return (res.Cost - res.PaymentTotal).ToString();
   }
   else
     return "n/a";   }
}

Now the UI developer has easy access to Reservation.BalanceDue without having to create this logic in the UI's code.

20.4.1. Creating an IsDirty Property for the ObjectContext

Many applications rely on some type of IsDirty flag to validate a user entry before a user can move to a new record or exit the form completely. An IsDirty flag supports the common pattern that asks a user whether he wants to save his changes before he leaves a record.

Because changes made in the UI will flow to the ObjectContext, you can easily query the context's ObjectStateEntries to see whether the EntityState is something other than Unchanged. This logic fits nicely into the BAEntities partial class, as it might be useful to access from other applications. If you want to keep this logic out of the model's classes, you can put it into the new class you will be building.

The new IsDirty property of BAEntities checks whether an ObjectStateEntry is Added, Deleted, or Modified. This will take into account changed relationships as well. If entries are found, IsDirty returns True. The code in Example 20-3 adds an IsDirty property to the BAEntities' partial class.

Example 20-3. Adding an IsDirty property to the BAEntities' partial class

Public ReadOnly Property IsDirty() As Boolean
  Get
    If Me.ObjectStateManager.GetObjectStateEntries _
      (EntityState.Added Or EntityState.Deleted Or _
       EntityState.Modified).Count > 0 Then
      Return True
    Else
      Return False
    End If
  End Get
End Property

public bool IsDirty
{
  get
  {
    if (this.ObjectStateManager.GetObjectStateEntries
      (EntityState.Added | EntityState.Deleted | EntityState.Modified)
      .Count() > 0)
       return true;
    else
      return false;
  }
}

You can take this a step further and make IsDirty an extension method of System.Data.Objects.ObjectContext so that it's not even necessary to add it to the partial class for every model's context.


20.5. Building the CommandExecutor Class

Because the DataBridge will rely on the CommandExecutor class, you will want to build that first.

The CommandExecutor class will contain the four Execute methods created in Chapter 18.

Having the methods separated out in this class allows you to not only provide any common exception handling when queries or other commands are being executed, but you can also encapsulate all of the actual execution in this class. When building the DataBridge class, you'll see that there is one unavoidable exception to this rule—calling functions that are methods of the context.

Example 20-4 shows the methods of the CommandExecutor class. The exception handlers are abbreviated because of the length of the code listing. In addition to the four methods already discussed, you will also see the GetReferenceList method from Chapter 17 and one additional method that allows you to take advantage of the GetObjectByKey method. There's a twist to the GetReferenceList. Unfortunately, Entity Framework entities do not support sorting with data-bound controls. Therefore, it's necessary to sort in the query. The method now takes an additional parameter to define what property to sort by. If you want to be able to pass in multiple properties, you'll need to revise the method so that it takes an array, then you can concatenate each string with the reference variable, refData, when building the Entity SQL expression.

NOTE

I toyed with the idea of creating an interface called IReferenceList to apply to entities that might be used as reference lists, then using properties named DisplayMember, ValueMember, and SortMember. When working with one of these lists, it meant that I wouldn't have to pass around the various property names for sorting or the display fields. Implementing this throughout the layers became cumbersome and overkill for this small solution, so I chose not to follow that path. When building your apps, you should consider interfaces as a means for providing common functionality across entities.

You'll see in the DataBridge class how each of these methods is used.

Example 20-4. The CommandExecutor class

Namespace DAL
Public Class CommandExecutor
  Public Function ExecuteFirstorDefault(Of TEntity) _
   (ByVal objectQuery As ObjectQuery(Of TEntity)) As TEntity
    Try
      Return objectQuery.FirstOrDefault()
    Catch ex As EntitySqlException
      Throw ex 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
    End Try
  End Function

  Public Function ExecuteFirstorDefault(Of TEntity) _
   (ByVal L2EQuery As IQueryable(Of TEntity)) As TEntity
    Try
      Return L2EQuery.FirstOrDefault()
    Catch ex As InvalidOperationException
      Throw ex 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
      End Try
    End Function

  Public Function ExecuteList(Of TEntity) _
  (ByVal objectQuery As ObjectQuery(Of TEntity)) As List(Of TEntity)
    Try
      Return objectQuery.ToList()
    Catch ex As EntitySqlException
      Throw ex 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
    End Try
  End Function

  Public Function ExecuteList(Of TEntity) _
  (ByVal L2EQuery As IQueryable(Of TEntity)) As List(Of TEntity)
    Try
      Return L2EQuery.ToList()
    Catch ex As InvalidOperationException
      Throw 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
    End Try
  End Function

  Public Function GetReferenceList(Of TEntity)(ByVal context As ObjectContext) _
   ByVal orderBy as String As List(Of TEntity)
    Dim entityName = GetType(TEntity).Name
    Dim eSetName = context.MetadataWorkspace.GetEntitySetFullName(entityName)
    Dim esql = "SELECT VALUE refData FROM " & eSetName & " AS refData " & _
               "ORDER BY refData." & orderBy;
    Dim objQuery = context.CreateQuery(Of TEntity)(esql)
    Return objQuery.ToList
  End Function

  Public Function ExecuteGetObjectByKey(Of TEntity) _
   (ByVal context As ObjectContext, ByVal key As EntityKey) As TEntity
    Dim entity As New Object
    Try
      If context.TryGetObjectByKey(key, entity) Then
        Return CType(entity, TEntity)
      Else
        Return Nothing
      End If
    Catch ex As ArgumentException
      If ex.Message.Contains("specified named connection is either not found") Then
        Throw 'TODO: Replace with handling code
      Else
        Throw 'handle or Throw the exception
      End If
    'additional exceptions as described in Chapter 18
    End Try
  End Function
End Class
End Namespace

namespace BAGA.DAL
public class CommandExecutor
{
  public TEntity ExecuteFirstorDefault<TEntity>(ObjectQuery<TEntity> objectQuery)
  {
    try
    {
      return objectQuery.FirstOrDefault();
    }
    catch (EntitySqlException ex)
    {
      throw ex; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public TEntity ExecuteFirstorDefault<TEntity>(IQueryable<TEntity> L2EQuery)
  {
    try
    {
      return L2EQuery.FirstOrDefault();
    }
    catch (InvalidOperationException ex)
    {
      throw ex; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public List<TEntity> ExecuteList<TEntity>(ObjectQuery<TEntity> objectQuery)
  {
    try
    {
      return objectQuery.ToList();
    }
    catch (EntitySqlException ex)
    {
      throw ex; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public List<TEntity> ExecuteList<TEntity>(IQueryable<TEntity> L2EQuery)
  {
    try
    {
      return L2EQuery.ToList();
    }
    catch (InvalidOperationException ex)
    {
      throw; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public List<TEntity> GetReferenceList<TEntity>(ObjectContext context, string orderBy)
  {
    var entityName = typeof(TEntity).Name;
    var eSetName = context.MetadataWorkspace.GetEntitySetFullName(entityName);
    var esql = "SELECT VALUE refData FROM " + eSetName + " AS refData " +
               "ORDER BY refData." + orderby;
    var objQuery = context.CreateQuery<TEntity>(esql);
    return objQuery.ToList();
  }

  public TEntity ExecuteGetObjectByKey<TEntity>(ObjectContext context, EntityKey key)
  {
    object entity = new object();
    try
    {
      if (context.TryGetObjectByKey(key, out entity))
        return (TEntity)entity;
      else
        return default(TEntity);
    }
    catch (ArgumentException ex)
    {
      if (ex.Message.Contains("specified named connection is either not found"))
        throw; //TODO: Replace with handling code
      else
        throw; //handle or Throw the exception
      //additional exceptions as described in Chapter 18
    }
  }
}
}

The last two methods require that you pass in the context being used by the calling class. This is not necessary in the first four methods because the context is already bound to the query that comes in as a parameter. What's nice about these methods using the query from the calling class is that they don't care if it is a short-lived context, as you would have in web services or web applications, or if it is a long-running context, such as the one you will use for this smart client application. That makes the CommandExecutor class truly reusable.

With the CommandExecutor class in place, let's take a look at the DataBridge class.


20.6. Building the DataBridge Class

The new DataBridge class is where the ObjectContext will live. It will be responsible for creating queries and getting them executed by the CommandExecutor to return data to the UI, as well as sending UI edits back to the database. It will also perform other ObjectContext-related tasks along the way.

The most critical element of the DataBridge is the ObjectContext, which will need to stay in memory to keep track of changes to the entity objects until it is time to call SaveChanges.

20.6.1. Using a Long-Running ObjectContext

The simplest way to achieve the architecture shown in Figure 20-1 is with a long-running ObjectContext. When the user interface instantiates your bridge class, the class can instantiate a new context and continue to use that context until the UI disposes the bridge class.

As long as the instantiated bridge class is in scope—staying in memory, and therefore keeping the ObjectContext alive—the user interface can retrieve, modify, and save data without having to worry about the complexities of maintaining state.

Much of the bridge's work will focus on interacting with the EDM and the ObjectContext. All of the benefits of querying against a model are available to you, including the simplicity of working with related data. Developers are used to providing other common functions to the client as well. They often use properties such as IsDirty to identify whether any changes have been made to an object. The ability to roll back changes if the user decides to cancel his updates is an important feature. You can use all of the tools you have been learning about in this book to provide these capabilities in your business layer.

20.6.2. Implementing the Primary Elements of the DataBridge Class

The DataBridge class will contain an ObjectContext that will be responsible not only for retrieving data through the EDM, but also for providing change-tracking support and calling SavingChanges back to the database. To do this the ObjectContext will need to remain in scope for the life of the class.

You can instantiate the context in the class constructor.

Additionally, this class will find many opportunities to take advantage of the model's EntitySet name and EntityContainer name. So, in this constructor, you can create and populate variables to retain the EntityContainer and EntitySet names. Although you'll instantiate the context and these variables in the class constructor, you'll need to declare them as class-level variables.

NOTE

I'll describe the most important pieces of this solution in this chapter, highlighting issues that are specific to working with the Entity Framework. The entire solution is available as a download on the book's website.

Example 20-5 shows the first step in creating this new class, which we'll call DataBridge.

Example 20-5. The shell for the new class

Public Class DataBridge
  Private _commonContext as BAEntities
  Private _dal As BAGA.DAL.CommandExecutor

  Public Sub New()
    _commonContext = New BAEntities ()
    _dal = New BAGA.DAL.CommandExecutor
  End Sub

  Public ReadOnly Property IsDirty() As Boolean
    Get
      Return _commonContext.IsDirty
    End Get
  End Property
End Class

public class DataBridge //: IDisposable
{
  private BAEntities _commonContext;
  private BAGA.DAL.CommandExecutor _dal;

  public DataBridge()
  {
     _commonContext = new BAEntities();
     _dal = new BAGA.DAL.CommandExecutor();
  }
  public bool IsDirty
  {
   get
  {
   return _commonContext.IsDirty;
  }
 }
}

The context is named _commonContext and will exist for the lifetime of the business object. Once you've created this context, you can use it throughout the class.

Notice that the DataBridge class also has a property for encapsulating the new BAEntities.IsDirty property so that the client can have easy access to the property.

20.6.3. Creating a Class for Lightweight Objects to Be Used in UI Pick Lists

The form will depend on a variety of lists from which to choose. The most important list is the customer list from which the user will select a customer to view or edit. The Customer class has a lot of properties in it. It would be a wasteful use of resources to return all of the information for every customer to populate that list. Instead, populate the lists using projection queries that return only the ID and Name rather than all of the properties of a Customer.

The ID and Name fields are instead encapsulated in a small custom class called KeyListItem, which you can use generically for lists that you will use in data-bound controls (see Example 20-6).

The class is necessary for two reasons. The first is so that you can reference it in the signature of the method that returns the lists: Public Function CustomerNamesList() As List(Of KeyListItem). Otherwise, you would be returning an anonymous type, and it is not possible to identify an anonymous type as a type in the signature.

The second reason is that although you can reference a struct in the signature, a struct won't suffice because it is not possible to do a projection query into a type that doesn't have named properties. Structs have only fields, no properties.

Example 20-6. The KeyListItem class

Public Class KeyListItem
    Private _ID As Integer
    Private _Name As String
    Public Property ID() As Integer
      Get
        Return _ID
      End Get
      Set(ByVal value As Integer)
        _ID = value
      End Set
    End Property
    Public Property Name() As String
      Get
        Return _Name
      End Get
      Set(ByVal value As String)
        _Name = value
      End Set
    End Property
  End Class

public class KeyListItem
{
  public int ID  { get; set; }
  public string Name{ get; set; }
}

20.6.4. Creating the Main Entity Pick List: For Customer Names

Now that you have the KeyListItem class, you can build the most important pick list, the one that users will use to select which customer to work with.

The class will offer some flexibility with respect to filtering the list: Customers Only, Customers on a Trip, and All Contacts (customers and noncustomers). Example 20-7 shows a method that uses a LINQ to Entities query to extract the customer IDs and names for all Customer entities. The method returns a List of KeyListItems. To do this the query uses an object initializer to create a new KeyListItem on the fly from the ID and Name that are selected in the query. Notice that after the query is defined, it is passed to _dal, the CommandExecutor, to execute the query.

Example 20-7. Extracting the customer IDs and names using a LINQ to Entities query

Public Function CustomerNamesList() As List(Of KeyListItem)
  Dim custs = From c In _commonContext.Contacts.OfType(Of Customer)() _
              Order By c.LastName, c.FirstName _
              Select New KeyListItem With {.ID = c.ContactID, _
                .Name = c.LastName.Trim & "," & c.FirstName}
  CType(custs, ObjectQuery).MergeOption = MergeOption.OverwriteChanges
  Return _dal.ExecuteList(Of KeyListItem)(custs);
End Function

public List<KeyListItem> CustomerNamesList()
{
  var custs =  from cust in _commonContext.Contacts.OfType<Customer>()
               orderby cust.LastName, cust.FirstName
               select new KeyListItem {ID = cust.ContactID,
               Name = cust.LastName.Trim() + "," + cust.FirstName };
    ((ObjectQuery)custs).MergeOption = MergeOption.OverwriteChanges;
    return _dal.ExectueList<KeyListItem>(custs);
}

NOTE

The Visual Basic compiler would allow you to call ExecuteList without identifying the type because it can infer the type from the custs variable. Therefore, _dal.ExecuteList(custs) would also be a valid way to call the method in VB.

20.6.5. Using MergeOptions to Cache or Refresh a Pick List

It is possible for the customer list to be queried more than once during the lifetime of the business layer class. You will need to decide whether the user requires fresh data each time the list is queried. For this example, we'll rely on the rule that the user should always have an updated list of customers and contacts. Therefore, the database will be queried anytime the user requests the customer list. Remember that by default, if the database returns data that already exists in the ObjectContext, those entities in the context will not be refreshed. Only new data will be added to the context. However, because we are using the rule that requires fresh data, the method sets the query's MergeOption to MergeOption.OverwriteChanges, overwriting any modified entities with the data from the store. Because you can use a MergeOption only with an ObjectQuery, you'll have to cast the LINQ to Entities query to an ObjectQuery to set the MergeOption.

An additional method, AllContactsList, returns a list of combined customers and noncustomers (both deriving from Contact) so that when someone from BreakAway's mailing list signs up for her first trip, the user pulls the list that includes noncustomers and converts that person into a customer.

20.6.6. Building a Frequently Used Entity Graph for the UI

The DataBridge's GetCustomerwithRelatedData method will return a Customer graph based on an ContactID that is passed in as a parameter. This method creates a query using Include to eager-load all of the related customer data that the form needs. If the selected item turns out to be a noncustomer, the code calls the ConvertContacttoCustomer function that you built in Chapter 13 to turn that contact into a customer in the database. In that case, you'll need to reexecute the query that loads the customer graph. You do this by calling the entire method recursively.

20.6.6.1. Precompiling a frequently used query

The query for eager-loading the customer graph uses a number of Includes. It will require a bit of effort for the Entity Framework to compile the query:

From c In _commonContext.Contacts.OfType(Of Customer)
                        .Include("Addresses")
                        .Include("Reservations.Trip.Destination")
                        .Include("Reservations.UnpaidReservation")
Where c.ContactID = custID

More importantly, the query will be used repeatedly as the user moves from one customer to another in the UI. That makes the query a perfect candidate for precompilation, which you learned about in Chapter 16.

Example 20-8 shows a method that precompiles this query.

Example 20-8. Creating a precompiled LINQ query

Private Sub PreCompileQueries()
  _compiledCustGraphQuery = CompiledQuery.Compile _
   (Function(ctx As BAEntities, custID As Integer) _
    From c In _commonContext.Contacts.OfType(Of Customer) _
                           .Include("Addresses") _
                           .Include("Reservations.Trip.Destination") _
                           .Include("Reservations.UnpaidReservation") _
       Where c.ContactID = custID)
End Sub

private void PreCompileQueries()
{
  _compiledCustGraphQuery = CompiledQuery.Compile
   ((BAEntities ctx, int custID) =>
      from c in _commonContext.Contacts.OfType<Customer>()
                              .Include("Addresses")
                              .Include("Reservations.Trip.Location")
                              .Include("Reservations.UnpaidReservation")
       where c.ContactID == custID
       select c);
}

To share the compiled query with other methods, you need to declare it as a class-level variable. But when you do that, you need to specify the type. In the PreCompiledQueries method, you were able to take advantage of implicitly typed variables and did not need to specify the type. But when declaring the variable, you'll have to spell out the System.Func type in the declaration (see Example 20-9).

Example 20-9. Declaring the variable that will be used for a compiled query

Private _compiledCustGraphQuery As _
 System.Func(Of BreakAwayEntities, Integer, IQueryable(Of Customer))

private Func<BAEntities, int, IQueryable<Customer>>
 _compiledCustGraphQuery;

Next, you'll need to run the PreCompiledQueries method. Because the query will always be required by the DataBridge class, you can call the method in the class constructor where the context is instantiated. The CompiledQuery does not need the ObjectContext to perform the compilation. The context won't be used until it is time to invoke the query, which happens when you build the Customer graph.

Example 20-10 shows the GetCustomerwithRelatedData method, which builds the graph that will be sent back to the UI after the user has selected which customer or contact to work with. If they have chosen a Customer, we simply query for that Customer. However, if they have used the AllContactsList and selected a Contact, that Contact will be converted to a Customer on the fly. Since only the ID is being passed in, you can't determine the type until after performing the query, which is why the method has additional code based on whether the query returned a customer or returned nothing. This is explained in more detail after the code listing.

Example 20-10. Getting a customer from the database, and converting a noncustomer to a customer on the fly if necessary

Public Function GetCustomerwithRelatedData(ByVal id As Int32) As Customer

  'contact and eKey will be used by TryGetObjectByKey
  Dim contact As Object
  Dim eKey = New EntityKey(_containerName & ".Contacts", _
                          "ContactID", ContactID)

  'retrieve the customer graph using the compiled query
    Dim custQuery = _
     _compiledCustGraphQuery.Invoke(_commonContext, ContactID)

    CType(custQuery, ObjectQuery).MergeOption =  _
     MergeOption.OverwriteChanges

    Dim cust = _dal.ExecuteFirstorDefault(Of Customer)(custQuery)
  If Not cust Is Nothing Then
    'customer was found, return it
    Return cust
  ElseIf _commonContext.TryGetObjectByKey(ekey, contact) Then
   'if contact exists, then convert to customer
      ConvertContacttoCustomer(CType(contact, Contact))
    Return GetCustomerwithRelatedData(id)
  Else
    Return Nothing
  End If
End Function

public Customer GetCustomerwithRelatedData(Int32 ContactID)
{
  object con = null;
  var ekey = new EntityKey(_containerName + ".Contacts", _
                           "ContactID", ContactID);
  var custQuery =
     _compiledCustGraphQuery.Invoke(_commonContext, ContactID);

  ((ObjectQuery)custQuery).MergeOption =  MergeOption.OverwriteChanges;
  var cust = _dal.ExecuteFirstorDefault<Customer>(custQuery);
  if (cust != null)
  {
    return cust;
  }
  else if (_commonContext.TryGetObjectByKey(ekey, out con))
  {
   // if contact exists, then convert to customer
    var contactToConvert = (Contact)con;
    ConvertContacttoCustomer(ref contactToConvert);
    return GetCustomerwithRelatedData(ContactID);
  }
  else
    return null;
}

NOTE

Because TryGetObjectByKey specifically requires an object type as its parameter, after TryGetObjectByKey returns the con variable, you'll need to cast con to a Contact type before passing it to the conversion function. This is what's happening in the Else clause.

20.6.6.2. Some decisions that drove the design of the GetCustomerwithRelatedData method

I made a number of choices for this class method when weighing the cost of trips to the database against the potential complexity of writing queries. Although it's not uncommon to have to make these types of decisions, the Entity Framework adds new factors that you need to consider:

Query the database, or use GetObjectByKey to retrieve the customer graph?

There are a few reasons not to use GetObjectByKey here. The first is that if the entity is already in the context, the call won't return the most current data from the database. Additionally, even if the object is already in the context, it might not have the related data. By going right to the database, you are assured that you'll get the freshest and most complete set of data. If the customer is not found in the database, TryGetObjectByKey is used to get the contact record. If the contact is already in the cache for some reason, an extra trip won't be made to the database. It's really just a safety measure to be sure that no customer was returned for the ID because the ID belongs to a contact (which should be safe to assume, but who likes to make assumptions?).

In a highly concurrent system, keep in mind that if you have an entity in your cache and it is deleted from the data store, a typical query of the data store will not cause the entity to be removed from the cache. This will happen whether you use GetObjectByKey or a query. You will need to decide how long you want to keep objects in the ObjectContext when you have these types of concerns.

Query for a contact, or for a derived customer type?

Rather than having two separate methods for the UI developer to choose from, the business layer has a single method that can handle both customers and noncustomers.

The ID passed in will be a ContactID and could be for either derived type. If you query for a generic contact, a second query will always be necessary to fetch the related data for those contacts who are customers. However, if the initial query is specifically for a Customer type, you can eager-load the related data at the same time.

The downside of querying for the Customer type is that when the ContactID is for a noncustomer, nothing will be returned. This leaves you with yet another choice to make. Do you query again (database trip #2) for the noncustomer to be 100% sure it's still in the database before executing the ConvertContacttoCustomer function (database trip #3), or do you save the database trip and just call the function and deal with any exception that might occur?

Furthermore, how likely is it that the ContactID is of a customer versus a non-customer? In this case, the decision was to query for a Customer type and pay the cost of the extra trips to the database for the less common cases where the ContactID is for a noncustomer.

In the long run, is it worth all of this trouble so that the UI developer can have a single method to get a customer, even if he is passing in a noncustomer? You should decide that for your application architecture, but for the BreakAway application, the answer was yes.

20.6.6.3. Converting a noncustomer to a customer

When converting a noncustomer to a customer, you will encounter a problem because the noncustomer contact is already in the ObjectContext. Because you cannot convert entities to other entity types, the ConvertContacttoCustomer method called in Example 20-10 will completely remove the noncustomer from the context, and then requery the database for the newly converted customer.

20.6.7. Supplying Additional Lists for UI Drop-Downs

In addition to the customer list, the form also requires a number of common lists. In an application using DataSets, you might have a single query that returns all of these simple pick lists (Activities, Destinations, Customer Types, and Trips). However, Entity Framework queries cannot return multiple resultsets; therefore, four separate queries are required.

The GetReferenceList method in the CommandExecutor class will do most of the work here. However, the DataBridge needs to expose a method for the UI developer to access. We'll call that method GetReferenceList also. The method that you already built returns straight-up entities and that will work for most of the lists that this application requires, e.g., Activity, Location, and CustomerType. However, the Trip entity doesn't provide enough information by itself and its list cannot be satisfied by the GetReferenceList method. If we provided multiple methods in the DataBridge for getting reference lists, this could be confusing for the UI developer. Therefore, the DataBridge.GetReferenceList will not only encapsulate the GetReferenceList method from the other class, but also provide additional logic for returning the appropriate Trip list, which will be sorted using navigation properties.

Example 20-11 shows the GetReferenceList method, which calls into the CommandExecutor to retrieve the data for the UI.

Example 20-11. The DataBridge's GetReferenceList method calling methods from the CommandExecutor class

Public Function GetReferenceList(Of TEntity)(ByVal orderBy As String) _
 As List(Of TEntity)
    If GetType(TEntity) Is GetType(Trip) Then
      Dim tripQuery = From t In _commonContext.Trips.Include("Destination") _
                    Order By t.Destination.DestinationName, t.StartDate _
                    Select t
      Return _dal.ExecuteList(tripQuery).Cast(Of TEntity).ToList()
    Else
      Return _dal.GetReferenceList(Of TEntity)(_commonContext, orderBy)
    End If
  End Function

public List<TEntity> GetReferenceList<TEntity>(string orderBy)
{
  if (typeof(TEntity) == typeof(Trip))
  {
    var tripQuery =
        from t in _commonContext.Trips.Include("Destination")
        orderby t.Destination.DestinationName, t.StartDate
        select t;
    return _dal.ExecuteList<Trip>(tripQuery).Cast<TEntity>().ToList();
  }
  else
  {
    return _dal.GetReferenceList<TEntity>(_commonContext, orderBy);
  }
}

A Set of Extensions to Allow Multiple Resultsets per Query

You may recall a pointer in Chapter 13 to Colin Meek's EFExtensions that are available on the MSDN Code Gallery. The extensions enable the Entity Framework to call stored procedures that return multiple resultsets whether the results are entities or anonymous types. You might want to consider these as an alternative to what is being demonstrated in this section.

20.6.7.1. Choosing between complete entities and narrower types for lists

Although the DataBridge class uses the KeyListItem class, which is designed to provide lists for data-bound controls, these pick lists are not using them. Instead, the queries are returning complete entities for these drop-down lists. I decided to use the entities rather than creating lists from projected queries. This results in more data traveling from the database back through the layers to the form. So, why does the business layer use entire entities instead?

I weighed two development benefits against the potential performance cost. One benefit is the simplicity of the queries; but more important is an advantage that Windows Forms data binding provides.

You can use navigation properties in entities for data binding. Combined with the fact that the Windows Forms ComboBox control knows how to bind to objects, you have a very simple solution for populating the lists. When you bind the ComboBox to the navigation property using the ComboBox.SelectedItem property and then use a list of entities as the control's DataSource, the control does the rest of the work for you. All of the entities except for the Trip entity have only two properties anyway, so the performance impact of using them rather than a key/value type of class shouldn't be significant in this scenario.

However, it is something to keep in mind, and you may choose a different method of creating pick lists in your application layers.

20.6.8. Saving Changes

The new class should have a way for the UI developer to save the user's changes.

Here is a SaveChanges method that wraps the ObjectContext's custom SaveAllChanges so that the UI can call it easily:

Public Function SaveChanges() As Boolean
  Try
    _commonContext.SaveAllChanges()
    Return True
  Catch ex As Exception
    Return False
  End Try
End Function

public bool SaveChanges()
{
  try
  {
    _commonContext.SaveAllChanges();
    return true;
  }
  catch (Exception ex)
  {
    return false;
  }
}

20.6.9. Rolling Back User Changes

In Chapter 16, you learned that there is no transactional support for the ObjectContext, nor is there an automatic way to revert entity modifications back to their original values. What if the user wants to undo the edits he made and continue working on the client?

Although the Entity Framework's rich features offer a number of ways to roll back to original values, in the long run the simplest route is to refresh the graph from the database. Additionally, you may already have a mechanism in place for rolling back changes to your objects. Be sure to test it thoroughly with the entity objects since the relationships work differently than most other frameworks might expect.

You may be tempted to try to do something in memory rather than make an extra trip to the database. Let's look at some of the opportunities, along with their pros and cons.

20.6.9.1. Considering in-memory rollbacks

Rolling back changes in the Entity Framework poses a number of challenges, because the ObjectContext does not provide any methods to do this.

You need to consider a few things when rolling back data in a graph:

Scalar properties of the parent object (e.g., Customer)

You must reset fields such as Name and BirthDate.

Navigation properties that are EntityReferences (e.g., PrimaryActivity)

To ensure that a reference, such as which activity the customer prefers, has not been changed, you'll need to reset the EntityKey values of each EntityReference.

Navigation properties that are EntityCollections

You need to reset these properties to the original EntityCollection. The scalar properties of items in the collection could have been modified, and items could have been added or removed.

Any additional EntityReferences or EntityCollections deeper in the graph that might have been modified

All of the aforementioned rollback considerations would need to be applied for additional items in a graph as well.

Changing the scalar properties is not a major challenge, and a few patterns are available for you to do this. One involves storing the scalar values in local variables, resetting them to roll back, and applying them. Another involves using the ObjectStateEntry's features to read the OriginalValues and push them into the CurrentValues. The first option is probably the easiest to code, but the second option, shown in Example 20-12, could be coded as a generic routine that you can reuse.

Example 20-12. Resetting an entity's current (scalar) values to its original values

Public Sub RevertEntityScalars(ByRef entity As EntityObject)
  Dim custEntry = _commonContext.ObjectStateManager. _
   GetObjectStateEntry(entity.EntityKey)
  Dim dri = custEntry.CurrentValues.DataRecordInfo
  For i = 0 To custEntry.CurrentValues.FieldCount - 1
    'changing an EntityKey property will throw an error, check first
    Dim propName = dri.FieldMetadata(i).FieldType.Name
    If Not (From keys In custEntry.EntityKey.EntityKeyValues _
            Where keys.Key = propName).Any Then
      custEntry.CurrentValues.SetValue(i, custEntry.OriginalValues(i))
    End If
  Next
End Sub

public void RevertEntityScalars(ref EntityObject entity)
{
  var custEntry = _commonContext.ObjectStateManager
                  . GetObjectStateEntry(entity.EntityKey);
  var dri = custEntry.CurrentValues.DataRecordInfo;
  for (var i = 0; i < custEntry.CurrentValues.FieldCount; i++)
  {
    //changing an EntityKey property will throw an error, check first
    var propName = dri.FieldMetadata[i].FieldType.Name;
    if (!(from keys in custEntry.EntityKey.EntityKeyValues
          where keys.Key == propName
          select keys).Any()) ;
    {
      custEntry.CurrentValues.SetValue(i, custEntry.OriginalValues[i]);
    }
  }
}

Navigation properties pose a bigger challenge, however. To read that information out of the ObjectStateManager you must search through RelationshipEntries to find Related EntityKeys. But at this point, you will hit a wall. Is the relationship Added or Deleted? If it's Added, how do you remove it completely from the context? If it's Deleted, how do you change it to Unchanged? Even if you could find a way around this, the next steps would be equally onerous: finding the related entities, removing any Added ones, changing the state of any Deleted ones, and reverting the scalar properties. This is probably not a path you want to go down unless you are writing a framework that will be reused often.

Other possibilities are available, but each poses such daunting challenges that it would take a good amount of crafty code to solve them.

NOTE

I have worked toward achieving a number of these patterns with the hope of sharing them, but I found them to be so elaborate that I don't believe they are good guidance for general use of the Entity Framework, nor would they fit into the scope of this book. I'll lay out the concepts here so that you know what I've attempted already, but I won't show the pieces of code, as they won't add up to a complete solution.

One of these schemes is to store the original EntityCollections and the EntityReference EntityKeys into a dictionary and then refresh the values from there. But you will run into another wall going down this path. You'll need to store a copy of these items; otherwise, any changes to the current graph will be made to items in the dictionary, and you won't have original values.

How do you make a copy of the graph? Not easily. You could use a combination of reflection and MetadataWorkspace to create clones of the entities. That would require a lot of work, but it has been done (see Matthieu Mezil's Entity Cloner blog post at http://msmvps.com/blogs/matthieu/archive/2008/05/31/entity-cloner.aspx/). You could also use binary serialization, streaming the results of the serialized graph into an object that you store in a variable (see the MSDN topic "How to: Serialize and Deserialize Objects [Entity Framework]," at http://msdn.microsoft.com/en-us/library/bb738528.aspx/). However, you will need to manually detach each item from the context before you serialize the graph, and you may recall that ObjectContext.Detach can detach only a single entity at a time, not a graph. You need to detach these items because there is a lingering reference to the ObjectContext when you serialize, which will prevent you from reattaching the entities to any other context. This results in a major effort and a lot of code if you were to do this in memory, although in some cases it will certainly be worth the effort.

20.6.9.2. Keeping it simple: Rolling back from the database if you can

The cleanest path for a rollback is to refresh the data from the database by calling the GetCustomerwithRelatedData method from Example 20-10, which uses the OverwriteChanges merge option when it executes the query.

Why not use ObjectContext.Refresh with StoreWins? Remember that this will update only the Customer. Even though it seems like overkill to make an extra trip to the database, this truly is the simplest way to achieve the rollback.

To make it simpler for the UI developer, the bridge class has a RollbackCustomer method (see Example 20-13). With this method, the UI developer doesn't have to know how you decided to solve the rollback problem. Plus, you can change the RollbackCustomer method at a later time without impacting the UI.

Example 20-13. The RollbackCustomer method in the DataBridge class

Public Function RollbackCustomer(ByRef cust As Customer) As Customer
  Return GetCustomerwithRelatedData(cust.ContactID)
End Function

public Customer RollbackCustomer(ref Customer cust)
{
  return GetCustomerwithRelatedData(cust.ContactID);
}


20.7. Using the DataBridge Class for Data Binding in a Master/Detail Form

Now that you've got the critical elements of the DataBridge class laid out, let's take a look at using it from a UI along with some of the specific concerns that entities bring to the UI in a layered application.

Rather than walk through building up the entire form, this section will focus on the critical UI elements and code that the form will need in order to work with the DataBridge toward the goal of removing business and data access logic from the UI.

The first thing to tackle is data binding to the data returned by the DataBridge.

20.7.1. Using BindingSourceControls with the DataBridge

Data binding is not limited to scenarios where the UI controls the data access. It is very handy even when you have separated your business and data access logic from the user interface. Without it you would have to manually wire up every text box and every drop-down control and keep track of the state of each control, updating the entities as needed. Although many developers may turn up their noses at the thought of using any type of automation in their applications, it makes a lot of sense to take advantage of these tools.

This form uses BindingSourceControls to handle that tedium for the Customer, Addresses, and Reservations collections (see Figure 20-4).

Figure 20-4. BindingSource controls wired up to grids for displaying the Customer's EntityCollections

20.7.2. Instantiating the DataBridge Class in the Form

The DataBridge class is elemental to the form. Rather than merely declaring it in the form's declarations, you can instantiate it at that time. There's no need to postpone instantiating the class, since it is used right away when the form itself is loading.

Public Class Form1
  Dim bridge As New BAGA.DataBridge

public partial class Form1
{
  private BAGA.DataBridge bridge = new BAGA.DataBridge();

20.7.3. Populating the Form with an Entity and Its Related Data

NOTE

Use the form's screenshot in Figure 20-3 as a reference for the elements of the form.

One of the form's first tasks is to populate a ComboBox control that will allow the user to select which customer to work with. Call the DataBridge.AllContactsList method and then bind the results, a List of KeyListItem types that have an ID property and a Name property, to the ComboBox control to do this.

NOTE

Tasks that are common to building Windows Forms will not be spelled out. If you want to see every detail of the form, you can download the solution from the book's website.

Each time a user selects a customer from the customer pick list, the customer graph is retrieved from the bridge class and then passed to a method, FillForm, which populates the form. Here the method binds the customer entity and its related children, Addresses and Reservations, to the three BindingSource controls. In turn, the form fields and grids are wired up to those BindingSources (see Example 20-14).

Example 20-14. Retrieving the customer graph and binding the customer to the form's data sources

Dim cust As Customer = bridge.GetCustomerwithRelatedData( _
 CInt(cboCustList.SelectedValue))
FillForm(cust)

Private Sub FillForm(ByVal cust As Customer)
  _fillCustomer = True 'prevents the combos' selected events from executing
  CustomerBindingSource.DataSource = cust
  ReservationsBindingSource.DataSource = cust.Reservations
  AddressesBindingSource.DataSource = cust.Addresses
  NeedsSaveSymbol.Visible = False
  _fillCustomer = False
End Sub

Customer cust = bridge.GetCustomerwithRelatedData( _
 Convert.ToInt32(cboCustList.SelectedValue));
FillForm(cust);

private void FillForm(Customer cust)
{
  _fillCustomer = true; // prevents the combos' selected events from executing
  CustomerBindingSource.DataSource = cust;
  ReservationsBindingSource.DataSource = cust.Reservations;
  AddressesBindingSource.DataSource = cust.Addresses;
  NeedsSaveSymbol.Visible = false;
  _fillCustomer = false;
}

The form uses a local Boolean, _fillCustomer, as a flag to help prevent the Selected events of the various combos from firing as they are being populated. You can see how this property is used in the complete sample application.

As you saw in previous chapters, Entity Framework entities are designed to work with data-bound controls in .NET. You can even data-bind the navigation properties, which is why you can data-bind the EntityReference properties such as PrimaryActivity and the Reservations and Addresses EntityCollections to another grid (using the BindingDataSource controls). With this capability, it is possible to build master/detail forms with entity graphs.

The controls that bind to scalar properties do so through the controls' DataBinding.Text property. As described earlier, the ComboBox controls that bind to navigation properties that are EntityReferences (e.g., PrimaryActivity) do so through the controls' SelectedItem property. Finally, the grids that are bound to the EntityCollection navigation properties do so using the DataSource property.

NOTE

The ComboBox controls are odd in that when the rollback is performed, even though the correct data is in the Customer the ComboBoxes don't automatically refresh. A RefreshCombo method explicitly sets the SelectedItem for each ComboBox control. For example:

CustomerTypeComboBox.SelectedItem = cust.CustomerType

Once the binding is in place, editing data in the graph, even within the collections, happens automatically. But there is one thing that is important to watch out for: deleting entities from the entity collections (e.g., deleting a reservation). We'll take a look at this issue after dealing with the pick lists.

20.7.4. Consuming the Pick Lists in the Form

In previous samples in this book, you saw how to use pick lists to allow the user to edit the EntityReference navigation properties of an entity. In the case of the Customer entity, this would require lists of activities and destinations for the customer's preferences and a list of CustomerTypes.

The form can easily call the GetReferenceList method from the DataBridge by merely passing in the required type. Because we'll need to consume the Activity and Destination lists twice, you'll see in Example 20-15 that I'm doing something peculiar with the second combo box for each of these. The explanation for this follows the code listing. Because the lists contain complete entities and the control uses the SelectedItem property, it is not necessary to define a ValueMember.

Example 20-15. Filling the ComboBoxes in the UI

Private Sub FillDropDowns()
  'Activity & Destination lists created in advance for reuse
  Dim ActList = bridge.GetReferenceList(Of Activity)("ActivityName")
  Dim DestList = bridge.GetReferenceList(Of Destination)("DestinationName")

  CustomerTypeComboBox.Fill(bridge.GetReferenceList(Of CustomerType)("CustomerTypeName"), _
   "CustomerTypeName")

  PrimaryActivityComboBox.Fill(ActList, "ActivityName")
  SecondaryActivityComboBox.Fill((From a In ActList Select a).ToList, "ActivityName")

  PrimaryDestinationComboBox.Fill(DestList, "DestinationName")
  SecondaryDestinationComboBox.Fill((From d In DestList Select d).ToList, _
   "DestinationName")
End Sub

private void FillDropDowns()
{
  //Activity & Destination lists created in advance for reuse
  var ActList = bridge.GetReferenceList<Activity>("ActivityName");
  var DestList = bridge.GetReferenceList<Destination>("DestinationName");

  CustomerTypeComboBox.Fill(bridgeS.GetReferenceList<CustomerType>("CustomerTypeName"),
                            "CustomerTypeName");

  PrimaryActivityComboBox.Fill(ActList, "ActivityName");

  SecondaryActivityComboBox.Fill((from a in ActList select a).ToList(),
                                 "ActivityName");

  PrimaryDestinationComboBox.Fill(DestList, "DestinationName");
  SecondaryDestinationComboBox.Fill((from d in DestList select d).ToList(),
                                     "DestinationName");
}

In this form, the ActivityList is bound to two separate controls: one for primary activities and one for secondary activities. Similarly, the destination list is also being used for multiple controls. The list of entities suffers the same fate as any other object type used in a list when bound to multiple objects: when one control changes the pointer on the list, the pointer change shows up in the other control. That is why a new list is created using a query for the following:

SecondaryActivityComboBox.DataSource

20.7.5. Deleting from Grids When EntityCollections and Referential Constraints Are Involved

Reservations is an EntityCollection, so when you bind to Customer.Reservations, you are literally binding to the EntityCollection.

When you delete a row from a grid, you are not deleting a Reservation, but rather are triggering EntityCollection.Remove, which merely removes the Reservation from the collection. The relationship is deleted, but the Reservation itself is Unchanged and will not be deleted when you call SaveChanges. However, when you call SaveChanges, you will get the following exception message, which is thrown as the Entity Framework is trying to work out the store query:

A relationship is being added or deleted from an AssociationSet
'FK_Reservations_Customers'. With cardinality constraints,
a corresponding 'Reservations' must also be added or deleted.

This is caused not by something in the conceptual model, but by a constraint defined in the store model, which reflects the primary key/foreign key relationship between Customer and Reservations in the database. A reservation must have a CustomerID foreign key. When you delete the relationship, the Entity Framework will try to update the Reservation by removing the CustomerID, which is not allowed based on the model's definition of this relationship (see Example 20-16).

Example 20-16. A dependent relationship between Reservation and Customer as described by the model's store layer (SSDL)

<Association Name="FK_Reservations_Customers">
  <End Role="Customers" Type="BreakAwayModel.Store.Customers"
       Multiplicity="1" />
  <End Role="Reservations" Type="BreakAwayModel.Store.Reservations"
        Multiplicity="*" />
  <ReferentialConstraint>
    <Principal Role="Customers">
      <PropertyRef Name="ContactID" />
    </Principal>
    <Dependent Role="Reservations">
      <PropertyRef Name="ContactID" />
    </Dependent>
  </ReferentialConstraint>
</Association>

The fact that the grid's Delete action only removes the child entity is a great thing if, for instance, your master and detail are for a Class with Students. It's probably pretty clear that you are just removing the student from the class and not kicking the student out of the school and obliterating every record of her existence. But the Entity Framework can't have different behaviors for different scenarios. Instead, the single predictable behavior is that the relationship is deleted, not the entity.

NOTE

LINQ to SQL has a similar issue because you are also deleting the relationship. As Beth Massi describes in her extended tutorial blog post "One-To-Many (Master-Detail) Forms with LINQ to SQL," LINQ to SQL deletes the relationship by setting the foreign key value of the child entity to null. So, when you delete a row, the entity isn't deleted and only the foreign key is removed. LINQ to SQL has an attribute that you can apply that will delete the object when a particular value is null. Check Beth's post for the details, at http://blogs.msdn.com/bethmassi/.

There is a case when this problem won't occur. That's when the child entity has a composite key. In that case, the constraint will be built into the Conceptual Schema Definition Layer (CSDL) and the Entity Framework will be able to handle it more easily. Following is an explanation of how the delete is impacted when the child has a composite key. Then you will see a solution for handling the problem that exists when attempting to delete the Reservation—where the child entity does not have a composite key, and therefore no constraint exists in the CSDL.

20.7.5.1. Deleting children from a grid is easier when the child has a composite key

Like the Customer/Reservations relationship, in the AdventureWorksLT database a SalesOrderDetail must be attached to a SalesOrderHeader. However, the SalesOrderDetail table has a composite key (OrderDetailID plus OrderID) that makes it possible to create a referential constraint in the CSDL; see Example 20-17.

Example 20-17. An association with a constraint in the CSDL

<Association Name="FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID">
  <End Role="SalesOrderHeader"
       Type="AdventureWorksLTModel.SalesOrderHeader" Multiplicity="1">
    <OnDelete Action="Cascade" />
  </End>
  <End Role="SalesOrderDetail"
       Type="AdventureWorksLTModel.SalesOrderDetail" Multiplicity="*" />
  <ReferentialConstraint>
    <Principal Role="SalesOrderHeader">
      <PropertyRef Name="SalesOrderID" />
    </Principal>
    <Dependent Role="SalesOrderDetail">
      <PropertyRef Name="SalesOrderID" />
    </Dependent>
  </ReferentialConstraint>
</Association>

When the user deletes a SalesOrderDetail in a detail grid, this causes the relationship between that entity and its SalesOrderHeader to be deleted. In turn, the constraint defined in the model tells the Entity Framework that because the relationship is gone, it must delete the entity (SalesOrderDetail) when SaveChanges is called. In this scenario, everything works as expected.

In more common scenarios (or less well-designed databases), you won't have the foreign key as an actual property in the entity once it's been used for a navigation property, so you can't create the constraint. This works only when you have a composite key.

20.7.5.2. Deleting child entities in a grid when no referential constraint exists in the CSDL

How can you solve the problem of deleting reservations and addresses from their detail grids? Let's look at some options that don't involve introducing business logic into the UI.

One option is to modify the database, introducing the composite key and then updating the association details in the model to reflect the constraint. If you are still designing your model and do not have a lot of code that already depends on the model, this might not be a bad option. But introducing the composite key as well as a new property in the entity could wreak havoc in existing code.

Another option is to create logic in the partial classes or even an extension method that identifies the deleted relationship, read the Store Schema Definition Layer (SSDL) using the MetadataWorkspace to identify which end of the relationship also needs to be deleted, and therefore place the logic into the model's business classes. This can get pretty complicated, but it would keep the logic out of the user interface.

But there's a much nicer option. You may recall from Chapter 10 that there is an AssociationChanged event but no AssociationChanging event. The AssociationChanged event is wired up to navigation properties in an entity. You can add logic to the AssociationChanged event for the Reservations and Addresses properties of the Customer entity and raise an event that will let the new class know to delete the object from the context. Then, when SaveChanges is called, the child will be deleted from the database.

This will take a few steps to wire up. Example 20-18 does this for the deleted reservations.

First, add an event to the Customer class that you can use for any child entity type being removed. The event will need to pass the entity that needs to be deleted, so include that in the signature. Use the IEntityWithRelationships interface as the type so that all of the casting will work as the entity is passed around. C# does this with an event and a delegate, as you'll see in the example.

Example 20-18. Creating the public event that will be raised

Partial Public Class Customer
'add an Public Event to the class
 Public Event ChildRelationshipRemoved( _
   ByVal entity As IEntityWithRelationships)

public partial class Customer
{
public delegate void ChildRelationshipRemovedEventHandler(
 IEntityWithRelationships entity);
public event ChildRelationshipRemovedEventHandler
 ChildRelationshipRemoved;

Next, you need to add an EventHandler for the custom method that will be called, Reservations_AssociationChanged, and that will be fired when the AssociationChanged event is raised. This goes in the Customer's constructor (see Example 20-19).

Example 20-19. Creating an EventHandler for the AssociationChanged event

Public Sub New()
AddHandler Me.Reservations.AssociationChanged, _
  AddressOf Reservations_AssociationChanged
End Sub

public Customer()
{
  Reservations.AssociationChanged +=
   CollectionChangeEventHandler(Reservations_AssociationChanged);
}

Still in the Customer's partial class, you now need to create the Reservations_AssociationChanged method. This will check to see whether the action that caused us to get here was from a relationship being removed. If so, you'll check for the condition that is a problem, which is that the relationship was removed, but the entity was not deleted. Example 20-20 shows this code.

NOTE

You are building in this business rule for any application that uses this entity. However, you are not automatically deleting the entity, just raising an event. Even if you wanted to automatically delete the entity, you can't do so from the Customer class anyway. So, you can raise the event to the class that caused the relationship to be removed, and if it wants to handle it, it can. Otherwise, it can just ignore the event completely.

Example 20-20. The event handler for AssociationChanged

Private Sub Reservations_AssociationChanged( _
 ByVal sender As Object, ByVal e As CollectionChangeEventArgs)
  Dim act As System.ComponentModel.CollectionChangeAction = e.Action
  If act = CollectionChangeAction.Remove Then
    Dim impactedReservation = CType(e.Element, Reservation)
    If Not impactedReservation.EntityState = EntityState.Deleted Then
        RaiseEvent ChildRelationshipRemoved( _
         CType(impactedReservation, IEntityWithRelationships))
      End If
    End If
  End Sub

private void Reservations_AssociationChanged(
 object sender, CollectionChangeEventArgs e)
{
  CollectionChangeAction act = e.Action;
  if (act == CollectionChangeAction.Remove)
  {
    var impactedReservation = (Reservation)e.Element;
    if (!(impactedReservation.EntityState == EntityState.Deleted))
     ChildRelationshipRemoved(
      (IEntityWithRelationships)impactedReservation);
  }
}

That's it for the Customer class. Now you have to capture this in the DataBridge class.

The first step is to wire up this event to the Customer when the Customer is created in the DataBridge class. In the GetCustomerwithRelatedData method, add the handler in Example 20-21, just before the part of the code that returns the customer to the UI. The new line of code is in bold. This handler says that when the ChildRelationshipRemoved event is raised, call the DeleteChildEntityMethod.

Example 20-21. Adding the EventHandler into the GetCustomerwithRelatedData method

If Not cust Is Nothing Then
    AddHandler cust.ChildRelationshipRemoved, _
    AddressOf DeleteChildEntity
  Return cust
End If

if (cust != null)
{
    cust.ChildRelationshipRemoved +=  DeleteChildEntity;
  return cust;
}

The last step is to add the new DeleteChildEntity method in the new layer to ensure that a Reservation that is removed from a Customer is deleted in the database (see Example 20-22). Notice that the method has the same signature as the event handler raised by the Customer.

Example 20-22. Deleting a child

Public Sub DeleteChildEntity(ByVal entity As IEntityWithRelationships)
  _commonContext.DeleteObject(entity)
End Sub

public void DeleteChildEntity(IEntityWithRelationships entity)
{
   _commonContext.DeleteObject(entity);
}

Although this problem was highlighted by the Windows Forms GridView control, you will have the same issue when deleting EntityCollection items from any other data-binding control, whether it's Windows Forms, WPF, or even the ASP.NET platform. So, by putting the solution within the business layer (including the entity class), you will be able to reuse it in other applications.


20.8. Allowing Users to Roll Back Their Edits

In the UI, you can provide a rollback button on the form that calls the RollbackCustomer method and repopulates the form. In Figure 20-5 the Discard Changes button is the user's mechanism for rolling back edits.

Figure 20-5. Adding a rollback feature (the Discharge Changes button) to the UI

In a Windows form where a BindingSource control is used, such as the CustomerBindingSource in the example form, you'll need to dig into the BindingSource to get a reference to the entity to roll back. Example 20-23 shows how the CustomerBindingSource.Current property returns a customer entity (though the returned item needs to be cast to Customer). Then that customer can be passed as a parameter to the DataBridge.RollbackCustomer method.

Example 20-23. Rolling back changes from the UI

Private Sub Rollback_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles Rollback.Click
    Dim custtoRollBack = CType(CustomerBindingSource.Current, Customer)
    Dim newcust = bridge.RollbackCustomer(custtoRollBack)
  'this seems to be necessitated by a drawback with WinForms objectbinding
    RefreshCombos()
End Sub

private void Rollback_Click(object sender, System.EventArgs e)
{
  var custtoRollBack = (Customer)CustomerBindingSource.Current;
  var newcust = bridge.RollbackCustomer(ref custtoRollBack);
 //this seems to be necessitated by a drawback with WinForms objectbinding
  RefreshCombos();
}


20.9. Helping the User Who Forgets to Save Changes

A typical line-of-business application helps users who might attempt to close a form or move to another record without explicitly saving their changes. Rather than automatically saving the edits, the application will ask whether the user wants to save his edits before leaving the form, or whether he wants to cancel them, leaving the database untouched.

The IsDirty property facilitates validating current data before the user is allowed to move to another set of data or leave the data entry form. In the sample form, this would occur when the user selects a new customer from the drop-down list, attempts to close the form, or shuts down the application completely (see Example 20-24).

You can call the ValidateCustomerEdits method from any of these events.

Example 20-24. Using IsDirty to validate edits

Private Function ValidateCustomerEdits() As Boolean
  If bridge.IsDirty Then
  Dim response = MessageBox.Show _
     ("Save Changes to current Customer" & _
      " (including addresses and reservations)?", _
     "BreakAway", MessageBoxButtons.YesNoCancel)
  Select Case response
    Case Windows.Forms.DialogResult.Yes
     'save and move on
      bridge.SaveChanges()
      Return True 'ok to continue
    Case Windows.Forms.DialogResult.No
     'ignore the changes, if user returns to this customer,
     'GetCustomer will update the cache
      Return True 'ok to continue
    Case Windows.Forms.DialogResult.Cancel
     'do not leave customer
      Return False
    End Select
  Else
    Return True
  End If
End Function

private bool ValidateCustomerEdits()
{
  if (bridge.IsDirty)
  {
    var response = MessageBox.Show
      ("Save Changes to current Customer " +
      "(including addresses and reservations)?",
      "BreakAway", MessageBoxButtons.YesNoCancel);
    switch (response)
    {
      case System.Windows.Forms.DialogResult.Yes:
       //save and move on
        bridge.SaveChanges();
        return true;
        break;
      case System.Windows.Forms.DialogResult.No:
       //ignore the changes, if user returns to this customer,
       //GetCustomer will update the cache
        return true;
        break;
      case System.Windows.Forms.DialogResult.Cancel:
        //do not leave customer
        return false;
    }
  }
  else
    return true;
}

If the user chooses to save changes, you only need to make a call to the SaveChanges method in the layer class, which calls the ObjectContext.SaveChanges or the new SaveAllChanges method from Chapter 18, which checks for exceptions.

You also can use the IsDirty property to provide visual clues regarding the state of the customer. In Figure 20-5, notice the icon to the right of the Save button. This is driven by the value of bridge.IsDirty and acts as a reminder to the user that he has changes to save. The icon's Visible property is set to True or False in the Validate events of the BindingSource and some of the controls. You can see it being set in the FillForm method in Example 20-14.


20.10. Summary

In this chapter, you got a chance to build code that can be reused in other EF applications. You also saw how to separate your interaction with the ObjectContext out of the UI, along with some of the challenges you will encounter in doing so. Although you saw solutions for these challenges, you also got a chance to step through some of the thought process that went into choosing the right solution for each given scenario. As such, you should be able to walk away with some ideas for going to the next level if you are willing to make the investment, such as some suggestions for persisting entities in memory for rolling back changes to a particular point in time.

Because you can take advantage of a long-running ObjectContext in a client application, you can focus on separating your logic without having to worry about the issues that occur when detaching and reattaching entities from a context that you have encountered (and are about to revisit in the next few chapters) with web applications and services. Therefore, you can perform queries and leave the entities attached to the original context, allowing users to modify the data and save using the same context. Although the long-running context makes change tracking easy, other properties of entities and the Entity Framework make it necessary to work out particular solutions to keep the business logic separate from the user interface when working with entities.


Chapter 21. Using the Entity Framework in n-Tier ASP.NET Applications

The preceding chapter focused on using the Entity Framework in an n-tier client-side application, as well as some of the UI challenges that you should be aware of. One of the key factors is that you were able to leverage a long-lived ObjectContext whose entities remain attached as they move into the UI and back. This allowed you to take advantage of Object Services change tracking.

Web applications are a completely different ballgame posing a new set of challenges for tracking changes in entities. Here, the client is a web page and the short-lived life cycle of a web page prevents entities from remaining attached to an ObjectContext. Additionally, because of the nature of server-based applications, any objects in memory are maintained on the server. For the developer, this means the server is responsible for maintaining objects for every user that is accessing the website at a given time, which could add up very quickly.

The biggest challenge of removing the UI's direct dependency on the Entity Framework in web applications is to figure out how to provide the state information that SaveChanges needs for updates in a way that balances the load between the client machine of the person accessing your site and the server. At the same time, you want to try to avoid unnecessary round trips to the database.

The EntityDataSource control goes a long way toward solving these problems; however, the EntityDataSource lives completely in the UI and precludes the separation of logic into layers.

In this chapter, we'll first look at the challenges and options available for handling entities in web applications, and then build a new set of classes whose job is to work with the ObjectContext and that can be accessed by the ASP.NET ObjectDataSource control. Finally, you will build a web page that relies on ObjectDataSource controls that are wired up to the new classes. In building this page, you will get an opportunity to address, learn about, and solve a number of issues that are specific to the fact that you are working with entities.


21.1. Understanding How an ObjectContext Fits into the Web Page Life Cycle

It will be helpful first to understand the life cycle of an ASP.NET web page in order to grasp why it creates a problem for the ObjectContext.

The Page object itself exists only for as long as it takes ASP.NET to render the HTML. Once the HTML has been created, the Page object is disposed along with any objects that it contained (see Figure 21-1).

Figure 21-1. ASP.NET page life cycle

Even if you did all of your work with the ObjectContext in the page's code-behind, that context, which the page instantiates, will be destroyed when the page is disposed. Any entities that were created in the page will be destroyed as well. For this reason, web pages are stateless by nature (see Figure 21-2).

Figure 21-2. ASP.NET page using an ObjectContext

Introducing a new layer into the mix continues to pose the problem of the disappearing context. Consider a class similar to the DataBridge class from Chapter 20 that instantiates an ObjectContext and retrieves or updates data on behalf of the UI using it.

Now the web page's .NET code can instantiate a new DataBridge class, which creates an ObjectContext and some entities. When the page completes its life cycle, it is disposed along with any objects that it owns, including the DataBridge object. When that object is disposed, so is the ObjectContext that it owns, and finally, the entities are also destroyed (see Figure 21-3).

Figure 21-3. Moving the ObjectContext out of the ASP.NET page and into a DataBridge class in the middle tier

All of this means that you can't merely instantiate an ObjectContext, query for entities, modify those entities, and call SaveChanges. You will never be coming back to the same ObjectContext, and therefore change tracking won't be performed.

21.1.1. Using EntityObjects in Read-Only Web Pages

The big challenges for working with objects in ASP.NET occur mostly in scenarios where you need the user to update, especially with a graph of entities, something such as a master/detail page.

If you are building pages that merely need to display data, things are much simpler. You can return data of any shape to the page and use the page's code-behind to populate controls or bind data.

The most important thing to be aware of is that you should not return queries from the DataBridge class, but rather the result of those queries, whether that is a single object, a collection, a List and IEnumerable, or something else. Just do not return an ObjectQuery or a LINQ to Entities query. This is not because of the page life cycle, but because it's very easy to create a business object whose ObjectContext will be disposed before the query has actually executed. See the sidebar for a more in-depth explanation of this.

Return Results, Not Queries, from the DataBridge Class

Although you can get away with binding a query when working directly in the code-behind of an ASP.NET page, remember that the query's job is to be executed and return results. Query execution requires an ObjectContext. If you return the query itself from a business class, it will be detached from the context as soon as the business object is disposed (which in turn disposes the context).

Here's an example of a method within a business class that returns an IQueryable of customer objects:

public IQueryable<Customer> GetCustomer(int custID)
{
  return _ctx.Customer.Where(c => c.CustomerID == custID);
}

In a web app, you might set the data source of a control to the results. Because you are returning an IEnumerable, this will be allowed even though it contains only a single item:

ListView1.DataSource=dal.GetCustomer(570);

The query will not be executed until the page begins to render the ListView control. By then it's quite possible that the business object will be long gone and the execution will fail.

So, in the business class, be sure to return results, not queries. That way, you don't have to worry about how the methods are used from the UI.

An additional benefit is that by executing the query and forcing the results to be iterated through using something such as First, ToList, or Execute, when the iteration is complete the EntityConnection and its database connection are disposed. Therefore, you won't have to think twice about the database connection, which is an unmanaged resource.

You could have a method that returns a full graph of information for a customer along with her reservation information, payments for those reservations, and details regarding the trips for which she made the reservations. You can grab all of this information in a single query, as shown in Example 21-1.

Example 21-1. Creating a deep graph to return to a web page for display

Public Function GetCustomer(ByVal ContactID As Int32) As Customer
 Dim custs = _commonContext.Contacts.OfType(Of Customer) _
                           .Include("Addresses") _
                           .Include("Reservations.Trip.Location") _
                           .Include("PrimaryActivity") _
                           .Include("SecondaryActivity") _
                           .Include("PrimaryDestination") _
                           .Include("SecondaryDestination") _
                           .Where("it.Contactid=" & ContactID)

   Dim cust as Customer = custs.FirstOrDefault()
  'important return the customer object, not the query
   Return cust
End Function

public Customer GetCustomer(Int32 ContactID)
{
  var custs = _commonContext.Contacts.OfType<Customer>()
                            .Include("Addresses")
                            .Include("Reservations.Trip.Destination")
                            .Include("PrimaryActivity")
                            .Include("SecondaryActivity")
                            .Include("PrimaryDestination")
                            .Include("SecondaryDestination")
                            .Where("it.Contactid=" + ContactID);
   Customer cust = custs.FirstOrDefault();
 //important return the customer object, not the custs query;
   return cust;
}

NOTE

Remember that the downside to a query with so many Includes is that you might faint if you look at the T-SQL. The Entity SQL generated by this ObjectQuery will at least be cached and does not need to be compiled again during the application's lifetime if you need to run the same query. You could also rewrite the query as a LINQ expression and create a compiled query to invoke that instead. That won't reduce the complexity of the native query, but it will remove the time it creates to compile the LINQ query into the native query.

A web page could then instantiate the class, call GetCustomer, and then populate controls using the returned data, as shown in Example 21-2.

Example 21-2. Retrieving entities from a separate class

Protected Sub Page_Load(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles Me.Load

  If Not IsPostBack Then
    Dim bal = New DataBridge
    Dim cust = dal.GetCustomerwithRelatedData(_custID)
    persistKeyInfo(cust)
    populateTextBoxes(cust)
    gridView_Addresses.DataSource = cust.Addresses
    gridView_Addresses.DataBind()
    gridView_Reservations.DataSource = cust.Reservations
    gridView_Reservations.DataBind()
  End If
End Sub

protected void Page_Load(object sender, System.EventArgs e)
{
  if (!IsPostBack)
  {
    var bal = new DataBridge();
    var cust = dal.GetCustomerwithRelatedData(_custID);
    persistKeyInfo(cust);
    populateTextBoxes(cust);
    gridView _Addresses.DataSource = cust.Addresses;
    gridView _Addresses.DataBind();
    gridView _Reservations.DataSource = cust.Reservations;
    gridView _Reservations.DataBind();
  }
}

Because the default behavior of the controls is to save their display values in View State, it is not necessary to retrieve the data each time the page posts back.

A class similar to the one you created for the Windows Forms application that provides methods for retrieving data can do the trick. The web page can instantiate that class, request data, and then dispose the class.

As long as the user will not be editing any data, things are pretty straightforward.

21.1.2. Exploring Options for Updating Entities in an ASP.NET Application

The need to update data is where the complexities lie—especially if you want to use ObjectContext to track and save changes.

21.1.2.1. Single or batch updates?

It would be pragmatic to first determine whether you need to be able to update one entity (or graph) at a time, or more than one. This can make a big difference in how you approach the updates.

In web applications, it is common for a user to work with one object at a time and perform a save to the database before moving on to another object. This is the simplest scenario to implement and all of the ASP.NET data source controls use it, including EntityDataSource.

In some applications, having batch edits and update scenarios is desired. An example of this is editing a number of rows in a grid, and then performing an update when the user has completed all of his edits. This also introduces more potential concurrency conflicts if a user is holding onto modifications for a longer period of time. Implementing this scenario has always been a challenge, and the problems regarding persisting large amounts of data to enable batch updates in ASP.NET are not new. The Entity Framework merely adds a few more irons to the fire.

21.1.2.2. Persist entities or use independent values?

It's easiest to make a call to SaveChanges when you have a long-running ObjectContext that is not an option in the web scenario.

NOTE

What about Global.asax? Although it is technically possible to spin up an ObjectContext in Global.asax when the web application starts up on the server and to use that as a global cache for entities, this would wreak havoc on your web server. That single context will attempt to coordinate every user's queries and updates.

Without the long-running context, there are two possible paths to take.

The first involves persisting the entities in memory on the server—most likely in the user's session. When the user wants to update you can attach those entities to a new context and update them using the new values coming from the controls on the page, then call SaveChanges. As you learned in Chapter 20, you would have to detach each entity from the context and use binary serialization to store them in a variable. Although this avoids an extra round trip to the server, there are two downsides to watch out for that have to do with scalability as your user base grows, with more users hitting your website simultaneously. You'll read about these in the next few pages.

A potential compromise to storing the full graph is to store only the entity values, but you would need to store the relationships as well, and since the entities don't have much more information in them than their values, you won't reduce the amount of data that is being persisted. At the same time, walking through a graph and extracting the properties, as well as those of all of the related entities, and then rebuilding them will be quite an intensive process.

The second path for allowing SaveChanges to do its job would entail performing the query again prior to saving. You can then update the newly queried entities with the values coming from the client. You saw this pattern when writing web services and WCF services in Chapter 14.

Before you consider whether to persist the data in memory, you will need to understand ASP.NET's mechanisms for storing data in memory and how those mechanisms are impacted when using entities.

21.1.3. Evaluating ASP.NET's State Solutions Against the Entity Framework

ASP.NET provides a number of mechanisms for maintaining the state of objects even as the Page object is destroyed. Let's take a look at those and see how they work for the entities.

21.1.3.1. View State

View State creates an encrypted binary stream of data representing objects or other data that it adds to the HTML of the page. Many ASP.NET controls use View State as a way to retain the contents of the control even if the page is posted back. For example, the text in a TextBox or the values in a DataGrid can automatically be stored in View State. When the page posts back, ASP.NET reads the View State and uses what it finds to help render the new HTML.

View State is a user interface mechanism and not something you would use outside of the ASP.NET Page class. If you don't have a lot of experience with ASP.NET, there are things about View State with respect to the Entity Framework that you should be aware of.

You can actually see the View State data if you view the source of an ASP.NET page in your web browser. The contents of View State can easily bloat the HTML and create performance problems if you use it without care. The biggest abuser of View State is generally a GridView, which could contain many rows and many columns' worth of data that it is trying to save across postbacks.

Although many of ASP.NET's controls and features can automatically read and write to View State, it is also possible for a developer to explicitly store objects into View State and retrieve them again when needed using a key to identify the data being stored (see Example 21-3). You'll need to cast the View State data back to its correct type when you retrieve it.

View State is a good place to persist small bits of data such as entity key information or TimeStamp values, but it is not advisable to use View State to persist entire entity objects, graphs, or even collections of entities.

Example 21-3. Explicitly storing and retrieving a small piece of data in View State

Page.ViewState("myKey")=myCustomer.EntityKey
custkey = CType(ViewState("custKey"), EntityKey)

Page.ViewState["myKey"]=myCustomer.EntityKey;
custkey = (EntityKey)(ViewState["custKey"]);

The EntityDataSource control uses View State behind the scenes to retain entity values; most importantly, the original values of the entity being edited so that when it's time to call SaveChanges, the context has access to Original and Current values and is able to build update commands based on them.

Although the focus of this chapter is on pulling the ObjectContext out of the UI layer, you may be curious about pushing entities into View State. ObjectContext is thankfully not an option, because it is not serializable.

Figure 21-4 shows the source for a simple page where a single Customer entity is being retrieved and put into View State. The only control on the page is a TextBox to serve as a basis for measuring View State and a button to provide a way to force a postback on the page. The figure shows a screenshot of only half of the source of the page. The other half is filled with the View State as well.

Figure 21-4. Half of the View State for a page that contains only a single Customer entity

When the customer is not being stored in View State, the size of the page is about 1,000 bytes. With the single customer stored into View State the size of the page grows to nearly 11,400 bytes. That is a pretty significant amount of data. And you can see in Table 21-1 how it could easily grow even larger. You need to watch out for this with any objects that you persist in View State, such as data sets, not just entities.

Table 21-1. Impact of Customer entities on View State
Customer entities in View State | Size of page (bytes)
0 | 1,038
1 | 11,380
20 | 44,588
50 | 97,676

This gives you a good idea of the potential impact of storing entities in View State. As the View State increases, the time it takes to deliver the page to the browser also increases. The first entity stored in View State has additional data included in it. After that, the additional Customer entities are only about 1,750 bytes each. You may not want to incur this additional cost in your applications.

21.1.3.2. Application cache and session state

The most common alternative to retaining objects in memory is to store them in the server's memory using either the application cache or ASP.NET session state.

Application cache is used to retain data that is accessed frequently but does not change frequently. More importantly, application cache is shared across all active sessions of a web application. It does not provide unique storage of memory for each user. Instead every user would be working with the same set of data. You could use application cache for read-only data that you want to share among users and you'll see a pattern for doing this further on in this chapter. However if that data needs to participate in relationships with data that the users are editing, which would require them to be managed by the same ObjectContext, then this wouldn't be feasible. Finally, you probably do not want to consider editing data that is being retained in the application cache unless you have a very specific need and are confident that you will have precise control over the interaction.

With session state, however, ASP.NET preserves a cache of memory on the server for every user currently accessing your website using a class called Session. Not only is session state a great way to retain information in memory, but as a user moves from page to page in your web application, the session state remains available. When the user ends her session with your website, that chunk of memory is disposed. Session state is most commonly accessed through a Page class, but you can also use it from a business layer.

Like View State, session state can only store objects that are serializable, and like View State it can grow out of control if you are not paying attention, but in a way that can be worse than an individual user's View State.

Although View State offloads this information to the browser, session state puts all of the stored information for every user hitting the website on the server. If 10 people are using your website, the server needs to store the session state for those 10 people in memory. If 1,000 people are using your website, imagine how much of the server's memory you might need to store all of their session state information. If you are concerned with scalability, you should use session state wisely.

NOTE

For websites that scale out dramatically, to the extent that multiple servers are used, session state can become problematic as a user may not hit the same server after a postback. In this case, your options will be to hit the database (using a ClientWins Refresh or requery) or to use one of the other session state solutions available for ASP.NET. The latter is a topic to be researched in the many resources and books that are dedicated to ASP.NET and website performance.

Another important thing to realize regarding session state is that a lot of effort is required for ASP.NET to move objects in and out of Session.

In the long run, session state is a very tempting place to store data, but you should use it for storing only small amounts of information as it comes with a lot of baggage.

ASP.NET provides another option which is a reasonable balance between achieving a near-perfect n-tier architecture and getting the job done without a huge amount of complexity—the ObjectDataSource control.


21.2. Introducing ASP.NET's ObjectDataSource Control

The ObjectDataSource control was introduced in ASP.NET 2.0. It acts as a bridge between data-bound server controls and your own classes that provide CRUD (Create, Read, Update, and Delete) methods for interacting with your business objects. With an ObjectDataSource control, you can point to a class and hook up its various CRUD methods to the Select, Insert, Update, and Delete events of the ObjectDataSource. The method that you tie to the ObjectDataSource.Select event is expected to return an IList, which can contain one or more objects. ObjectDataSource's Insert, Update, and Delete events are designed to work with a single object, not a collection. Once the ObjectDataSource is wired up to this class, you can use the ObjectDataSource as the DataSource of a server that is data-binding controls, such as GridView or FormView. Data-binding controls can automatically format themselves based on the properties of the object exposed through the ObjectDataSource.

21.2.1. Why ObjectDataSource?

ObjectDataSource relieves the UI developer of myriad complexities when trying to bind objects to web server controls. You'll see that to use ObjectDataSource with entities you'll need to execute a number of tricks; but in the long run, it is still a great deal simpler than trying to orchestrate updatable data binding manually.

Figure 21-5 shows how the ObjectDataSource fits into an application where it is used to serve up Contact entities. The class that provides the CRUD methods in this image is named ContactProvider. It will use an ObjectContext to retrieve and save changes to Contact entities.

Figure 21-5. ObjectDataSource's CRUD properties pointing to the business layer's CRUD methods

21.2.2. ObjectDataSource Enforces Its Rules on Your Objects

This class will be quite different from the class we discussed in Chapter 20. ObjectDataSource can interact with only a single type, such as Contact. So although ObjectDataSource simplifies data binding, especially for pages with master detail information, you'll need a separate ObjectDataSource for each type you are working with and a separate set of CRUD methods in your provider class or classes. The Insert, Update, and Delete methods that you bind it to will operate on only a single object, resulting in a lot of calls to the server as well as to the database.

Because of this, when using an ObjectDataSource, you will not be able to take advantage of entity graphs or relationships or ObjectContext's ability to retain change tracking for many entities at once. It's not impossible, but you would have to do so many tweaks that it will eliminate the value of the control.

ObjectDataSource and Its Many Calls to the Database

One of the advantages of the Entity Framework is that you can query for a graph in a single call to the database and update the full graph as well. However, when using ObjectDataSources to populate a web page with related data, you will query for and update those objects separately. This means a lot of hits to the database.

Whether this is a bad thing is relative and depends on your application, your users, your resources, and your needs. The many single calls to the database are part of the paradigm of the stateless web. Although client-side applications are expected to work with data as a group—for example, edit a customer and the customer's orders and then call one update—web users are used to working with records individually. On top of this, the web page is already hitting the server every time the user clicks on an Insert, Update, or Delete button. With the default controls, even a Cancel button causes a call to the server.

21.2.3. How the ObjectDataSource Gets Data into and out of the Data-Binding Controls

Consider a master/detail page using the BreakAway data where you might use a DetailsView to display the parent record (e.g., Customer) and a GridView to display a collection of children (e.g., Reservations).

You would bind the DetailsView to an ObjectDataSource that you have linked to a Customer provider with Get/Update/Insert and Delete methods for Customer entities. ASP.NET automatically wires the DetailsView events to the ObjectDataSource methods. So, when a user updates a record in the DetailView, that control automatically calls the ObjectDataSource's Update method. It then takes the values in the data control and passes them to the Update method in the business layer.

This happens separately for each control. If you have the master object, Customer, displayed in the DetailsView control, and then Customer.Reservations in another control, the Reservations would need its own ObjectDataSource that hooks up to methods that retrieve, insert, update, and delete reservations. You can see this in Figure 21-6.

Figure 21-6. Each ObjectDataSource on a page working with a different business object


21.3. Designing Object Provider Classes to Be Used with an ObjectDataSource

To plan what object provider classes you will want to design, you should have an idea of what objects the web page will require.

The example web page that you'll create, shown in Figure 21-7, is for BreakAway customers to update their personal data and view their reservation information. The form will contain three ObjectDataSources. The personal information from the Customer entity is displayed in a DetailsView that is hooked up to one of the ObjectDataSources and can be edited. The Customer's Reservations will be displayed in a ListView control and will be read-only. Addresses will be displayed in a tiled ListView control. The user can add, edit, or delete addresses. The Reservations and Addresses ListViews are hooked up to the other two ObjectDataSource controls.

Because the customers will be able to edit their personal data, this means you'll need to provide the Activity and Destination lists so that they can select their favorites. A separate class will be used to provide these read-only lists.

Figure 21-7. The Customer data entry form prior to the input of the talented web designer

In all, the solution requires four new classes: CustomerProvider, AddressProvider, ReservationProvider, and ListProvider.

First, we'll write these classes, and then you'll get to see how to create and wire up the ObjectDataSource.

21.3.1. Creating the CustomerProvider Class

Example 21-4 lays out the class that acts as the Customer provider that will work with an ObjectDataSource.

NOTE

You may want to incorporate these methods into a class that you can use with a variety of UIs. But keep in mind that because ObjectDataSource forces you to deal with one object at a time, these may not be useful in other applications where you'll want to work with related data graphs in a single ObjectContext.

Because customers are not deleted in the BreakAway business, there is no method for delete. Nor is there an insert method, because this web UI is meant to be used by existing customers. The AddressProvider, further on, has these additional methods.

An important feature of this class is that it implements IDisposable. This is a requirement for it to be used by ObjectDataSource. IDisposable allows a class to be explicitly disposed by classes that instantiate it, rather than waiting around for the garbage collector to see that it's no longer being used.

There are a few notable differences from the class you built for the Windows form in Chapter 20. Because the ObjectContext is short-lived, there's no need to worry about how incoming data should be merged because the context will always be brand-new when a query is executed and it won't contain any preexisting entities. Also, to allow the UI to display the names of the navigation properties (e.g., the ActivityName from PrimaryActivity) those navigations are included in the query. Only existing customers will use this website; therefore, there is no need for the method to convert a contact to a customer as you did in the smart client in the previous chapter.

NOTE

Although the context and the class are short-lived, the web application is not. Precompiling LINQ queries is a very good idea for websites. The query in GetCustomer uses the Where query builder method (you can tell because of the Entity SQL clause). Therefore, an Entity SQL expression will be created and will be reused from the query plan cache. Remember that not every Entity SQL expression falls into the scope of what will benefit from caching, so if you are unsure about what to do, use a LINQ query instead and precompile it.

Notice in Example 21-4 that a graph is being retrieved. The method associated with ObjectDataSource's Read can return a graph. The limitation for single entities is for the Insert, Update, and Delete methods. Therefore, you can display the related data that is in the graph, but you only update the primary entity. You will be able to get to Customer.PrimaryActivity.ActivityName and the other reference properties because the full graph will be returned to the web page.

Notice that the class leverages the CommandExecutor class that you built in the previous example.

NOTE

To benefit, add overloads to the GetList, GetFirstorDefault, and GetReferenceList methods in the CommandExecutor to allow you to pass in a MergeOption. The entities created in the following classes will be immediately detached from the context when the page is disposed. Therefore, there is no point in wasting the extra effort to create ObjectStateEntry objects for the entities. The code listings that follow do not demonstrate this, however the download on the book's website does.

Example 21-4. The CustomerProvider class

Imports BAGA
Imports System.Data.Objects

Public Class CustomerProvider
  Implements IDisposable

  Private _commonContext As BAEntities
  Private _dal As DAL.CommandExecutor

  Public Sub New()
    _commonContext = New BAEntities
    _dal = New DAL.CommandExecutor
  End Sub

  Public Function GetCustomer (ByVal ContactID As Int32) As Customer

  'include Entity References to get ActivityName and DestinationName
    Dim custs = _commonContext.Contacts.OfType(Of Customer) _
                              .Include("PrimaryActivity") _
                              .Include("SecondaryActivity") _
                              .Include("PrimaryDestination")
                              .Include("SecondaryDestination") _
                              .Where("it.Contactid=" & ContactID)
   'return the object, not the query
    Return _dal.ExecuteFirstOrDefault(custs)
  End Function

  Public Sub UpdateCustomer(ByVal Customer As Customer, _
                            ByVal orig_Customer As Customer)
    Try
     'build entitykey from incoming contactID so that you can Attach
      Dim ekey = New EntityKey("BAEntities.Contacts", _
                               "ContactID", orig_Customer.ContactID)
      orig_Customer.EntityKey = ekey

      _commonContext.Attach(orig_Customer)

     'don't let certain props ever get overwritten
     'this code is unnecessary if these properties' setters were marked
     'Internal
      Customer.AddDate = orig_Customer.AddDate
      Customer.InitialDate = orig_Customer.InitialDate

      _commonContext.ApplyPropertyChanges("Contacts", Customer)

    'ApplyPropertyChanges does not affect navigation properties
    'You need to update manually
      With orig_Customer
        .PrimaryActivityReference.EntityKey = _
          Customer.PrimaryActivityReference.EntityKey
        .SecondaryDestinationReference.EntityKey = _
          Customer.SecondaryDestinationReference.EntityKey
        .PrimaryDestinationReference.EntityKey =  _
          Customer.PrimaryDestinationReference.EntityKey
        .SecondaryDestinationReference.EntityKey = _
          Customer.SecondaryDestinationReference.EntityKey
      End With

      'call the custom SaveAllChanges method
      _commonContext.SaveAllChanges()
    Catch ex As Exception
     'insert proper exception handling here... don't just throw it
      Throw
    End Try
  End Sub

End Class

using BAGA;
using System.Data.Objects;
namespace BAGA.ObjectProviders
{
  public class CustomerProvider : IDisposable
  {
    private BAEntities _commonContext;
    private DAL.CommandExecutor _dal;
    public CustomerProvider()
    {
        _commonContext = new BAEntities();
        _dal = new DAL.CommandExecutor();
    }

    public Customer GetCustomerwithRelatedData(Int32 ContactID)
    {
      var custs = _commonContext.Contacts.OfType<Customer>()
                               .Include("PrimaryActivity")
                               .Include("SecondaryActivity")
                               .Include("PrimaryDestination")
                               .Include("SecondaryDestination")
                               .Where("it.Contactid=" + ContactID);
      return _dal.ExecuteFirstorDefault(custs);
      }
    public void UpdateCustomer(Customer Customer, Customer orig_Customer)
    {
      try
      {
        //build entitykey from incoming contactID so that you can Attach
        var ekey = new EntityKey("BAEntities.Contacts",
                                 "ContactID", orig_Customer.ContactID);
        orig_Customer.EntityKey = ekey;
        _commonContext.Attach(orig_Customer);

        //don't let certain props ever get overwritten
        Customer.AddDate = orig_Customer.AddDate;
        _commonContext.ApplyPropertyChanges("Contacts", Customer);

        //applypropchanges does not affect navigation properties
        //critical to have original reference id
        //SaveChanges uses that when building the update cmd
        orig_Customer.PrimaryActivityReference.EntityKey =
          Customer.PrimaryActivityReference.EntityKey;
        orig_Customer.SecondaryActivityReference.EntityKey =
          Customer.SecondaryActivityReference.EntityKey;
        orig_Customer.PrimaryDestinationReference.EntityKey =
          Customer.PrimaryDestinationReference.EntityKey;
        orig_Customer.SecondaryDestinationReference.EntityKey =
         Customer.SecondaryDestinationReference.EntityKey;
        //SavingChanges event takes care of Modified Date
        _commonContext.SaveAllChanges();
      }
      catch (Exception ex)
      {
        //insert proper exception handling here...
        throw ex;
      }
    }
  }
} //end of root namespace

In the GetCustomer method, pay special attention to the fact that the method returns an object, not the query. If you returned the query, it will be executed when the page begins to render the control to which the query is bound. This will fail because the ObjectContext instantiated in the CustomerProvider object will be out of scope.

The UpdateCustomer method does a number of things that are specific to how the Entity Framework works.

First, it takes an original version of the object and the current version. The ObjectDataSource has a means to provide both of these values. This Customer exists in the database, so we need to Attach to the context, not Add. However, Attach requires an EntityKey, so you need to create that using the ContactID. This is because the Customer will be created from scratch using the values in the data-binding control on the UI side. You'll see the UI code-behind after of the class provider code listings.

NOTE

Remember that in the BreakAway model, Customer inherits from Contact and therefore has the key property, ContactID, not CustomerID.

The next notable code is an update of the AddDate and InitialDate properties. In case those are used in the UI, you want to make sure that the user or UI developer doesn't attempt to update those values. The simplest thing is to just be sure the original and new values are equal and SaveChanges will ignore them.

Controlling Property Scope with Getter and Setter

You can control access to individual entity properties by modifying the Getter and Setter properties of an Entity property in the Entity Data Model Designer. By setting the Setter property of the Customer.InitialDate and Contact.AddDate properties to Internal, when the Customer entity class is generated the SET for the class property will be marked Internal. Only code in the same assembly can modify the property.

But how will the values ever be set on new Customers and Contacts from external assemblies such as this one? In Chapter 9, you added code to the SavingChanges call that ensured that new Contacts had their AddDate populated and new Customers had their CustomerTypeReference populated with a default value. You can add logic to also ensure that Customer.InitialDate is set.

These modifications are performed in the same assembly where the Customer class and the Contact class live, and would therefore be allowed by the Internal access of the fields. Then you won't have to worry about code in external assemblies modifying those fields inadvertently.

Pay attention to the reminder that ApplyPropertyChanges affects only scalar values, which is why the method updates the EntityReference values manually.

The call to the SaveAllChanges custom property that you wrote in Chapter 18 will handle concurrency exceptions. If SaveAllChanges has anything to report back to this class, you can catch it in the exception handler.

21.3.2. Creating the AddressProvider Class

The AddressProvider class (see Example 21-5) contains all four CRUD operations: GetAddresses, UpdateAddress, InsertAddress, and DeleteAddress. The Get and Update methods are similar to those in the CustomerProvider.

This class also contains a few notable bits of code that are discussed after the code listing.

Example 21-5. The AddressProvider class

Imports BAGA
Imports System.Data.Objects

Public Class AddressProvider
  Implements IDisposable
  Private _commonContext As BAEntities
  Private _dal As DAL.CommandExecutor

  Public Sub New()
    _commonContext = New BAEntities
    _dal = New DAL.CommandExecutor
  End Sub

  Public Function GetAddresses(ByVal contactID As Int32) _
   As List(Of Address)
    Dim addresses = _commonContext.Addresses _
                    .Where("it.Contact.ContactID=" & contactid)
    _dal.ExecuteList(addresses)
  End Function

  Public Sub UpdateAddress _
   (ByVal Address As Address, ByVal orig_Address As Address)
    Try
     'build entitykey
      Dim ekey = New EntityKey("BAEntities.Addresses", _
                               "addressID", orig_Address.addressID)
      orig_Address.EntityKey = ekey
      _commonContext.Attach(orig_Address)

     'no navigation properties or other fields to worry about
     'SavingChanges custom event takes care of ModifiedDate
      _commonContext.ApplyPropertyChanges("Addresses", Address)
      _commonContext.SaveAllChanges()
    Catch ex As Exception
      'add proper exception handling
      Throw
    End Try
  End Sub

  ''' <summary>
  ''' Inserts new Address. ContactID property must be set
  ''' </summary>
  ''' <param name="Address"></param>
  Public Sub InsertAddress(ByVal Address As Address)
    Try
      _commonContext.AddToAddresses(Address)
      _commonContext.SaveAllChanges()
    Catch ex As Exception
      'add proper exception handling
      Throw
    End Try
  End Sub

  Public Sub DeleteAddress(ByVal Address As Address)
    Try
      Address.EntityKey = New EntityKey("BAEntities.Addresses", _
                                        "addressID", Address.addressID)
      _commonContext.Attach(Address)
      _commonContext.DeleteObject(Address)
      _commonContext.SaveAllChanges()
    Catch ex As Exception
     'add proper exception handling
      Throw ex
    End Try
  End Sub
End Class

public class AddressProvider : IDisposable
{
  private BAEntities _commonContext;
  private DAL.CommandExecutor _dal;
  public AddressProvider()
  {
    _commonContext = new BAEntities();
    _dal = new DAL.CommandExecutor();
  }

  public List<Address> GetAddresses(Int32 contactid)
  {
    var addresses = _commonContext.Addresses
                    .Where("it.Contact.ContactID=" + contactid);
    return _dal.ExecuteList(addresses);
  }

  public void UpdateAddress(Address Address, Address orig_Address)
  {
    try
    {
      //build entitykey
      var ekey = new EntityKey("BAEntities.Addresses",
                               "addressID", orig_Address.addressID);
      orig_Address.EntityKey = ekey;

      _commonContext.Attach(orig_Address);
      //no navigation properties or other fields to worry about
      //SavingChanges event takes care of Modified Date
      _commonContext.ApplyPropertyChanges("Addresses", Address);
      _commonContext.SaveAllChanges();
    }
    catch (Exception ex)
    {
      //add proper exception handling
      throw;
    }
  }

  public void InsertAddress(Address Address)
  {
    Debug.Assert(Address.ContactID != null,
                "Address.ContactID must be set");
    try
    {
      _commonContext.AddToAddresses(Address);
      _commonContext.SaveAllChanges();
    }
    catch (Exception ex)
    {
      //add proper exception handling
      throw;
    }

  }
  public void DeleteAddress(Address Address)
  {
    try
    {
      Address.EntityKey = new EntityKey(
       "BAEntities.Addresses", "addressID", Address.addressID);
      _commonContext.Attach(Address);
      _commonContext.DeleteObject(Address);
      _commonContext.SaveAllChanges();
    }
    catch (Exception ex)
    {
      //add proper exception handling
      throw;
    }
  }
}

The Insert method has a Debug.Assert in it. An address requires a ContactReference. If it's not included, an exception will be thrown during the SaveChanges call as the Entity Framework checks referential constraints. Debug.Assert will test this during debug, and if the assertion fails, a message will be output to the debug window.

When the UI instantiates the AddressProvider to request an update, the AddressProvider has no idea which Customer is being edited on the UI side. In the UI, the Customer object is not available when the Address is being inserted by its ObjectDataSource. It puts the onus on the UI developer to provide the Customer's EntityKey in the Address.CustomerReference before passing the Address back to the AddressProvider.

However, it seems unfair to force the UI developer to know this type of detail about entities. What's a reference? What's an EntityKey? It would be convenient if the more familiar foreign key, ContactID, was available directly in the address. You can solve this by creating a property in the partial class for Address in the model that does this work on the fly. All it requires is that the UI developer passes in the ContactID, which he will have access to. This property will take the ContactID and create the ContactReference.EntityKey. You'll find this to be a convenient addition to the Address entity and you may want to implement the concept elsewhere in your entities. In fact, you'll be leveraging this in the Customer class to set and expose the ID for PrimaryActivity and the other customer preferences so that the UI developer has easy access to those as well. For the DebugAssert to check this property, it also needs a getter. Example 21-6 shows the new ContactID property that you can add to the partial class for the Address entity in the BreakAwayModel project.

Example 21-6. A ContactID property added into the Address entity's partial class

Public Property ContactID() As Integer
  Get
    If ContactReference.EntityKey.EntityKeyValues.Count > 0 Then
      Return CType( _
       ContactReference.EntityKey.EntityKeyValues(0).Value, Integer)
    Else
      Return Nothing
    End If
  End Get

  Set(ByVal value As Integer)
    If value > 0 Then
      ContactReference.EntityKey = _
       New EntityKey("BAEntities.Contacts", "ContactID", value)
    End If
  End Set
End Property

public int ContactID
{
  get
  {
    if (ContactReference.EntityKey.EntityKeyValues.Count() > 0)
      return (int)ContactReference.EntityKey.EntityKeyValues[0].Value;
    else
      return null;
  }
  set
  {
    if (value > 0)
       ContactReference.EntityKey =
        new EntityKey("BAEntities.Contacts", "ContactID", value);
  }
}

Now the assert will be able to use this, and when we get to the UI, you'll see it in action there as well.

In the Delete method, remember that DeleteObject won't work unless the object is already in the context, so you need to query for it using the AddressID, delete it, and then finally call SaveAllChanges.

The class is pretty straightforward otherwise.

21.3.3. Creating the ReservationsProvider Class

In the target website, the reservations will be read-only. Therefore, the ReservationsProvider class has only one method, GetReservations, which returns a list of reservations for the given customer (see Example 21-7).

Example 21-7. The GetReservations method

Public Function GetReservations(ByVal contactID As Integer) _
 As List(Of Reservation)
   Dim res = _commonContext.Reservations _
             .Include("Trip.Destination") _
             .Include("UnpaidReservation") _
             .Where("it.Customer.ContactID=" & contactid)
   Return _dal.ExecuteList(res)
End Function

public List<Reservation> GetReservations(int contactid)
{
 var res = _commonContext.Reservations
                         .Include("Trip.Destination")
                          .Include("UnpaidReservation")
                          .Where("it.Customer.ContactID=" + contactid);
  return _dal.ExecuteList(res);
}

You can always add in the other CRUD methods if you plan to reuse the class elsewhere.

21.3.4. Providing Reusable, Cached Reference Lists

This web application needs the same drop-down lists as the Windows Forms application in Chapter 20. We'll take the GetReferenceList method that you built in Chapter 20 and enhance it so that it is able to cache the lists. That way, the database call required for these lists won't be repeated for each user of the application.

NOTE

The new pattern is similar to one you might use for any caching strategy and not necessarily specific to Entity Framework. As with other ASP.NET caching strategies, you will likely want to enable periodic refreshing of these lists. Note that unfortunately, the SQL Query Notification feature that was added to ASP.NET 2.0 and ADO.NET 2.0 does not work with Entity Framework. This is driven by the fact that the necessary mechanism required of the database does not exist in all databases and therefore Entity Framework cannot leverage the feature.

First you should take advantage of the web application's application cache to store the lists. Why run the same queries for the same data over and over again and keep hitting the database? Instead, once the lists have been retrieved from the database, they are stored in the application class using a static (Shared in VB) variable. Even as the ListProvider is disposed and reinstantiated, that data remains in the application cache.

The only publicly exposed member of the ListProviders is the RefLists property, which stores the individual lists as they are created and returns them to the caller when requested. This is the property that is exposed as a static variable.

The static variable, RefLists, is a List of reference lists. The ListProvider.GetReferenceList checks to see if the requested list, that is a list with the requested entity type, exists in the RefLists instance. If it does, then GetReferenceList returns the desired list. If not, the helper method, AddNewRefList is called. The helper method creates and executes the necessary queries by calling the CommandExecutor's GetReferenceList method that you wrote in Chapter 20, and then adds the resulting List to RefLists. After this, GetReferenceList is able to return the new list to the calling code.

Because RefLists is static/Shared, it remains in the application memory, indifferent to the ListProvider class being instantiated and disposed. When a new instance of ListProvider is created, the preexisting RefLists is accessed from the application memory. Example 21-8 shows the complete listing for the ListProvider class which serves up reference lists for the user interface.

Example 21-8. The ListProvider class

Public Class ListProvider
  Implements IDisposable
  Private _commonContext As BAEntities
  Private _dal As BAGA.DAL.CommandExecutor
  Private Shared RefLists As List(Of IList)
  Public Sub New()
    _commonContext = New BAEntities()
    _dal = New BAGA.DAL.CommandExecutor()
    RefLists = New List(Of IList)()
  End Sub
  Public Function GetReferenceList(Of TEntity)(ByVal orderBy As String) _
   As IList
    'check to see if the list exists already
    For Each enumList As IList In RefLists
      If TypeOf enumList(0) Is TEntity Then
        Return enumList
      End If
    Next enumList
    'if we are here, then the list was not found
    Dim newlist = AddNewRefList(Of TEntity)(orderBy)
    Return newlist
  End Function
  Private Function AddNewRefList(Of TEntity)(ByVal orderBy As String) As IList
    If GetType(TEntity) Is GetType(Trip) Then
      Dim tripQuery = _
       From t In _commonContext.Trips.Include("Destination") _
       Order By t.Destination.DestinationName, t.StartDate _
       Select t
      Dim newlist As List(Of TEntity) = _
       _dal.ExecuteList(Of Trip)(tripQuery).Cast(Of TEntity).ToList
      RefLists.Add(newlist)
      Return newlist
    Else
      Dim newlist = _dal.GetReferenceList(Of TEntity)(_commonContext, orderBy)
      RefLists.Add(newlist)
      Return newlist
    End If
  End Function
End Class

public class ListProvider : IDisposable
{
  private BAEntities _commonContext;
  private BAGA.DAL.CommandExecutor _dal;
  static List<IList> RefLists;

  public ListProvider()
  {
    _commonContext = new BAEntities();
    _dal = new BAGA.DAL.CommandExecutor();
    RefLists = new List<IList>();
  }

  public IList GetReferenceList<TEntity>(string orderBy)
  {
     //check to see if the list exists already
    foreach (IList enumList in RefLists)
    {
      if (enumList[0] is TEntity)
        return enumList;
    }
    //if we are here, then the list was not found
    var newlist= AddNewRefList<TEntity>(orderBy);
    return newlist;
  }

  private IList AddNewRefList<TEntity>(string orderBy)
  {
    if (typeof(TEntity) == typeof(Trip))
    {
      var tripQuery =
          from t in _commonContext.Trips.Include("Destination")
          orderby t.Destination.DestinationName, t.StartDate
          select t;
      List<TEntity> newlist= _dal.ExecuteList<Trip>.Cast<TEntity>().ToList();
      RefLists.Add(newlist);
      return newlist;
    }
    else
    {
      var newlist = _dal.GetReferenceList<TEntity>(_commonContext, orderBy);
      RefLists.Add(newlist);
      return newlist;
    }
  }
}


21.4. Wiring Up the Provider Classes to ObjectDataSource Controls

Although wiring up the classes to the ObjectDataSource controls' properties is no different from wiring up any other business classes you may use with ObjectDataSource, you will need to take some action in some of the ObjectDataSource controls' events specifically because you are working with entities. Additionally, the data-bound controls will need some special tweaks as well.

21.4.1. Using the ObjectDataSource Wizard to Perform the Initial Wiring to the Provider Classes

If you are unfamiliar with using an ObjectDataSource control, it has a wizard—the ObjectDataSource Wizard—which helps with the basics of wiring up the class and the methods. When you create a new ObjectDataSource, a drop-down list shows all of the classes that are available. If your provider classes are in a different assembly, you'll need a reference to that assembly. Once you have selected the provider class, the wizard walks you through selecting which methods in that class to associate with the different actions—Select, Update, Insert, and Delete. You need to wire up only the actions you want to use. So, in the CustomerDataSource, you only need to identify the methods for Select and Update.

NOTE

ObjectDataSource newbies can find lots of resources and walkthroughs in the MSDN docs and elsewhere.

21.4.2. Tweaking ObjectDataSource Properties to Work with Entities

Once you have walked through the wizard, you need to take some important steps to modify the properties of the new ObjectDataSource controls so that they can work with the BreakAway entities.

We'll use the CustomerDataSource as an example.

21.4.2.1. Getting the object to the entity provider

The ObjectDataSource needs to be able to automatically construct the proper objects using the values in the binding controls. Specify the class using its fully qualified name, in the DataObjectTypeName property BAGA.Customer.

NOTE

If you don't specify the DataObjectTypeName property and the ObjectDataSource needs it to construct a desired class, you will get an exception. If your class methods require special parameters, you'll need to leave the property empty and build those parameters in the appropriate event. This example does not use parameters other than the classes.

21.4.2.2. Providing original and current values for updates

Because the Entity Framework requires original values in order to properly execute SaveChanges, you'll need to set the ObjectDataSource.ConflictDetection property to CompareAllValues. This will tell the ObjectDataSource to send both the current version of the object and a version containing the original values.

Use the ObjectDataSource.OldValuesParameterFormatString property to designate a string to append to the object name when constructing the original version of the object. A typical setting for this is orig_{0}, which will name the object containing original values to orig_Customer when the object itself is Customer.

These are the only additional settings you'll need to make to the ObjectDataSource.

21.4.3. Using ObjectDataSource Events to Solve Problems That Are Particular to Entities

Although a good portion of the work that is necessary to get values from the binding control to the entity providers is automated, you need to do some special things because of the way in which entities function.

21.4.3.1. Inserting and updating entities that contain EntityReferences

Recall that EntityDataSource is able to flatten navigation properties as they get to a databinding Control. The ObjectDataSource is not able to do that. This means you'll have to explicitly do some additional binding in the markup, and it also means those properties won't automatically make the return trip as they did with the EntityDataSource, so you'll need to do some manual labor in the code-behind to get around this.

Customer has five properties that are EntityReferences: CustomerType, PrimaryActivity, SecondaryActivity, PrimaryDestination, and SecondaryDestination. We'll focus on the last four.

The query in the CustomerProvider class included the navigations for the preference properties.

By default, when you data-bind the control to the ObjectDataSource, BoundField controls will be created for each field in the object. Example 21-9 shows the control that displays FirstName.

Example 21-9. BoundField controls for a scalar property

<asp:BoundField DataField="FirstName" HeaderText="FirstName"
                SortExpression="FirstName" />

But BoundFields do not support navigation paths. Instead, you'll need to use a TemplateField that uses DataBinder.Eval for the binding. Example 21-10 shows how ActivityName from the PrimaryActivity navigation property is displayed with an ItemTemplate.

Example 21-10. A TemplateField, which can be used for navigation properties

<asp:TemplateField HeaderText="Favorite Activity">
  <ItemTemplate>
   <asp:Label ID="Label1" runat="server"
               Text='<%# Databinder.Eval(Container.DataItem,
                         "PrimaryActivity.ActivityName") %>'>
    </asp:Label>
  </ItemTemplate>
</asp:TemplateField>

The ItemTemplate is used for the read-only view of the control. Additionally, an EditItemTemplate is required if you plan for users to edit the value. You can use a drop-down list in an EditItemTemplate so that users will be able to select from a list of activities.

Example 21-11 shows the complete TemplateField, including both the ItemTemplate and the EditItemTemplate.

Example 21-11. A TemplateField with both ItemTemplates to display and edit

<asp:TemplateField HeaderText="Favorite Activity">
  <EditItemTemplate>
    <asp:DropDownList runat="server" id="act1DDL">
    </asp:DropDownList>
  </EditItemTemplate>
  <ItemTemplate>
    <asp:Label ID="Label1" runat="server"
               Text='<%# DataBinder.Eval(Container.DataItem,
                         "PrimaryActivity.ActivityName") %>'>
    </asp:Label>
  </ItemTemplate>
</asp:TemplateField>

Notice that the EditItemTemplate has no binding. All of this needs to be done in the code-behind of the page. Let's look at how that works.

21.4.3.2. Editing EntityReference navigation properties

The only time the lists are needed is when the user is about to edit a customer record. In the example, a DetailsView control is used to display the customer. Therefore, the time to retrieve and bind the lists is when DetailsView is being rebuilt for the edit view. This translates to: after the user has clicked the Edit button and the page is in the process of posting back with the intention of rendering the DetailsView in Edit mode.

The point in the page events where you can insert your logic to bind the lists from the ListProvider to the DropDownLists in the UI is in the DetailsView.PreRender event.

In this event, you'll first need to check to see whether the DetailsViewMode is Edit. If it is, a new ListProvider object is instantiated to get the lists of destinations and activities. To bind to the drop-downs, you need to extract each drop-down list control from within the DetailsView using the FindControl method. It's tricky work, but the code listing in Example 21-12 will do the job for you.

NOTE

This method uses EntityReference values which have been stored into ViewState in another method. You'll see the code that does this further on.

Example 21-12. Binding the drop-downs at runtime

Protected Sub custDV_PreRender(ByVal sender As Object, ByVal e As EventArgs)
  'only get lists when in Edit mode
  If custDV.CurrentMode = DetailsViewMode.Edit Then
    'get lists
    Dim dal As New DAL.ListProvider()
    Dim actList = dal.GetReferenceList(Of Activity)("ActivityName")
    Dim destList = dal.GetReferenceList(Of Destination)("DestinationName")

    'find drop downs in markup
    Dim ddlA1 = CType(custDV.FindControl("act1DDL"), DropDownList)
    Dim ddlA2 = CType(custDV.FindControl("act2DDL"), DropDownList)
    Dim ddlL1 = CType(custDV.FindControl("loc1DDL"), DropDownList)
    Dim ddlL2 = CType(custDV.FindControl("loc2DDL"), DropDownList)

    'call custom method to bind the lists to the dropdowns
    BindPickList(ddlA1, actList, CInt(ViewState("origAct1ID")), _
                 "ActivityID","ActivityName")
    BindPickList(ddlA2, actList, CInt(ViewState("origAct2ID")), _
                "ActivityID","ActivityName")
    BindPickList(ddlL1, destList, CInt(ViewState("origDest1ID")), _
                "DestinationID","DestinationName")
    BindPickList(ddlL2, destList, CInt(ViewState("origDest2ID")), _
                "DestinationID", "DestinationName")
  End If
End Sub

Private Sub BindPickList(ByVal ddl As DropDownList, ByVal list As IList, _
                        ByVal ddlValue As Integer, ByVal IDProp As String, _
                        ByVal DisplayProp As String)
  ddl.DataSource = list
  ddl.DataTextField = DisplayProp
  ddl.DataValueField = IDProp
  ddl.DataBind()
  ddl.SelectedValue = ddlValue.ToString()
End Sub

protected void custDV_PreRender(object sender, EventArgs e)
{
  //only get lists when in Edit mode
  if (custDV.CurrentMode == DetailsViewMode.Edit)
  {
    //get lists
    DAL.ListProvider dal = new DAL.ListProvider();
    var actList = dal.GetReferenceList<Activity>("ActivityName");
    var destList = dal.GetReferenceList<Destination>("DestinationName");

    //find drop downs in markup
    var ddlA1 = (DropDownList)(custDV.FindControl("act1DDL"));
    var ddlA2 = (DropDownList)(custDV.FindControl("act2DDL"));
    var ddlL1 = (DropDownList)(custDV.FindControl("loc1DDL"));
    var ddlL2 = (DropDownList)(custDV.FindControl("loc2DDL"));

    //call custom method to bind the lists to the dropdowns
    BindPickList(ddlA1, actList, (int)ViewState["origAct1ID"],
                "ActivityID","ActivityName");
    BindPickList(ddlA2, actList, (int)ViewState["origAct2ID"],
                "ActivityID","ActivityName");
    BindPickList(ddlL1, destList, (int)ViewState["origDest1ID"],
                "DestinationID","DestinationName");
    BindPickList(ddlL2, destList, (int)ViewState["origDest2ID"],
                "DestinationID", "DestinationName");
  }
}

private void BindPickList(DropDownList ddl, IList list, int ddlValue,
                          string IDProp,string DisplayProp)
{
  ddl.DataSource = list;
  ddl.DataTextField = DisplayProp;
  ddl.DataValueField = IDProp;
  ddl.DataBind();
  ddl.SelectedValue = ddlValue.ToString();
}

BindPickList wraps the redundant data-binding code for each drop-down into a single method. After the list is bound, it also sets the Selected value of the drop-down to the current value of the navigation property.

Figure 21-8 displays the result of the binding and shows a section of the form when the DetailsView is in Edit mode.

Figure 21-8. Editing navigation properties with DropDownLists

21.4.3.3. Updating EntityReference navigation properties

The ObjectDataSource will not know how to populate the navigation properties when it builds the Customer object for Updates. This problem would exist for Insert and Delete as well if they were being used for Customer objects. Therefore, you will have to lend a helping hand to the Update method of the ObjectDataSource control.

The ObjectDataSourceStatusEventArgs of this method contains an InputParameters property. This property provides references to both the current and original objects. In the Updating method, you can populate the values of the four preference properties before the objects are passed on to the provider class. The current values can be found in the drop down lists while the original values have been stored in ViewState. You accessed these already in the PreRender event. Let's first take a look at the ObjectDataSource's Selected event where the reference values were stored in ViewState in Example 21-13.

Example 21-13. Storing the original EntityReference values in View State for later use

Private Sub CustomerDataSource_Selected(ByVal sender As Object, _
 ByVal e As System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs) _
 Handles CustomerDataSource.Selected

  Dim cust = CType(e.ReturnValue, BAGA.Customer)
  'dropdown list can't take a null value, so use 0 as a default
  If cust.PrimaryActivity Is Nothing Then
    ViewState("origAct1ID") = 0
  Else
    ViewState("origActID") = cust.PrimaryActivity.ActivityID
  End If

  If cust.SecondaryActivity Is Nothing Then
    ViewState("origAct2ID") = 0
  Else
    ViewState("origActID") = cust.SecondaryActivity.ActivityID
  End If

  If cust.PrimaryDestination Is Nothing Then
    ViewState("origDest1ID") = 0
  Else
    ViewState("origDest1ID") = cust.PrimaryDestination.DestinationID
  End If

  If cust.SecondaryDestination Is Nothing Then
    ViewState("origDest2ID") = 0
  Else
    ViewState("origDest2ID") = cust.SecondaryDestination.DestinationID
  End If
End Sub

private void CustomerDataSource_Selected(object sender,
 System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs e)
{
  var cust = (BAGA.Customer)e.ReturnValue;
  //dropdown list can't take a null value, so use 0 as a default
  if (cust.PrimaryActivity == null)
    ViewState["origAct1ID"] = 0;
  else
    ViewState["origActID"] = cust.PrimaryActivity.ActivityID;

  if (cust.SecondaryActivity == null)
    ViewState["origAct2ID"] = 0;
  else
    ViewState["origActID"] = cust.SecondaryActivity.ActivityID;

  if (cust.PrimaryDestination == null)
    ViewState["origDest1ID"] = 0;
  else
    ViewState["origDest1ID"] = cust.PrimaryDestination.DestinationID;

  if (cust.SecondaryDestination == null)
    ViewState["origDest2ID"] = 0;
  else
    ViewState["origDest2ID"] = cust.SecondaryDestination.DestinationID;
}

Now, the values can be retrieved in the CustomerDataSource.Updating event. First you will need to grab the references to the original and current customer, then update the reference properties with the values from ViewState. This is demonstrated in Example 21-14.

Example 21-14. Adding reference values to the customer objects in ObjectDataSource.Updating

Protected Sub CustomerDataSource_Updating(ByVal sender As Object, _
 ByVal e As System.Web.UI.WebControls.ObjectDataSourceMethodEventArgs)

  'cast objects from event args to Customer types
  Dim currCust As var=CType(e.InputParameters(0), Customer)
  Dim origCust As var=CType(e.InputParameters(1), Customer)

  'set ref values on current customer
  currCust.PrimaryActivityID =  _
   CInt((CType(custDV.FindControl("act1DDL"), DropDownList)).SelectedValue)
  currCust.SecondaryActivityID = _
   CInt((CType(custDV.FindControl("act2DDL"), DropDownList)).SelectedValue)
  currCust.PrimaryDestinationID =  _
    CInt ((CType(custDV.FindControl("loc1DDL"), DropDownList)).SelectedValue)
  currCust.SecondaryDestinationID =  _
   CInt((CType(custDV.FindControl("loc2DDL"), DropDownList)).SelectedValue)

  'set ref values on original customer
  origCust.PrimaryActivityID = CInt(ViewState("origAct1ID"))
  origCust.SecondaryActivityID = CInt(ViewState("origAct2ID"))
  origCust.PrimaryDestinationID = CInt(ViewState("origDest1ID"))
  origCust.SecondaryDestinationID = CInt(ViewState("origDest2ID"))

End Sub

protected void CustomerDataSource_Updating(object sender,
 System.Web.UI.WebControls.ObjectDataSourceMethodEventArgs e)
{
  //cast objects from event args to Customer types
  var currCust=(Customer)e.InputParameters[0];
  var origCust=(Customer)e.InputParameters[1];

  //set ref values on current customer
  currCust.PrimaryActivityID =
   Convert.ToInt32(((DropDownList)(custDV.FindControl("act1DDL"))).SelectedValue);
  currCust.SecondaryActivityID =
   Convert.ToInt32(((DropDownList)(custDV.FindControl("act2DDL"))).SelectedValue);
  currCust.PrimaryDestinationID =
   Convert.ToInt32(((DropDownList)(custDV.FindControl("loc1DDL"))).SelectedValue);
  currCust.SecondaryDestinationID =
   Convert.ToInt32(((DropDownList)(custDV.FindControl("loc2DDL"))).SelectedValue);

  //set ref values on original customer
  origCust.PrimaryActivityID = (int)ViewState["origAct1ID"];
  origCust.SecondaryActivityID = (int)ViewState["origAct2ID"];
  origCust.PrimaryDestinationID = (int)ViewState["origDest1ID"];
  origCust.SecondaryDestinationID = (int)ViewState["origDest2ID"];
}

With this code in place, edits to the reference navigation properties will be included in the update.

21.4.3.4. Inserting records with EntityReference properties

One last ObjectDataSource event tweak is required to accommodate working with entities. The Update method for Address will be handled completely by the behind-the-scenes work that the ObjectDataSource and data-binding control perform. The Insert and Delete need some help, though. Address has a referential constraint that requires that the ContactReference exists. Although the AddressDataSource is bound to the SelectedValue of the Customer's DetailsView, it won't know to use that value for new addresses. And because of the way deletes work when EntityRefs are involved, the Delete operation will also need the value.

Thanks to the new Address.ContactID property you added to the Address partial class earlier in this chapter, you can set the ContactID of the Address in a single method that covers both the Inserting and Deleting events (see Example 21-15).

Example 21-15. Forcing the ContactID value into the Address for inserts and deletes

Private Sub AddressDataSource_InsertingorDeleting _
 (ByVal sender As Object, ByVal e As ObjectDataSourceMethodEventArgs) _
 Handles AddressDataSource.Inserting, AddressDataSource.Deleting

  Dim add = CType(e.InputParameters("Address"), BAGA.Address)
 'tie address to current customer using custom property
  add.ContactID = custDV.SelectedValue
End Sub

protected void AddressDataSource_InsertingorDeleting(object sender,
 System.Web.UI.WebControls.ObjectDataSourceMethodEventArgs e)
 {
  var add = (BAGA.Address)(e.InputParameters["Address"]);
  //Insert and Delete both need the ContactReference. Setting
  //this property will cause the ContactReference.EntityKey to be created
  add.ContactID = (int)custDV.SelectedValue;
}

That's it. Don't forget to wire up this new event to both the Inserting and Deleting events of the AddressDataSource in the C# Windows form.

NOTE

Because of the large amount of code used in this example, the full code listings in both VB and C# are available on the book's website.


21.5. Understanding Why We Didn't Use Object Graphs in This Business Layer

ObjectDataSources simplify data binding when you want to use your own business classes. However, some of the features of the Entity Framework require that you do a little extra work on both ends to make sure everything flows properly. But the simplicity comes with a cost, which is that you don't get to take advantage of many of the Entity Framework's benefits.

What about doing something so that you can work with entity graphs?

With the Entity Framework, a single query can provide the entire graph of the Customer with Addresses and Reservations. A single SaveChanges call can update all of the entities at once.

ObjectDataSource, on the other hand, performs many queries and updates because of the separation of the objects, and so that it can work well with the short page life cycle. Each insert, update, and delete will be followed by a new select.

21.5.1. Database Hits Versus Very Busy Memory

The alternative is to have less database activity and take advantage of View State and session state. The benefits of View State and session state are combined with a lot of drawbacks. The impact of storing the extra data, as explained in the first section of the chapter, isn't the only drawback; rather, with session state you also have to worry about things such as session timeouts and distributed web servers. Additionally, when you store objects into session state, the session state manager must serialize and deserialize each object every time a class that uses session state is instantiated.

NOTE

For a great look at how session state works and how to get better performance when using it, read the September 2005 MSDN Magazine article "Fast, Scalable, and Secure Session State Management for Your Web Applications" by Mike Volodarsky, a web server guru on the ASP.NET team. You can find it online at http://msdn.microsoft.com/magazine/.

Most guidance that you will find recommends using View State and session state to persist small bits of data across postbacks that are not available elsewhere.

Keep in mind that less database activity doesn't necessarily mean fewer trips to the server. With each retrieval, update, insert, and delete the user requests, the server-side code must still get hit. So, the only thing you are reducing is the number of additional hits from the web server to the database. But you are replacing that with heavy activity in the server's memory, which could be worse.

There have been many impassioned debates about View State versus session state versus no state at all since ASP.NET was released, and the debates continue.

Due to all of the potential issues with attempting to store the objects in memory in a web application, going this route will be a choice for developers who have thoroughly considered the pros and cons of doing so and determined that in their particular scenario, storing the objects in memory and doing the additional work of managing the entities is still preferable. However, this will be a less common solution, whereas the ObjectDataSource should satisfy a majority of your needs.


21.6. Summary

In this chapter, you learned about the issues you will encounter when using entities in an ASP.NET application where the data and business layers are separated from the UI. Although ASP.NET provides a number of options for working with entities, the prudent choice for many developers is to build a separate class that interacts with the Entity Data Model for each CRUD action the user requests. When you structure the class properly, you can use it easily with an ObjectDataSource on the web page, which simplifies data-binding tasks. However, because the class is specialized for ObjectDataSource, it might not be appropriate to reuse it in other applications.

Entities introduce some new challenges for developers who have already been working with ObjectDataSources—most notably, working with EntityReference navigation properties. The example laid out in this chapter points out where these problems arise and how to solve them.

The alternative to this solution is to pull a graph into memory, work against that, and then call SaveChanges when the user is finished editing the entire graph. This option introduces a lot of complexities that may be acceptable for developers who are building complex websites and need to finely balance every bit of memory access and network activity. But it will take some advanced understanding of ASP.NET state management to achieve that balance.

Now that you have written layers for client applications and web applications, the last scenario is to write a more advanced business layer for using entity graphs in WCF services. This will be the job of the next chapter.


Chapter 22. Implementing a Smarter WCF Service for Working with Entities

In a world where services are increasingly being used to enable distributed computing, no one can take services lightly when approaching the Entity Framework. As with web applications, using services to provide read-only information to client applications is pretty straightforward. However, when you want those clients to be able to update data through your services, the Entity Framework's change tracking breaks down as you try to move data across tiers, and it creates big problems for performing updates.

In Chapter 14, you wrote your first services using the Entity Framework. The ASMX Web Service demonstrated a simple solution whereby single entities were sent to explicit insert, update, and delete operations. No graphs were involved in the messages being sent to the service. The WCF service in the second half of that chapter went a step further and worked with graphs. This introduced the challenge of determining the state of each child in the graph that came back to the service. You may recall using assumptions such as "if an entity's ID is 0, it must be new." You also forced the client to send the deleted entity IDs in a separate object within a data contract. This worked, and it leveraged your existing knowledge of the Entity Framework at that point in the book. But the service was complicated, it forced the client to do a lot of extra work, and in some programming scenarios those assumptions just won't work.

Now that you have learned so much more about the Entity Framework, you can build a smarter WCF service that reduces the intensity of the work required on the client side and takes advantage of some of the more advanced features of the Entity Framework.

Guidance for writing distributed applications—especially WCF service applications—with the Entity Framework has been one of the features that enterprise developers have requested most since the early betas. The pattern and ideas laid forth in this chapter should go a long way toward helping you construct your service-oriented applications with version 1 of the Entity Framework.

NOTE

Although this chapter has a lot of code in it, the chapter doesn't provide the entire solution. For that, please visit the book's website.


22.1. Will Your Client Agree to Your Data Contract?

Although it would be very nice to provide a web service that clients can use randomly, without them having to know much more than what the operations expect and will return, the challenges of change tracking throw a wrench into this plan.

If you want to provide your own services over which you have complete control, consumers of your service will have to make some accommodations on the client side. The solution presented in this chapter requires that the consuming applications adopt a few prewritten classes and follow a small number of very specific rules.

Like with the solutions in Chapter 14, the clients can still be "Entity Framework-agnostic"; in other words, they do not need to reference the Entity Framework APIs or your model's assembly. In fact, they don't even need to be .NET applications, though this would require that they rewrite your provided logic to duplicate the functionality.

ADO.NET Data Services for Public Services

There is an alternative for scenarios in which enforcing rules on the client is not possible. ADO.NET Data Services, originally known as Project Astoria, provides a means to expose your Entity Data Model (EDM) and the data store behind the model through a WCF service. ADO.NET Data Services is a feature of .NET 3.5 that was released along with the Entity Framework with Visual Studio 2008 Service Pack 1.

With ADO.NET Data Services, you can expose various entities through a URL endpoint, which will inherently provide Read, Write, Insert, and Delete capabilities using HTTP verbs such as GET, PUT, and POST. The services have built-in functionality that takes care of all of the change-tracking issues and so much more.

The data is not provided through specific service operations, but through HTTP calls that can be made directly in languages such as JavaScript or by using the .NET APIs that are part of the ADO.NET Data Services framework. In addition, you can customize how the service works in many ways.

There's much more to this technology, and a web search will uncover numerous articles, blog posts, demos, webcasts, conference presentations, and books devoted to the topic.

Although ADO.NET Data Services is very convenient and is heroic in the way it eliminates the biggest problems you encounter when building your own services with the Entity Framework, the feature is designed to provide data as a resource, as opposed to providing service operations.

ADO.NET Data Services is not targeted for closed enterprise applications, but rather for Internet applications that need a mechanism to access data through HTTP.

You should consider using ADO.NET Data Services when you want your services to be publicly available. You won't have to worry about data contracts or how consumers are using your services. All of the rules will be built right into the data services. Additionally, because ADO.NET Data Services has a common interface, consumers will already know how to interact with your data, and they won't have to learn about specific operations.

You should consider using ADO.NET Data Services when you don't need granular control over your services and all you really want to do is make it easy for people to access your data.


22.2. Shipping DTOs, Not EntityObjects

The approach to this service is quite different from what you wrote in Chapter 14. Here are the key factors for this new service.

Data Transfer Objects (DTOs) will be used to ship the data between the service and the client. As you saw with the few DTOs you constructed in the earlier services, these are much lighter in weight.

In addition to the properties that match the EntityObjects, each DTO will contain its own EntityState property so that you don't need to rely on the nonexistent ObjectContext to keep track of the ObjectState on the client side.

Remember the XML you saw in Chapter 14 that represented what the payload of a Customer entity object looked like? Conversely, the ShortCust object that you also created in that chapter for both the web and WCF services was very small. This was a DTO.

Because your service will be designed to support Entity Framework-agnostic clients, there is really no need to send all of that extra data down the wire, so for this service you'll create DTOs for each entity that will be used: Customer, Address, Reservation, Trip, and Payment. You can store the DTOs in a separate project so that you can use the project from different services.

NOTE

You should give the new project a namespace that will help you differentiate entity classes from the DTO classes when they have the same name, as you will use both in the service implementation. My entity objects are in the BAGA namespace, so the namespace that I'm using for the DTO project is BAGA.DTO.

22.2.1. Building a Data Transfer Object to Take the Place of an Entity Object

As you saw with ShortCust, you need to mark each class as a DataContract and each property as a DataMember attribute. To be serialized, the properties must have a getter and a setter.

Example 22-1 shows part of the new Customer class with a few of its properties. The consumer does not care that the Customer inherits from a Contact; therefore, there is nothing here about the inheritance. The Customer class is independent. You can create a single Customer class with the appropriate fields. In Visual Basic, each property has a local variable associated with it and that variable has been placed directly after the property. In C#, you can take advantage of auto-implemented properties.

Example 22-1. Part of the new Customer DTO class

<DataContract()> _
Partial Public Class Customer

  <DataMember()> _
  Public Property CustomerID() As Integer
    Get
      Return Me._CustomerID
    End Get
    Set(ByVal value As Integer)
      Me._CustomerID = value
    End Set
  End Property
  Private _CustomerID As Integer

  <DataMember()> _
  Public Property FirstName() As String
    Get
      Return Me._FirstName
    End Get
    Set(ByVal value As String)
      Me._FirstName = value
    End Set
  End Property
  Private _FirstName As String

End Class

[DataContract()]
public partial class Customer
{
  [DataMember()]
  public int CustomerID {get ; set; }

  [DataMember()]
  public string FirstName {get ; set; }
}

22.2.1.1. Replacing EntityReference navigation properties

The navigation properties for entity references, such as the preference properties, have been replaced with one property to contain its key value and another to contain its display value. This is the information that will be useful to a consumer of your service. The VB fields set default values for their properties. If you use the auto-implemented properties in C#, you can set the defaults in the class constructor.

In Example 22-2, the PrimaryActivityName and PrimaryActivityID properties in the Customer DTO replace PrimaryActivityReference. There is no need for an Activity value object here. The name and ID are all the client application will need.

Example 22-2. Properties in the Customer DTO replacing the PrimaryActivityReference

<DataMember()> _
  Public Property PrimaryActivityName() As String
    Get
      Return _PrimaryActivityName
    End Get
    Set(ByVal value As String)
      _PrimaryActivityName = value
    End Set
  End Property
  Private _PrimaryActivityName As String = ""

  <DataMember()> _
  Public Property PrimaryActivityID() As Integer
    Get
      Return _PrimaryActivityID
    End Get
    Set(ByVal value As Integer)
      _PrimaryActivityID = value
    End Set
  End Property

   [DataMember()]
  public string PrimaryActivityName {get;set;}
  [DataMember()]
  public int PrimaryActivityID {get;set;}

  public Customer()
  {
    //initialize properties
    PrimaryActivityName="";
    PrimaryActivityID=0;
  }

22.2.1.2. Replacing EntityCollection navigation properties

Customer.Reservations and other child EntityCollections have been replaced with properties that return lists, as shown in the Reservations property in Example 22-3.

Example 22-3. A property to expose the Reservations child collection

<DataMember()> _
  Public Property Reservations() As List(Of Reservation)
    Get
      Return _Reservations
    End Get
    Set(ByVal value As List(Of Reservation))
      If (Not (value) Is Nothing) Then
        _Reservations = value
      End If
    End Set
  End Property
  Private _Reservations

[DataMember()]
public List<Reservation> Reservations
{
  get
  {
    return _Reservations;
  }
  set
  {
    if ((value) != null)
      _Reservations = value;
  }
}
  private List<Reservation> _Reservations;

Adjustments have been made to some of the classes to expose properties that will be more logical to the consumer. For example, the Reservation class has properties for ReservationID, ReservationDate, TripDetails, and CustomerID.


22.3. Creating EntityState Properties That Do Not Rely on ObjectContext

One of the key features of this new service is to provide a more dependable way to determine an entity's state when it has been returned from the client. Previously, we used some assumptions that will work for many scenarios, but not all. One assumption was that all added entities would have an ID of 0; another was that a modified ModifiedDate property would indicate that an entity had been changed. Tracking deleted entities was more complicated and required that the client provide a list of IDs for all entities that were deleted on the client side.

Although that method for tracking deleted entities is dependable, it means a lot of extra work on the client's behalf, as well as in the service. The method to identify added or modified entities is indeterminate, which means it is possible that an entity will have an ID of 0 even if it's not new, or that a changed entity will have an Unchanged ModifiedDate field.

Rather than depending on these mechanisms, you can add a property directly to the classes that will be edited on the client side so that when the entities are returned, you will know exactly what their state is.

22.3.1. The EntityStateLocal Enums and Interface

Rather than adding the property into each entity in the model, you can create an interface and implement it in the DTO classes that will be used in the service. The interface provides a single property: EntityState_Local.

The EntityState_Local property is tied to a new enum, EntityStateLocal. For consistency, this enum provides the same members and values as the Entity Framework's System.Data.EntityState enum.

Because this enum needs to be available to the service's client, it is given a DataContract attribute and each enum has an EnumMember attribute. The files that contain the interface and the enum can go into the same project as the DTOs that need to access them. In addition, you can put both the interface and the enum into a single file in the model's project.

Example 22-4 shows what the code for the interface and enums looks like.

Example 22-4. The EntityState interface and the enums on which it will depend

Public Interface IEntityStateLocal
  Property EntityState_Local() As EntityStateLocal
End Interface

<DataContract()> _
Public Enum EntityStateLocal
  <EnumMember()> Modified = 16
  <EnumMember()> Detached = 1
  <EnumMember()> Unchanged = 2
  <EnumMember()> Added = 4
  <EnumMember()> Deleted = 8
End Enum

public interface IEntityStateLocal
{
  EntityStateLocal EntityState_Local {get; set;}
}

[DataContract()]
public enum EntityStateLocal: int
{
  [EnumMember()]
  Modified = 16,
  [EnumMember()]
  Detached = 1,
  [EnumMember()]
  Unchanged = 2,
  [EnumMember()]
  Added = 4,
  [EnumMember()]
  Deleted = 8
}

22.3.2. Implementing the IEntityStateLocal Interface in the DTO Classes

The service will allow users to edit customers, reservations, and addresses. After implementing the interface in each of the necessary classes, you need to implement its members—in this case, the EntityState_Local property. The property needs to be serialized as part of the class; therefore, mark it as a DataMember. Additionally, as with the other properties, you will need to declare a local variable for the property to use.

First, modify the partial class declaration, as shown in the following code:

<DataContract()> _
Partial Public Class Customer
  Implements IEntityStateLocal

  <DataMember()> _
  Public Property EntityState_Local() As EntityStateLocal _
   Implements IEntityStateLocal.EntityState_Local
    Get
      Return Me._EntityStateLocal
    End Get
    Set(ByVal value As EntityStateLocal)
      Me._EntityStateLocal = value
    End Set
  End Property
  Private _EntityStateLocal As EntityStateLocal

[DataContract()]
public partial class Customer : IEntityStateLocal
{

  [DataMember()]
  public EntityStateLocal EntityState_Local
  { get ; set ;}

NOTE

A funny side effect of the automatic property syntax in C# is that it looks exactly the same as the interface.

You can use the same code in any of the classes that the consuming application will modify. In this example, that would be Customer, Address, and Reservation.

You'll also need to use the IEntityStateLocal interface in the model's entity classes that align with these DTOs.

22.3.3. Implementing the IEntityStateLocal Interface in the Entity Classes

The DTOs will bring the EntityState values back from the consumer, but when you convert the DTOs back into entities you need to retain those properties. To do that the entity classes need to implement the interface as well. If the interface is in its own project, you can reference it. This is preferable for building reusable code. Otherwise, you'll minimally need to get the interface code along with the custom enums into the model's project.

In the model assembly, implement the interface in the same three classes: Customer, Address, and Reservation. You'll do this by adding the Implements syntax to the partial classes created to extend the code-generated classes. An important part of the implementation means also implementing the EntityState_Local property from the interface. In Example 22-5, the Address partial class shows the implementation.

Example 22-5. The Address entity class implementing IEntityStateLocal

Partial Public Class Address
  Implements IEntityStateLocal
  Dim _localState As EntityStateLocal

  <DataMember()> _
  Public Property EntityState_Local() As EntityStateLocal
   Implements IEntityStateLocal.EntityState_Local
    Get
      Return _localState
    End Get
    Set(ByVal value As EntityStateLocal)
      _localState = value
    End Set
  End Property

   'other class properties

public partial class Address : IEntityStateLocal
{
  [DataMember]
  public EntityStateLocal EntityState_Local { get; set; }

  //other class properties

The Customer class has a twist. Because it inherits from Contact, you must implement the interface in the Contact class, not in the Customer class.

You'll see this pay off when it's time to update the customer in the service.


22.4. Designing the Service Interface

This service will allow consumers to modify a customer and its related addresses and reservations, and then save that information. The service contract contains four operations:

  • GetCustomerList returns a list of customer names with their IDs. It uses the same ShortCust class that the previous service used, except that it has been renamed to ShortCustomer.

  • GetCustomer returns a customer graph that includes the customer's addresses and reservations. The reservations include the reference objects for trip details, payments, and unpaid reservations.

  • SaveCustomer takes a customer graph and updates any customer, address, or reservation information that has been added, deleted, or modified on the client side.

  • GetTrips will provide a list of trips so that the user will be able to create new reservations for customers by selecting for which trip the reservation was made.

Example 22-6 shows the operation contracts for the WCF service.

Example 22-6. Operation contracts for the WCF service

<ServiceContract()> _
Public Interface ICustomerService

  <OperationContract()> _
  Function GetCustomerList() As List(Of ShortCustomer)

  <OperationContract()> _
  Function SaveCustomer(ByVal new_Cust As BAGA.DTO.Customer) As Boolean

  <OperationContract()> _
  Function GetCustomer(ByVal CustomerID As Integer) As BAGA.DTO.Customer

  <OperationContract()> _
  Function GetTrips() As List(Of BAGA.DTO.Trip)

End Interface

[ServiceContract()]
public interface ICustomerService
{
  [OperationContract()]
  List<ShortCustomer> GetCustomerList();

  [OperationContract()]
  bool SaveCustomer(BAGA.DTO.Customer new_Cust);

  [OperationContract()]
  BAGA.DTO.Customer GetCustomer(int CustomerID);

  [OperationContract()]
  List<BAGA.DTO.Trip> GetTrips();
}


22.5. Implementing the Service Operations

The CustomerService class contains the implementations of the contract operations defined in ICustomerService, as well as some helper methods. This class will take advantage of the Customer provider and the List provider and in turn the CommandExecutor class that you built for the ASP.NET application in Chapter 21. Your service can reference the project that contains these classes.

22.5.1. Returning CustomerList as ShortCustomers with GetCustomerList

GetCustomerList returns a list of ShortCustomer objects containing customer names and IDs, as shown in Example 22-7. The CustomerProvider class does not contain a meaningful method to return the correct data for this list. You can certainly extend that class do so, however, for this example, we'll just code the query into the GetCustomerList method and call the CommandExecutor method directly.

Example 22-7. The GetCustomerList service operation

Public Function GetCustomerList() As List(Of ShortCustomer) _
 Implements ICustomerService.GetCustomerList
  Using context As New BAEntities
    Dim custs = From c In context.Contacts.OfType(Of Customer)() _
                Order By c.LastName & c.FirstName _
                Select New ShortCustomer With _
                     {.ContactID = c.ContactID, _
                      .Name = c.LastName.Trim & ", " & c.FirstName.Trim}
    Dim dal as New DAL.CommandExecutor
    Return dal.ExecuteList(custs)
  End Using
End Function

public List<ShortCustomer> GetCustomerList()
{
  using (BAEntities context = new BAEntities())
  {
    var custs = from c in context.Contacts.OfType<Customer>()
                orderby c.LastName + c.FirstName
                select new ShortCustomer {ContactID = c.ContactID,
                       Name = c.LastName.Trim() + ", " + c.FirstName.Trim()};
    var dal=new BAGA.DAL.CommandExecutor();
    return dal.ExecuteList(custs);
  }
}

22.5.2. Returning Additional Reference Lists

GetTrips, shown in Example 22-8, returns a reference list for selecting trips. The type returned in the List is Trip DTO. After creating the reference list of Trips entities, use the same method that you used in Chapter 21.

Example 22-8. The GetTrips service operation

Public Function GetTrips() As List(Of BAGA.DTO.Trip) _
 Implements ICustomerService.GetTrips
  Using listProvider As New BAGA.Providers.ListProvider
    Dim tripsList = CType(ListProvider.GetReferenceList(Of Trip)(""), _
                        List(Of Trip))
  End Using
  Dim trips = From t In tripsList _
              Select New BAGA.DTO.Trip With _
              {.TripID = t.TripID, .TripDetails = t.TripDetails}
  Return trips.ToList
End Function

public  List<BAGA.DTO.Trip> GetTripsDTO()
{
  using (var listProvider=new BAGA.Providers.ListProvider())
  {
    List<Trip> tripsList = (List<Trip>)listProvider.GetReferenceList<Trip>("");
  }
  var trips =
      from t in tripsList
      select new BAGA.DTO.Trip
      {TripID = t.TripID, TripDetails = t.TripDetails};
  return trips.ToList();
}

22.5.3. Returning a Single Entity Graph for Editing with GetCustomer

GetCustomer gets the appropriate customer by passing the incoming CustomerID to a new method in the CustomerProvider class, GetCustomerwithReservationAndAddresses. Building on previous lessons, this method leverages a compiled LINQ to Entities query, which eager loads the related reservations and addresses for the given customer. As you did in Chapter 20, this query is declared in the CustomerProvider class declarations and defined and compiled in a method called PreCompileCustGraph. Notice in Example 22-9 that the variable is Shared/static so that it is available throughout the lifetime of the web service and only re-compiled if it's gone out of scope.

Example 22-9. New functionality added to the CustomerProvider class for a compiled query

Public Shared _compiledCustGraphQuery _
 As Func(Of BAEntities, Integer, IQueryable(Of Customer))

Private Sub PreCompileCustGraph()
  _compiledCustGraphQuery = CompiledQuery.Compile _
    (Function(ctx As BAEntities, custID As Integer) _
       From c In _commonContext.Contacts.OfType(Of Customer) _
                             .Include("Addresses") _
                             .Include("Reservations.Trip.Destination") _
                             .Include("Reservations.UnpaidReservations") _
                             .Include("Reservations.Payments") _
      Where c.ContactID = custID)
End Sub

Public Function GetCustomerwithReservationsAndAddresses _
 (ByVal ContactID As Int32) As Customer
  If _compiledCustGraphQuery Is Nothing Then
    PreCompileCustGraph()
  End If
  Dim query = _compiledCustGraphQuery.Invoke(_commonContext,ContactID)
  Return _dal.ExecuteFirstorDefault(query)
End Function

static Func<BAEntities, int, IQueryable<Customer>> _compiledCustGraphQuery;

private void PreCompileCustGraph()
{
  _compiledCustGraphQuery =
        CompiledQuery.Compile((BAEntities ctx, int custID) =>
        from c in _commonContext.Contacts.OfType<Customer>()
                                .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 the Service 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<BAGA.DTO.Reservation>();
  foreach (var res in cust.Reservations)
    custDTO.Reservations.Add(ReservationEF_toReservationDTO(res));

  custDTO.Addresses = new List<BAGA.DTO.Address>();
  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<BAGA.DTO.Payment>();
  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 to Unchanged 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<T>(List<T> 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 the Reservation.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 TimeStamps 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 of ExecuteFirstOrDefault 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<Customer>()
                     .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 of SaveCustomer 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 the SaveAllChanges wrapper method that you saw in previous chapters. The SaveAllChanges method has been moved to the partial class for BAEntities in the model's assembly. SaveAllChanges calls SaveChanges and handles OptimisticConcurrency and other entity exceptions.

Example 22-15 shows the code listing for the entire SaveCustomer 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<Address>(custEF.Addresses.ToList(),
                                 custFromServer.Addresses,
                                 context, "Addresses");
          UpdateChildren<Reservation>(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 the IEntityStateLocal 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 only EntityObjects 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 the Customer. First it finds the children whose state is Added 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 for Modified children and uses the ApplyPropertyChanges method to update the matching entity that is already in the context.

Finally, the method looks for Deleted children. Any children marked for deletion are located in the context using the ObjectStateManager and are then deleted using the DeleteObject 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<TEntity>
  (List<TEntity> childCollectionfromClient, EntityCollection<TEntity>
   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 their EntityState_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 <system.serviceModel> 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 the DataContract 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 the EntityState_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 the EntityState_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 the Reservation class.

As the parent node of the graph, the Customer class has some additional responsibilities. When a Customer 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 standard Collection.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<ShortCustomer> 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<Trip> GetTripList()
{
  using (var svc = new CustomerServiceClient())
  {
    return svc.GetTrips();
  }
}

The GetCustomer method, shown next, calls Customer.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 a Customer 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 EntityObjects 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 call Load 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 to DefiningQueries, which we discussed in Chapter 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:

  • <system drive>:\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).

  • <system drive>:\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 the System.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 as EntityKey, 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 of SqlDataReader, 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 a DbDataRecord 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 the System.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 as EntityConnection, 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.


Colophon

Appendix. Colophon

The animal on the cover of Programming 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 from Riverside 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]